@morpho-org/morpho-aave-v3-sdk
v3.3.0
Published
📦 SDK library for interacting with Morpho AaveV3
Downloads
1,130
Readme
Morpho's AaveV3-ETH Optimizer SDK
This repository contains the core typescript features used to build a dapp based on Morpho-AaveV3 smart contracts.
Warning
This package is used by the morpho association to build the morpho-aaveV3 dapp but is still under development and subject to changes.
Use at your own risk. Any feedback is welcome.
Installation
You need to use a node version >= 18.0.0
npm install @morpho-org/morpho-aave-v3-sdk
yarn add @morpho-org/morpho-aave-v3-sdk
Configuration
At the root of your dapp or your script:
/* _app.ts */
import sdk from "@morpho-org/morpho-aave-v3-sdk/configuration";
sdk.setConfiguration(config);
where config
is an object with the following optional properties:
| property | type | default | description |
|------------------------| ------------------------------------ |---------------------------|-----------------------------------------------------------------------------------------------------------------------------------------|
| isProd
| boolean
| false
| Set to true
if the dapp is running in production |
| defaultProvider
| string
| process.env.RPC_URL
| The default provider to use. It fallbacks on the default provider from ethers
|
| defaultMaxIterations
| { supply: number; borrow: number }
| { supply: 4, borrow: 4 }
| Max number of iterations run by the matching engine |
| gasLimitPercent
| ethers.BigNumber
| 11000 (110%)
| Percentage of the gas estimation used as the gas limit for transactions (with 4 decimals) |
| percentApproximation
| ethers.BigNumber
| 9900 (99%)
| Scaling applied to transactions' amount to prevent reverting due to block inclusion delay |
| txSignature
| string
| undefined
| If provided, the signature will be appended to the transaction's data to identify the transaction's origin. It must be in hex format |
Usage
The whole sdk is built around the MorphoAaveV3Adapter
class. This is the core element of the sdk.
Data structure
Within the adapter, data are stored in different objects:
| name | public* | source | interface | description |
| --------------------- | -------- | ----------- | --------------------------------------------------------------------- | ------------------------------------------------------------------------ |
| globalData | ✅ yes | ⚡️ fetched | GlobalData | Global data about the chain and the protocol |
| marketsConfigs | ✅ yes | ⚡️ fetched | MarketMapping<
MarketConfig
>
| Properties of each market that don't (or rarely) change |
| marketsData | ✅ yes | ⚙️ computed | MarketMapping<
MarketData
>
| Data by market (metrics, apys, ...) that need to be updated frequently |
| marketsList | ✅ yes | ⚡️ fetched | string[]
| List of the markets listed on Morpho-AaveV3 |
| userData | ✅ yes | ⚙️ computed | UserData
| User Data that are not specific to a market |
| userMarketsData | ✅ yes | ⚙️ computed | MarketMapping<
UserMarketData
>
| User Data by market |
| scaledMarketsData | ❌ no | ⚡️ fetched | MarketMapping<
ScaledMarketData
>
| Raw data by market, before any processing or computation |
| scaledUserMarketsData | ❌ no | ⚡️ fetched | MarketMapping<
ScaledUserMarketData
>
| Raw user data by market, before any processing or computation |
| rewardsDistribution | ❌ no | ⚡️ fetched | MorphoEpochDistribution
| Morpho rewards distribution of the current epoch |
* see the section about data to see how to access public data
Initialization
To create an adapter, you must provide fetchers. These are special entities that are used to fetch data. For each fetcher, you can use one from this fetchers or use your own one (as long as it matches the interface). You have 5 different fetchers:
| fetcher | fetched data | available |
| -------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| MarketFetcher | marketsConfigs
, marketsList
, scaledMarketsData
| chain, static |
| UserFetcher | scaledUserMarketsData
, userData.ethBalance
| chain, static |
| GlobalDataFetcher | globalData
| chain, static |
| RewardsFetcher | rewardsDistribution
| api, static |
From chain
If you want to fetch all data from the chain, you can use MorphoAaveV3Adapter.fromChain
const adapter = MorphoAaveV3Adapter.fromChain();
- you can provide a specific
provider
fromethers
to use:
const adapter = MorphoAaveV3Adapter.fromChain({ provider: myProvider });
await adapter.refreshAll("latest");
by default, the one from the configuration will be used
- Since some data can't be fetched from chain, you can provide specific fetcher for them:
const adapter = MorphoAaveV3Adapter.fromChain({ extraFetchersConfig });
await adapter.refreshAll("latest");
where extraFetchersConfig
has the following interface:
const extraFetchersConfig: {
rewards?: "api" | RewardsFetcher;
}
By default,
marketSupply
will be fetched from the morpho-labs subgraphrewards
will be fetched from morpho API
From mock
You can also provide static data to the adapter to have a static state in the adapter using MorphoAaveV3Adapter.fromMock
const adapter = MorphoAaveV3Adapter.fromMock(mock);
await adapter.refreshAll("latest");
Where mock
can be an AdapterMock
. If no mock is provided, this one will be used
Note
You can provide loading delays to thefromMock
function for testing purposes to simulate real conditions
Read Data
RxJs
The sdk leverages on RxJS to allow you to build highly reactive apps out of the box.
To do so, every public data (see Data structure) are associated with an rxjs Subject
:
const adapter = MorphoAaveV3Adapter.fromChain();
await adapter.refreshAll("latest");
adapter.marketsConfigs$.subscribe((marketsConfigs) => ...);
adapter.marketsData$.subscribe((marketsData) => ...);
adapter.userMarketsData$.subscribe((userMarketsData) => ...);
adapter.marketsList$.subscribe((marketsList) => ...);
adapter.userData$.subscribe((userData) => ...);
adapter.globalData$.subscribe((globalData) => ...);
Getters
If you don't use RxJs, you can access these data using getter functions:
const adapter = MorphoAaveV3Adapter.fromChain();
await adapter.refreshAll("latest");
const marketsConfigs = adapter.getMarketsConfigs();
const marketsData = adapter.getMarketsData();
const userMarketsData = adapter.getUserMarketsData();
const marketsList = adapter.getMarketsList();
const userData = adapter.getUserData();
const globalData = adapter.getGlobalData();
Execute a transaction
Notifications
To keep track of what's happening during the transactions' executions, the adapter can be provided with a notifier
adapter.addNotifier(notifier); // Adds `notifier` to the list of the adapter's notifiers.
adapter.removeNotifier(notifier); // Removes `notifier` from the list of adapter's notifiers. It needs to be the same object (reference) as the one that has been added
adapter.resetNotifiers(); // Removes all the notifiers and return them in an array.
A notifier can be any instance/object matching the ITransactionNotifier
interface.
The handlers are called according to the following timeline:
Transactions with Morpho-AaveV3 contract
adapter.handleMorphoTransaction(txType, underlyingAddress, amount, options);
with
| Param | Type | Description |
| ------------------- | ------------------------------------------------------ | -------------------------------------------------------------------------------------- |
| txType
| TransactionType
| Type of the operation to perfom |
| underlyingAddress
| string
| Address of the underlying market on which to perform the operation |
| amount
| ethers.BigNumber
| Amount of the transaction. Use ethers.constants.MaxUint256
to use the maximum amount |
| options
| optional, TransactionOptions
| Transaction options |
Approval
Morpho-AaveV3 leverages the Permit2 Approval feature, but you can still perform classic approvals.
Permit2
adapter.handlePermit2Approval(underlyingAddress, deadline, amount, options);
| Param | Type | Description |
| ------------------- | ------------------------------------------------------------------------------------- | ----------------------------------------------------- |
| underlyingAddress
| string
| Address of the underlying token you wanna provide |
| deadline
| ethers.BigNumber
| Deadline after which the approval isn't valid anymore |
| amount
| ethers.BigNumber
| Amount to approve |
| options
| optional, ApprovalHandlerOptions
| Transaction options |
Classic
adapter.handleApproval(underlyingAddress, amount, options);
| Param | Type | Description |
| ------------------- | ------------------------------------------------------------------------------------- | ------------------------------------------------- |
| underlyingAddress
| string
| Address of the underlying token you wanna provide |
| amount
| ethers.BigNumber
| Amount to approve |
| options
| optional, ApprovalHandlerOptions
| Transaction options |
Claim Morpho rewards
adapter.handleClaimMorpho({ overrides });
Wrap ETH
adapter.handleWrapEth(amount, { overrides });
With amount
being of type ethers.BigNumber
Connection
- Connect a user to the adapter:
adapter.connect(user, signer); // Data will be fetched for `user` and `signer` will be used for transactions
// in read-only
adapter.connect(user); // Data will be fetched for `user` but transactions will be ignored
- Disconnect connected user:
adapter.disconnect();
- Get the connection state:
adapter.isConnected();
Refreshing
refreshAll
adapter.refreshAll("latest");
All the data will be refreshed.
Note If the block is undefined, the data will be fetched at the last fetched block. If
refreshAll
is called for the first time, the data will be fetched at the block "latest"
refreshData
adapter.refreshData();
Fetch a new block from the chain and update all indexes locally without fetching markets data If the block is not a new block, the update will be ignored.
refetchData
adapter.refetchData();
Refetch the data from the chain and recompute computed data.
Note Only
globalData
,scaledMarketsData
,scaledUserMarketsData
andrewardsDistribution
will be refetched since the others are not likely to change between two blocks
Max capacity
You can use getUserMaxCapacity
to get the maximum amount for a given operation on a given market.
const { amount, limiter } = adapter.getUserMaxCapacity(underlyingAddress, txType);
The maximum amount
is given in underlying and the limiter
is one of the following (see MaxCapacityLimiter)
"LIMITED_BY_WALLET_BALANCE"; // The user can't supply/repay more than his wallet balance
"LIMITED_BY_OPERATION_PAUSED"; // The required operation is paused
"LIMITED_BY_ZERO_PRICE"; // The amount can't be estimated because the fetched price for the given market is zero
"LIMITED_BY_BORROW_CAPACITY"; // The user can't borrow more than his borrow capacity
"LIMITED_BY_POOL_LIQUIDITY"; // The amount is limited by AaveV3 liquidity
"LIMITED_BY_CAP"; // There is a borrow/supply cap on AaveV3 and it limits the operation
"LIMITED_BY_BALANCE"; // The user can't withdraw/repay more than his current balance on Morpho
Simulation
The adapter provides a simulation tool that allows you to simulate the impact of a transaction on its data.
const simulator = adapter.getSimulator(timeout);
with timeout
being the minimum delay (in ms) between two refresh. Explicitly set to O
to prevent it from refreshing. The default value is 1000
(1s)
Data structure
The simulator has the same data structure as the adapter. See Data Structure for more details.
Note Since the adapter's values are evolving, the simulator will re-run the simulation on the new values when they change.
Simulate
simulator.simulate([
{
type,
amount,
underlyingAddress
},
...
]);
Reset
Run simulator.reset()
reset the operation list.
Note This is equivalent to
simulator.simulate([])
Close
When you don't need the simulator anymore, run simulator.close()
to free the memory.
Warning Not closing simulators can lead to big performance issues