https://github.com/morpho-org/morpho-aavev3-optimizer-sdk
📚📚 Typescript based package to ease the Morpho-AaveV3 Optimizer integration
https://github.com/morpho-org/morpho-aavev3-optimizer-sdk
Last synced: 4 months ago
JSON representation
📚📚 Typescript based package to ease the Morpho-AaveV3 Optimizer integration
- Host: GitHub
- URL: https://github.com/morpho-org/morpho-aavev3-optimizer-sdk
- Owner: morpho-org
- License: mit
- Created: 2023-05-12T08:34:53.000Z (about 3 years ago)
- Default Branch: main
- Last Pushed: 2025-01-08T08:41:02.000Z (over 1 year ago)
- Last Synced: 2025-10-26T14:37:41.474Z (7 months ago)
- Language: TypeScript
- Homepage:
- Size: 695 KB
- Stars: 2
- Watchers: 2
- Forks: 0
- Open Issues: 9
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
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](https://github.com/morpho-org/morpho-aave-v3).
> **Warning**
> This package is used by the morpho association to build the [morpho-aaveV3 dapp](https://aave-v3.morpho.xyz) 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`
```bash
npm install @morpho-org/morpho-aave-v3-sdk
```
```bash
yarn add @morpho-org/morpho-aave-v3-sdk
```
## Configuration
At the root of your dapp or your script:
```ts
/* _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](https://docs.morpho.xyz/concepts-overview/advanced-concepts/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](src/types/common.ts#L7) | _Global data about the chain and the protocol_ |
| marketsConfigs | ✅ yes | ⚡️ fetched | `MarketMapping<`[`MarketConfig`](src/types/markets.ts#L86)`>` | _Properties of each market that don't (or rarely) change_ |
| marketsData | ✅ yes | ⚙️ computed | `MarketMapping<`[`MarketData`](src/types/markets.ts#L296)`>` | _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`](src/types/user.ts#L6) | _User Data that are not specific to a market_ |
| userMarketsData | ✅ yes | ⚙️ computed | `MarketMapping<`[`UserMarketData`](src/types/user.ts#L262)`>` | _User Data by market_ |
| scaledMarketsData | ❌ no | ⚡️ fetched | `MarketMapping<`[`ScaledMarketData`](src/types/markets.ts#L182)`>` | _Raw data by market, before any processing or computation_ |
| scaledUserMarketsData | ❌ no | ⚡️ fetched | `MarketMapping<`[`ScaledUserMarketData`](src/types/user.ts#L184)`>` | _Raw user data by market, before any processing or computation_ |
| rewardsDistribution | ❌ no | ⚡️ fetched | [`MorphoEpochDistribution`](src/helpers/rewards/rewards.types.ts#L66) | _Morpho rewards distribution of the current epoch_ |
_\* see [the section about data](#read-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](#data-structure). 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](src/fetchers/fetchers.interfaces.ts#L20) | `marketsConfigs`, `marketsList`, `scaledMarketsData` | [chain](src/fetchers/Chain/ChainMarketFetcher.ts), [static](src/fetchers/Static/StaticMarketFetcher.ts) |
| [UserFetcher](src/fetchers/fetchers.interfaces.ts#L35) | `scaledUserMarketsData`, `userData.ethBalance` | [chain](src/fetchers/Chain/ChainUserFetcher.ts), [static](src/fetchers/Static/StaticUserFetcher.ts) |
| [GlobalDataFetcher](src/fetchers/fetchers.interfaces.ts#L48) | `globalData` | [chain](src/fetchers/Chain/ChainGlobalDataFetcher.ts), [static](src/fetchers/Static/StaticGlobalDataFetcher.ts) |
| [RewardsFetcher](src/fetchers/fetchers.interfaces.ts#L62) | `rewardsDistribution` | [api](src/fetchers/Api/ApiRewardsFetcher.ts), [static](src/fetchers/Static/StaticRewardsFetcher.ts) |
#### From chain
If you want to fetch all data from the chain, you can use `MorphoAaveV3Adapter.fromChain`
```ts
const adapter = MorphoAaveV3Adapter.fromChain();
```
- you can provide a specific `provider` from `ethers` to use:
```ts
const adapter = MorphoAaveV3Adapter.fromChain({ provider: myProvider });
await adapter.refreshAll("latest");
```
by default, the one from the [configuration](#configuration) will be used
- Since some data can't be fetched from chain, you can provide specific fetcher for them:
```ts
const adapter = MorphoAaveV3Adapter.fromChain({ extraFetchersConfig });
await adapter.refreshAll("latest");
```
where `extraFetchersConfig` has the following interface:
```ts
const extraFetchersConfig: {
rewards?: "api" | RewardsFetcher;
}
```
By default,
- `marketSupply` will be fetched from the morpho-labs subgraph
- `rewards` 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`
```ts
const adapter = MorphoAaveV3Adapter.fromMock(mock);
await adapter.refreshAll("latest");
```
Where `mock` can be an [`AdapterMock`](src/mocks/index.ts#L18). If no mock is provided, [this one](src/mocks/mock1.ts#L119) will be used
> **Note**
> You can provide loading delays to the `fromMock` function for testing purposes to simulate real conditions
### Read Data
#### RxJs
The sdk leverages on [RxJS](https://rxjs.dev) to allow you to build highly reactive apps out of the box.
To do so, every public data (see [Data structure](#data-structure)) are associated with an rxjs `Subject`:
```ts
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:
```ts
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`
```ts
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`](src/txHandler/notifiers/TransactionNotifier.interface.ts) interface.
The handlers are called according to the following timeline:

#### Transactions with Morpho-AaveV3 contract
```ts
adapter.handleMorphoTransaction(txType, underlyingAddress, amount, options);
```
with
| Param | Type | Description |
| ------------------- | ------------------------------------------------------ | -------------------------------------------------------------------------------------- |
| `txType` | [`TransactionType`](src/types/tx.ts#L34) | 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`](src/types/tx.ts#L8) | Transaction options |
#### Approval
Morpho-AaveV3 leverages the [Permit2 Approval]() feature, but you can still perform classic approvals.
##### Permit2
```ts
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`](src/txHandler/ApprovalHandler.interface.ts#L5) | Transaction options |
##### Classic
```ts
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`](src/txHandler/ApprovalHandler.interface.ts#L5) | Transaction options |
#### Claim Morpho rewards
```ts
adapter.handleClaimMorpho({ overrides });
```
#### Wrap ETH
```ts
adapter.handleWrapEth(amount, { overrides });
```
With `amount` being of type `ethers.BigNumber`
#### Connection
- Connect a user to the adapter:
```ts
adapter.connect(user, signer); // Data will be fetched for `user` and `signer` will be used for transactions
```
```ts
// in read-only
adapter.connect(user); // Data will be fetched for `user` but transactions will be ignored
```
- Disconnect connected user:
```ts
adapter.disconnect();
```
- Get the connection state:
```ts
adapter.isConnected();
```
#### Refreshing
##### `refreshAll`
```ts
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`
```ts
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`
```ts
adapter.refetchData();
```
Refetch the data from the chain and recompute computed data.
> **Note**
> Only `globalData`, `scaledMarketsData`, `scaledUserMarketsData` and `rewardsDistribution` 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.
```ts
const { amount, limiter } = adapter.getUserMaxCapacity(underlyingAddress, txType);
```
The maximum `amount` is given in underlying and the `limiter` is one of the following (see [MaxCapacityLimiter](src/types/common.ts#L74))
```ts
"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.
```ts
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](#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
```ts
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