diff --git a/README.md b/README.md
index 5e686096..5c6a7577 100755
--- a/README.md
+++ b/README.md
@@ -221,6 +221,7 @@ All the deployed contracts' addresses are available [here](../../wiki/Addresses)
| [Melon](contracts/adapters/melon) | A protocol for decentralized on-chain asset management. | [Asset adapter](contracts/adapters/melon/MelonAssetAdapter.sol) | ["MelonToken"](./contracts/adapters/melon/MelonTokenAdapter.sol) |
| [mStable](./contracts/adapters/mstable) | mStable unifies stablecoins, lending and swapping into one standard. | [Asset adapter](./contracts/adapters/mstable/MstableAssetAdapter.sol)
[Staking adapter](./contracts/adapters/mstable/MstableStakingAdapter.sol) | ["Masset"](./contracts/adapters/mstable/MstableTokenAdapter.sol) |
| [Nexus Mutual](./contracts/adapters/nexus) | A people-powered alternative to insurance. | [Staking adapter](./contracts/adapters/nexus/NexusStakingAdapter.sol) | — |
+| [Pendle Finance](./contracts/adapters/pendle) | Trading future yield. | [Asset adapter](./contracts/adapters/pendle/PendleMarketAdapter.sol)
[Staking adapter](./contracts/adapters/pendle/PendleStakingAdapter.sol) | ["Pendle Market"](./contracts/adapters/pendle/PendleMarketTokenAdapter.sol) |
| [Pickle Finance](./contracts/adapters/pickle) | Off peg bad, on peg good. | [Asset adapter](./contracts/adapters/pickle/PickleAssetAdapter.sol)
[Staking adapter V1](./contracts/adapters/pickle/PickleStakingV1Adapter.sol)
[Staking adapter V2](./contracts/adapters/pickle/PickleStakingV2Adapter.sol) | ["PickleJar"](./contracts/adapters/pickle/PickleTokenAdapter.sol) |
| [PieDAO](./contracts/adapters/pieDAO) | The Asset Allocation DAO. | [Asset adapter](./contracts/adapters/pieDAO/PieDAOPieAdapter.sol)
[Staking adapter](./contracts/adapters/pieDAO/PieDAOStakingAdapter.sol) | ["PieDAO Pie Token"](./contracts/adapters/pieDAO/PieDAOPieTokenAdapter.sol) |
| [PoolTogether](./contracts/adapters/poolTogether) | Decentralized no-loss lottery. Supports SAI, DAI, and USDC pools. | [Asset adapter](./contracts/adapters/poolTogether/PoolTogetherAdapter.sol) | ["PoolTogether pool"](./contracts/adapters/poolTogether/PoolTogetherTokenAdapter.sol) |
diff --git a/contracts/adapters/pendle/PendleMarketAdapter.sol b/contracts/adapters/pendle/PendleMarketAdapter.sol
new file mode 100755
index 00000000..cddf93b1
--- /dev/null
+++ b/contracts/adapters/pendle/PendleMarketAdapter.sol
@@ -0,0 +1,42 @@
+// Copyright (C) 2020 Zerion Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+pragma solidity 0.6.5;
+pragma experimental ABIEncoderV2;
+
+import { ERC20 } from "../../ERC20.sol";
+import { ProtocolAdapter } from "../ProtocolAdapter.sol";
+
+/**
+ * @title Asset adapter for Pendle Finance (markets).
+ * @dev Implementation of ProtocolAdapter abstract contract.
+ * @author Anton Buenavista
+ */
+contract PendleMarketAdapter is ProtocolAdapter {
+
+ string public constant override adapterType = "Asset";
+
+ string public constant override tokenType = "PendleMarket LP token";
+
+ /**
+ * @return Amount of Pendle Market LP tokens held by the given account.
+ * @param token Address of the Pendle Market.
+ * @param account Address of the user.
+ * @dev Implementation of ProtocolAdapter interface function.
+ */
+ function getBalance(address token, address account) public view override returns (uint256) {
+ return ERC20(token).balanceOf(account);
+ }
+}
diff --git a/contracts/adapters/pendle/PendleMarketTokenAdapter.sol b/contracts/adapters/pendle/PendleMarketTokenAdapter.sol
new file mode 100644
index 00000000..bb40f8de
--- /dev/null
+++ b/contracts/adapters/pendle/PendleMarketTokenAdapter.sol
@@ -0,0 +1,135 @@
+// Copyright (C) 2020 Zerion Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+pragma solidity 0.6.5;
+pragma experimental ABIEncoderV2;
+
+import { ERC20 } from "../../ERC20.sol";
+import { TokenMetadata, Component } from "../../Structs.sol";
+import { TokenAdapter } from "../TokenAdapter.sol";
+
+
+/**
+ * @dev PendleMarket contract interface.
+ * Only the functions required for PendleTokenAdapter contract are added.
+ * The PendleMarket contract is available here
+ * github.com/pendle-finance/pendle-core/blob/master/contracts/interfaces/IPendleMarket.sol.
+ */
+interface PendleMarket {
+ function xyt() external view returns (address);
+ function token() external view returns (address);
+ function getReserves()
+ external
+ view
+ returns (
+ uint256 xytBalance,
+ uint256 xytWeight,
+ uint256 tokenBalance,
+ uint256 tokenWeight,
+ uint256 currentBlock
+ );
+}
+
+/**
+ * @title Token adapter for PendleMarket Tokens.
+ * @dev Implementation of TokenAdapter abstract contract.
+ * @author Anton Buenavista
+ */
+contract PendleMarketTokenAdapter is TokenAdapter {
+
+ /**
+ * @return TokenMetadata struct with ERC20-style token info.
+ * @dev Implementation of TokenAdapter interface function.
+ */
+ function getMetadata(address token) external view override returns (TokenMetadata memory) {
+ return TokenMetadata({
+ token: token,
+ name: getMarketName(token),
+ symbol: ERC20(token).symbol(),
+ decimals: ERC20(token).decimals()
+ });
+ }
+
+ /**
+ * @return Array of Component structs with underlying tokens rates for the given token.
+ * @dev Implementation of TokenAdapter abstract contract function.
+ */
+ function getComponents(address market) external view override returns (Component[] memory) {
+ address token = PendleMarket(market).token();
+ address xyt = PendleMarket(market).xyt();
+ uint256 totalSupply = ERC20(market).totalSupply();
+ (uint256 xytBalance, ,uint256 tokenBalance, , ) = PendleMarket(market).getReserves();
+
+ Component[] memory components = new Component[](2);
+
+ components[0] = Component({
+ token: xyt,
+ tokenType: "ERC20",
+ rate: totalSupply == 0 ? 0 : xytBalance * 1e18 / totalSupply
+ });
+ components[1] = Component({
+ token: token,
+ tokenType: "ERC20",
+ rate: totalSupply == 0 ? 0 : tokenBalance * 1e18 / totalSupply
+ });
+
+ return components;
+ }
+
+ function getMarketName(address market) internal view returns (string memory) {
+ return string(
+ abi.encodePacked(
+ getSymbol(PendleMarket(market).xyt()),
+ "/",
+ getSymbol(PendleMarket(market).token()),
+ " Market"
+ )
+ );
+ }
+
+ function getSymbol(address token) internal view returns (string memory) {
+ (, bytes memory returnData) = token.staticcall(
+ abi.encodeWithSelector(ERC20(token).symbol.selector)
+ );
+
+ if (returnData.length == 32) {
+ return convertToString(abi.decode(returnData, (bytes32)));
+ } else {
+ return abi.decode(returnData, (string));
+ }
+ }
+
+ /**
+ * @dev Internal function to convert bytes32 to string and trim zeroes.
+ */
+ function convertToString(bytes32 data) internal pure returns (string memory) {
+ uint256 length = 0;
+ bytes memory result;
+
+ for (uint256 i = 0; i < 32; i++) {
+ if (data[i] != byte(0)) {
+ length++;
+ }
+ }
+
+ result = new bytes(length);
+
+ for (uint256 i = 0; i < length; i++) {
+ result[i] = data[i];
+ }
+
+ return string(result);
+ }
+}
diff --git a/contracts/adapters/pendle/PendleStakingAdapter.sol b/contracts/adapters/pendle/PendleStakingAdapter.sol
new file mode 100644
index 00000000..f1e3b642
--- /dev/null
+++ b/contracts/adapters/pendle/PendleStakingAdapter.sol
@@ -0,0 +1,53 @@
+// Copyright (C) 2020 Zerion Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+pragma solidity 0.6.5;
+pragma experimental ABIEncoderV2;
+
+import { ProtocolAdapter } from "../ProtocolAdapter.sol";
+
+
+/**
+ * @dev Proxy contract interface for calculating liquidity mining rewards.
+ * Only the functions required for PendleStakingZerionProxy contract are added.
+ */
+interface PendleStakingZerionProxy {
+ function earned(address user) external view returns (uint256);
+}
+
+/**
+ * @title Adapter for Pendle Finance protocol.
+ * @dev Implementation of ProtocolAdapter interface.
+ * @author Anton Buenavista
+ */
+contract PendleStakingAdapter is ProtocolAdapter {
+
+ string public constant override adapterType = "Asset";
+
+ string public constant override tokenType = "ERC20";
+
+ address internal constant PENDLE = 0x808507121B80c02388fAd14726482e061B8da827;
+ address internal constant STAKING_WRAPPER = 0x3cFfEd42e0BD6d45894283d90cF3F75e7CA55855;
+
+ /**
+ * @return Amount of PENDLE rewards after staking for a given account.
+ * @dev Implementation of ProtocolAdapter interface function.
+ */
+ function getBalance(address token, address account) external view override returns (uint256) {
+ if (token == PENDLE) {
+ return PendleStakingZerionProxy(STAKING_WRAPPER).earned(account);
+ }
+ }
+}
diff --git a/test/PendleMarketAdapter.js b/test/PendleMarketAdapter.js
new file mode 100755
index 00000000..103a9383
--- /dev/null
+++ b/test/PendleMarketAdapter.js
@@ -0,0 +1,99 @@
+import displayToken from './helpers/displayToken';
+
+const AdapterRegistry = artifacts.require('AdapterRegistry');
+const ProtocolAdapter = artifacts.require('PendleMarketAdapter');
+const TokenAdapter = artifacts.require('PendleMarketTokenAdapter');
+const ERC20TokenAdapter = artifacts.require('ERC20TokenAdapter');
+
+contract('PendleMarketTokenAdapter', () => {
+ const ytUSDCMarket = '0x8315BcBC2c5C1Ef09B71731ab3827b0808A2D6bD';
+ const ytUSDCAddress = '0xcDb5b940E95C8632dEcDc806B90dD3fC44E699fE';
+ const usdcAddress = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48';
+ // Random address with positive balances
+ const testAddress = '0x76A16d9325E9519Ef1819A4e7d16B168956f325F';
+
+ let accounts;
+ let adapterRegistry;
+ let protocolAdapterAddress;
+ let tokenAdapterAddress;
+ let erc20TokenAdapterAddress;
+ const PENDLE_LPT = [
+ ytUSDCMarket,
+ 'YT-aUSDC-29DEC2022/USDC Market',
+ 'PENDLE-LPT',
+ '18',
+ ];
+ const YT = [
+ ytUSDCAddress,
+ 'YT Aave interest bearing USDC 29DEC2022',
+ 'YT-aUSDC-29DEC2022',
+ '6',
+ ];
+ const USDC = [
+ usdcAddress,
+ 'USD Coin',
+ 'USDC',
+ '6',
+ ];
+
+ beforeEach(async () => {
+ accounts = await web3.eth.getAccounts();
+ await ProtocolAdapter.new({ from: accounts[0] })
+ .then((result) => {
+ protocolAdapterAddress = result.address;
+ });
+ await TokenAdapter.new({ from: accounts[0] })
+ .then((result) => {
+ tokenAdapterAddress = result.address;
+ });
+ await ERC20TokenAdapter.new({ from: accounts[0] })
+ .then((result) => {
+ erc20TokenAdapterAddress = result.address;
+ });
+ await AdapterRegistry.new({ from: accounts[0] })
+ .then((result) => {
+ adapterRegistry = result.contract;
+ });
+ await adapterRegistry.methods.addProtocols(
+ ['Pendle Market'],
+ [[
+ 'Mock Protocol Name',
+ 'Mock protocol description',
+ 'Mock website',
+ 'Mock icon',
+ '0',
+ ]],
+ [[
+ protocolAdapterAddress,
+ ]],
+ [[[
+ ytUSDCMarket,
+ ]]],
+ )
+ .send({
+ from: accounts[0],
+ gas: '1000000',
+ });
+ await adapterRegistry.methods.addTokenAdapters(
+ ['ERC20', 'PendleMarket LP token'],
+ [erc20TokenAdapterAddress, tokenAdapterAddress],
+ )
+ .send({
+ from: accounts[0],
+ gas: '1000000',
+ });
+ });
+
+ it('should return correct balances', async () => {
+ await adapterRegistry.methods['getBalances(address)'](testAddress)
+ .call()
+ .then((result) => {
+ displayToken(result[0].adapterBalances[0].balances[0].base);
+ displayToken(result[0].adapterBalances[0].balances[0].underlying[0]);
+ displayToken(result[0].adapterBalances[0].balances[0].underlying[1]);
+ assert.deepEqual(result[0].adapterBalances[0].balances[0].base.metadata, PENDLE_LPT);
+ assert.deepEqual(result[0].adapterBalances[0].balances[0].underlying[0].metadata, YT);
+ assert.deepEqual(result[0].adapterBalances[0].balances[0].underlying[1].metadata, USDC);
+ });
+ });
+});
diff --git a/test/PendleStakingAdapter.js b/test/PendleStakingAdapter.js
new file mode 100644
index 00000000..ae6ff10b
--- /dev/null
+++ b/test/PendleStakingAdapter.js
@@ -0,0 +1,68 @@
+import displayToken from './helpers/displayToken';
+
+const AdapterRegistry = artifacts.require('AdapterRegistry');
+const ProtocolAdapter = artifacts.require('PendleStakingAdapter');
+const ERC20TokenAdapter = artifacts.require('ERC20TokenAdapter');
+
+contract('PendleStakingAdapter', () => {
+ const pendleAddress = '0x808507121B80c02388fAd14726482e061B8da827';
+ // Random address with positive balances
+ const testAddress = '0xf8F26686F1275E5AA23A82c29079C68d3De4D3b4';
+
+ let accounts;
+ let adapterRegistry;
+ let protocolAdapterAddress;
+ let erc20TokenAdapterAddress;
+
+ beforeEach(async () => {
+ accounts = await web3.eth.getAccounts();
+ await ProtocolAdapter.new({ from: accounts[0] })
+ .then((result) => {
+ protocolAdapterAddress = result.address;
+ });
+ await ERC20TokenAdapter.new({ from: accounts[0] })
+ .then((result) => {
+ erc20TokenAdapterAddress = result.address;
+ });
+ await AdapterRegistry.new({ from: accounts[0] })
+ .then((result) => {
+ adapterRegistry = result.contract;
+ });
+ await adapterRegistry.methods.addProtocols(
+ ['Pendle'],
+ [[
+ 'Mock Protocol Name',
+ 'Mock protocol description',
+ 'Mock website',
+ 'Mock icon',
+ '0',
+ ]],
+ [[
+ protocolAdapterAddress,
+ ]],
+ [[[
+ pendleAddress,
+ ]]],
+ )
+ .send({
+ from: accounts[0],
+ gas: '1000000',
+ });
+ await adapterRegistry.methods.addTokenAdapters(
+ ['ERC20'],
+ [erc20TokenAdapterAddress],
+ )
+ .send({
+ from: accounts[0],
+ gas: '1000000',
+ });
+ });
+
+ it('should return correct balances', async () => {
+ await adapterRegistry.methods['getBalances(address)'](testAddress)
+ .call()
+ .then((result) => {
+ displayToken(result[0].adapterBalances[0].balances[0].base);
+ });
+ });
+});