GithubHelp home page GithubHelp logo

aave-utilities's Introduction

Aave logo

Aave Utilities

The Aave Protocol is a decentralized non-custodial liquidity protocol where users can participate as suppliers or borrowers.

Aave Utilities is a JavaScript SDK extending ethers.js for interacting with V2 and V3 of the Aave Protocol, an upgrade to the existing aave-js library.


Installation

Aave utilities are available as npm packages:

The @aave/math-utils package contains methods for formatting raw contract data for usage on a frontend

The @aave/contract-helpers package contains methods for generating transactions based on method and parameter inputs. Can be used to read and write data on the protocol contracts.

// with npm
npm install @aave/contract-helpers @aave/math-utils

// with yarn
yarn add @aave/contract-helpers @aave/math-utils

Compatibility

This library has a peer dependency of ethers v5, and will not work with v6.

To install the correct version, run:

npm install ethers@5

Features

  1. Data Methods
  2. Transaction Methods

Data Methods

The @aave/contract-helpers and @aave/math-utils packages are utilities to fetch and format smart contract data respectively. This section will guide you to setup and use these packages to query Aave Protocol data.


Data Methods Setup

After installing the aave-utilities packages, it's also recommended to add the Aave Address Book package which will be used in the examples to import contract addresses directly.

To initialize an instance of an @aave/contract-helpers service, an ethers provider is required to pass into the constructor. ethers.js is a library for interacting with Ethereum and other EVM compatible blockchains, our ethers provider instance will serve as an RPC connection to read data from the blockchain.

The two services which will be used for all data fetching methods are:

  • UiPoolDataProvider: Used for querying reserve and user data
  • UiIncentiveDataProvider: Used for querying reward emissions and user claimable rewards

The sample code below shows a complete example of initializing and using these services to query Aave protocol data.

Sample Code
import { ethers } from 'ethers';
import {
  UiPoolDataProvider,
  UiIncentiveDataProvider,
  ChainId,
} from '@aave/contract-helpers';
import * as markets from '@bgd-labs/aave-address-book';

// ES5 Alternative imports
//  const {
//    ChainId,
//    UiIncentiveDataProvider,
//    UiPoolDataProvider,
//  } = require('@aave/contract-helpers');
//  const markets = require('@bgd-labs/aave-address-book');
//  const ethers = require('ethers');

// Sample RPC address for querying ETH mainnet
const provider = new ethers.providers.JsonRpcProvider(
  'https://eth-mainnet.public.blastapi.io',
);

// User address to fetch data for, insert address here
const currentAccount = '0x464C71f6c2F760DdA6093dCB91C24c39e5d6e18c';

// View contract used to fetch all reserves data (including market base currency data), and user reserves
// Using Aave V3 Eth Mainnet address for demo
const poolDataProviderContract = new UiPoolDataProvider({
  uiPoolDataProviderAddress: markets.AaveV3Ethereum.UI_POOL_DATA_PROVIDER,
  provider,
  chainId: ChainId.mainnet,
});

// View contract used to fetch all reserve incentives (APRs), and user incentives
// Using Aave V3 Eth Mainnet address for demo
const incentiveDataProviderContract = new UiIncentiveDataProvider({
  uiIncentiveDataProviderAddress:
    markets.AaveV3Ethereum.UI_INCENTIVE_DATA_PROVIDER,
  provider,
  chainId: ChainId.mainnet,
});

async function fetchContractData() {
  // Object containing array of pool reserves and market base currency data
  // { reservesArray, baseCurrencyData }
  const reserves = await poolDataProviderContract.getReservesHumanized({
    lendingPoolAddressProvider: markets.AaveV3Ethereum.POOL_ADDRESSES_PROVIDER,
  });

  // Object containing array or users aave positions and active eMode category
  // { userReserves, userEmodeCategoryId }
  const userReserves = await poolDataProviderContract.getUserReservesHumanized({
    lendingPoolAddressProvider: markets.AaveV3Ethereum.POOL_ADDRESSES_PROVIDER,
    user: currentAccount,
  });

  // Array of incentive tokens with price feed and emission APR
  const reserveIncentives =
    await incentiveDataProviderContract.getReservesIncentivesDataHumanized({
      lendingPoolAddressProvider:
        markets.AaveV3Ethereum.POOL_ADDRESSES_PROVIDER,
    });

  // Dictionary of claimable user incentives
  const userIncentives =
    await incentiveDataProviderContract.getUserReservesIncentivesDataHumanized({
      lendingPoolAddressProvider:
        markets.AaveV3Ethereum.POOL_ADDRESSES_PROVIDER,
      user: currentAccount,
    });

  console.log({ reserves, userReserves, reserveIncentives, userIncentives });
}

fetchContractData();

Markets Data

Once you have successfully completed the Setup instructions and are querying on-chain data, the next step is to format this data into human readable format and compute helpful values. This is done using the formatter functions in the @aave/math-utils package. There are two formatters for market data, one with incentives data, and one without. Examples for both methods are shown below.

The output of these methods is an array of formatted reserve data for each reserve in an Aave market.


formatReserves

formatReserves returns an array of formatted configuration and live usage data for each reserve in an Aave market

Sample Code
import { formatReserves } from '@aave/math-utils';
import dayjs from 'dayjs';

// `reserves` variable here is input from Setup section

const reservesArray = reserves.reservesData;
const baseCurrencyData = reserves.baseCurrencyData;

const currentTimestamp = dayjs().unix();

/*
- @param `reserves` Input from [Fetching Protocol Data](#fetching-protocol-data), `reserves.reservesArray`
- @param `currentTimestamp` Current UNIX timestamp in seconds
- @param `marketReferencePriceInUsd` Input from [Fetching Protocol Data](#fetching-protocol-data), `reserves.baseCurrencyData.marketReferencePriceInUsd`
- @param `marketReferenceCurrencyDecimals` Input from [Fetching Protocol Data](#fetching-protocol-data), `reserves.baseCurrencyData.marketReferenceCurrencyDecimals`
*/
const formattedPoolReserves = formatReserves({
  reserves: reservesArray,
  currentTimestamp,
  marketReferenceCurrencyDecimals:
    baseCurrencyData.marketReferenceCurrencyDecimals,
  marketReferencePriceInUsd: baseCurrencyData.marketReferenceCurrencyPriceInUsd,
});

formatReservesAndIncentives

formatReservesAndIncentives returns an array of formatted configuration and live usage data plus an object with supply, variable borrow, and stable borrow incentives for each reserve in an Aave market

Sample Code
import { formatReservesAndIncentives } from '@aave/math-utils';
import dayjs from 'dayjs';

// 'reserves' and 'reserveIncentives' inputs from Fetching Protocol Data section

const reservesArray = reserves.reservesData;
const baseCurrencyData = reserves.baseCurrencyData;

const currentTimestamp = dayjs().unix();

/*
- @param `reserves` Input from [Fetching Protocol Data](#fetching-protocol-data), `reserves.reservesArray`
- @param `currentTimestamp` Current UNIX timestamp in seconds, Math.floor(Date.now() / 1000)
- @param `marketReferencePriceInUsd` Input from [Fetching Protocol Data](#fetching-protocol-data), `reserves.baseCurrencyData.marketReferencePriceInUsd`
- @param `marketReferenceCurrencyDecimals` Input from [Fetching Protocol Data](#fetching-protocol-data), `reserves.baseCurrencyData.marketReferenceCurrencyDecimals`
- @param `reserveIncentives` Input from [Fetching Protocol Data](#fetching-protocol-data), `reserveIncentives`
*/
const formattedPoolReserves = formatReservesAndIncentives({
  reserves: reservesArray,
  currentTimestamp,
  marketReferenceCurrencyDecimals:
    baseCurrencyData.marketReferenceCurrencyDecimals,
  marketReferencePriceInUsd: baseCurrencyData.marketReferenceCurrencyPriceInUsd,
  reserveIncentives,
});

User Data

Once you have successfully completed the Setup instructions and are querying on-chain data, the next step is to format this data into human readable format and compute cumulative user metrics. This is done using the formatter functions in the @aave/math-utils package. There are two formatters for user data, one with incentives data, and one without. Examples for both methods are shown below.

The output of these methods is an object containing cumulative metrics (healthFactor, totalLiquidity, totalBorrows, etc.) and an array of formatted reserve data plus user holdings (aTokens, debtTokens) for each reserve in an Aave market.

formatUserSummary

Returns formatted summary of Aave user portfolio including: array of holdings, total liquidity, total collateral, total borrows, liquidation threshold, health factor, and available borrowing power

Sample Code
import { formatUserSummary } from '@aave/math-utils';
import dayjs from 'dayjs';

// 'reserves' and 'userReserves' inputs from Setup section

const reservesArray = reserves.reservesData;
const baseCurrencyData = reserves.baseCurrencyData;
const userReservesArray = userReserves.userReserves;

const currentTimestamp = dayjs().unix();

const formattedPoolReserves = formatReserves({
  reserves: reservesArray,
  currentTimestamp,
  marketReferenceCurrencyDecimals:
    baseCurrencyData.marketReferenceCurrencyDecimals,
  marketReferencePriceInUsd: baseCurrencyData.marketReferenceCurrencyPriceInUsd,
});

/*
- @param `currentTimestamp` Current UNIX timestamp in seconds, Math.floor(Date.now() / 1000)
- @param `marketReferencePriceInUsd` Input from [Fetching Protocol Data](#fetching-protocol-data), `reserves.baseCurrencyData.marketReferencePriceInUsd`
- @param `marketReferenceCurrencyDecimals` Input from [Fetching Protocol Data](#fetching-protocol-data), `reserves.baseCurrencyData.marketReferenceCurrencyDecimals`
- @param `userReserves` Input from [Fetching Protocol Data](#fetching-protocol-data), combination of `userReserves.userReserves` and `reserves.reservesArray`
- @param `userEmodeCategoryId` Input from [Fetching Protocol Data](#fetching-protocol-data), `userReserves.userEmodeCategoryId`
*/
const userSummary = formatUserSummary({
  currentTimestamp,
  marketReferencePriceInUsd: baseCurrencyData.marketReferenceCurrencyPriceInUsd,
  marketReferenceCurrencyDecimals:
    baseCurrencyData.marketReferenceCurrencyDecimals,
  userReserves: userReservesArray,
  formattedReserves,
  userEmodeCategoryId: userReserves.userEmodeCategoryId,
});

formatUserSummaryAndIncentives

Returns formatted summary of Aave user portfolio including: array of holdings, total liquidity, total collateral, total borrows, liquidation threshold, health factor, available borrowing power, and dictionary of claimable incentives

Sample Code
import { formatUserSummaryAndIncentives } from '@aave/math-utils';
import dayjs from 'dayjs';

// 'reserves', 'userReserves', 'reserveIncentives', and 'userIncentives' inputs from Setup section

const reservesArray = reserves.reservesData;
const baseCurrencyData = reserves.baseCurrencyData;
const userReservesArray = userReserves.userReserves;

const currentTimestamp = dayjs().unix();

const formattedPoolReserves = formatReserves({
  reserves: reservesArray,
  currentTimestamp,
  marketReferenceCurrencyDecimals:
    baseCurrencyData.marketReferenceCurrencyDecimals,
  marketReferencePriceInUsd: baseCurrencyData.marketReferenceCurrencyPriceInUsd,
});

/*
- @param `currentTimestamp` Current UNIX timestamp in seconds, Math.floor(Date.now() / 1000)
- @param `marketReferencePriceInUsd` Input from [Fetching Protocol Data](#fetching-protocol-data), `reserves.baseCurrencyData.marketReferencePriceInUsd`
- @param `marketReferenceCurrencyDecimals` Input from [Fetching Protocol Data](#fetching-protocol-data), `reserves.baseCurrencyData.marketReferenceCurrencyDecimals`
- @param `userReserves` Input from [Fetching Protocol Data](#fetching-protocol-data), combination of `userReserves.userReserves` and `reserves.reservesArray`
- @param `userEmodeCategoryId` Input from [Fetching Protocol Data](#fetching-protocol-data), `userReserves.userEmodeCategoryId`
- @param `reserveIncentives` Input from [Fetching Protocol Data](#fetching-protocol-data), `reserveIncentives`
- @param `userIncentives` Input from [Fetching Protocol Data](#fetching-protocol-data), `userIncentives`
*/
const userSummary = formatUserSummaryAndIncentives({
  currentTimestamp,
  marketReferencePriceInUsd: baseCurrencyData.marketReferenceCurrencyPriceInUsd,
  marketReferenceCurrencyDecimals:
    baseCurrencyData.marketReferenceCurrencyDecimals,
  userReserves: userReservesArray,
  formattedReserves,
  userEmodeCategoryId: userReserves.userEmodeCategoryId,
  reserveIncentives,
  userIncentives,
});

Transaction Methods

The transaction methods package provides an sdk to interact with Aave Protocol contracts. See ethers.js for instructions on installing setting up an ethers provider

Once initialized this sdk can be used to generate the transaction data needed to perform protocol interactions. If an approval is required, the method will return an array with two transactions, or single transaction if no approval is needed.

Transactions Setup

All transaction methods will return an array of transaction objects of this type:

import { EthereumTransactionTypeExtended } from '@aave/contract-helpers';

To send a transaction from this object:

import { BigNumber, providers } from 'ethers';

function submitTransaction({
  provider: providers.Web3Provider,  // Signing transactions requires a wallet provider, Aave UI currently uses web3-react (https://github.com/NoahZinsmeister/web3-react) for connecting wallets and accessing the wallet provider
  tx: EthereumTransactionTypeExtended
}){
  const extendedTxData = await tx.tx();
  const { from, ...txData } = extendedTxData;
  const signer = provider.getSigner(from);
  const txResponse = await signer.sendTransaction({
    ...txData,
    value: txData.value ? BigNumber.from(txData.value) : undefined,
  });
}

Submitting Transactions

All transaction methods will return an array of transaction objects of the following type:

import { EthereumTransactionTypeExtended } from '@aave/contract-helpers';

To send a transaction from this object:

import { BigNumber, providers } from 'ethers';

function submitTransaction({
  provider: ethers.providers.provider,  // Signing transactions requires a wallet provider
  tx: EthereumTransactionTypeExtended
}){
  const extendedTxData = await tx.tx();
  const { from, ...txData } = extendedTxData;
  const signer = provider.getSigner(from);
  const txResponse = await signer.sendTransaction({
    ...txData,
    value: txData.value ? BigNumber.from(txData.value) : undefined,
  });
}

Pool V3

Transaction methods to perform actions on the V3 Pool contract

supplyBundle

A bundle method for supply, formerly deposit, which supplies the underlying asset into the Pool reserve. For every token that is supplied, a corresponding amount of aTokens is minted.

The bundle method provides the transaction for the action, approval if required (txn or signature request), and callback to generate signed supplyWithPermit txn.

Sample Code
import { PoolBundle } from '@aave/contract-helpers';

const poolBundle = new PoolBundle(provider, {
  POOL: poolAddress,
  WETH_GATEWAY: wethGatewayAddress,
});

/*
- @param `user` The ethereum address that will make the deposit
- @param `reserve` The ethereum address of the reserve
- @param `amount` The amount to be deposited
- @param @optional `onBehalfOf` The ethereum address for which user is depositing. It will default to the user address
*/
const supplyBundle: ActionBundle = await poolBundle.supplyBundle({
  user,
  reserve,
  amount,
  onBehalfOf,
});

// Submit bundle components as shown in #bundle-methods section

</details>

<br />

### supply

Formerly `deposit`, supply the underlying asset into the Pool reserve. For every
token that is supplied, a corresponding amount of aTokens is minted

<details>
  <summary>Sample Code</summary>

```ts
import { Pool } from '@aave/contract-helpers';

const pool = new Pool(provider, {
  POOL: poolAddress,
  WETH_GATEWAY: wethGatewayAddress,
});

/*
- @param `user` The ethereum address that will make the deposit
- @param `reserve` The ethereum address of the reserve
- @param `amount` The amount to be deposited
- @param @optional `onBehalfOf` The ethereum address for which user is depositing. It will default to the user address
*/
const txs: EthereumTransactionTypeExtended[] = await pool.supply({
  user,
  reserve,
  amount,
  onBehalfOf,
});

// If the user has not approved the pool contract to spend their tokens, txs will also contain two transactions: approve and supply. These approval and supply transactions can be submitted just as in V2,OR you can skip the first approval transaction with a gasless signature by using signERC20Approval -> supplyWithPermit which are documented below

// If there is no approval transaction, then supply() can called without the need for an approval or signature

Submit transaction(s) as shown here


signERC20Approval

This method is used to generate the raw signature data to be signed by the user. Once generated, a function is called to trigger a signature request from the users wallet. This signature can be passed a parameter to supplyWithPermit or repayWithPermit in place of an approval transaction.

Note: Not all tokens are compatible with the ERC-2612 permit functionality. You can check the Aave interface config for an updated list of supported tokens by network.

Sample Code
import { Pool } from '@aave/contract-helpers';

const pool = new Pool(provider, {
  POOL: poolAddress,
  WETH_GATEWAY: wethGatewayAddress,
});

/*
- @param `user` The ethereum address that will make the deposit 
- @param `reserve` The ethereum address of the reserve 
- @param `amount` The amount to be deposited 
- @param `deadline` Expiration of signature in seconds, for example, 1 hour = Math.floor(Date.now() / 1000 + 3600).toString()
*/
const dataToSign: string = await pool.signERC20Approval({
  user,
  reserve,
  amount,
  deadline,
});

const signature = await provider.send('eth_signTypedData_v4', [
  currentAccount,
  dataToSign,
]);

// This signature can now be passed into the supplyWithPermit() function below

supplyWithPermit

Same underlying method as supply but uses a signature based approval passed as a parameter.

Sample Code
import { Pool } from '@aave/contract-helpers';

const pool = new Pool(provider, {
  POOL: poolAddress,
  WETH_GATEWAY: wethGatewayAddress,
});

/*
- @param `user` The ethereum address that will make the deposit 
- @param `reserve` The ethereum address of the reserve 
- @param `amount` The amount to be deposited 
- @param `signature` Signature approving Pool to spend user funds, received from signing output data of signERC20Approval()
- @param @optional `onBehalfOf` The ethereum address for which user is depositing. It will default to the user address
*/
const txs: EthereumTransactionTypeExtended[] = await pool.supplyWithPermit({
  user,
  reserve,
  amount,
  signature,
  onBehalfOf,
});

Submit transaction as shown here


borrow (V3)

Borrow an amount of reserve asset.

User must have a collateralized position (i.e. aTokens in their wallet)

Sample Code
import { Pool, InterestRate } from '@aave/contract-helpers';

const pool = new Pool(provider, {
  POOL: poolAddress,
  WETH_GATEWAY: wethGatewayAddress,
});

/*
- @param `user` The ethereum address that repays 
- @param `reserve` The ethereum address of the reserve on which the user borrowed
- @param `amount` The amount to repay, or (-1) if the user wants to repay everything
- @param `interestRateMode` // Whether the borrow will incur a stable (InterestRate.Stable) or variable (InterestRate.Variable) interest rate
- @param @optional `onBehalfOf` The ethereum address for which user is repaying. It will default to the user address
*/
const txs: EthereumTransactionTypeExtended[] = pool.Borrow({
  user,
  reserve,
  amount,
  interestRateMode,
  onBehalfOf,
});

Submit transaction as shown here


repay (V3)

Repays a borrow on the specific reserve, for the specified amount (or for the whole amount, if (-1) is specified). the target user is defined by onBehalfOf. If there is no repayment on behalf of another account, onBehalfOf must be equal to user

If the Pool is not approved to spend user funds, an approval transaction will also be returned

Sample Code
import { Pool } from '@aave/contract-helpers';

const pool = new Pool(provider, {
  POOL: poolAddress,
  WETH_GATEWAY: wethGatewayAddress,
});

/*
- @param `user` The ethereum address that will make the deposit 
- @param `reserve` The ethereum address of the reserve 
- @param `amount` The amount to be deposited 
- @param `interestRateMode` // Whether stable (InterestRate.Stable) or variable (InterestRate.Variable) debt will be repaid
- @param @optional `onBehalfOf` The ethereum address for which user is depositing. It will default to the user address
*/
const txs: EthereumTransactionTypeExtended[] = await pool.repay({
  user,
  reserve,
  amount,
  interestRateMode,
  onBehalfOf,
});

// If the user has not approved the pool contract to spend their tokens, txs will also contain two transactions: approve and repay. This approval transaction can be submitted just as in V2, OR you approve with a gasless signature by using signERC20Approval -> supplyWithPermit which are documented below

// If there is no approval transaction, then repay() can called without the need for an approval or signature

Submit transaction(s) as shown here


repayWithPermit

Same underlying method as repay but uses a signature based approval passed as a parameter.

Sample Code
import { Pool } from '@aave/contract-helpers';

const pool = new Pool(provider, {
  POOL: poolAddress,
  WETH_GATEWAY: wethGatewayAddress,
});

/*
- @param `user` The ethereum address that will make the deposit 
- @param `reserve` The ethereum address of the reserve 
- @param `amount` The amount to be deposited 
- @param `signature` Signature approving Pool to spend user funds, from signERC20Approval()
- @param @optional `onBehalfOf` The ethereum address for which user is depositing. It will default to the user address
*/
const txs: EthereumTransactionTypeExtended[] = await pool.supplyWithPermit({
  user,
  reserve,
  amount,
  signature,
  onBehalfOf,
});

Submit transaction as shown here


repayWithATokens

Repays a borrow on the specific reserve, for the specified amount, deducting funds from a users aToken balance instead of the underlying balance. To repay the max debt amount or max aToken balance without dust (whichever is lowest), set the amount to -1

There is no need for an approval or signature when repaying with aTokens

Sample Code
import { Pool, InterestRate } from '@aave/contract-helpers';

const pool = new Pool(provider, {
  POOL: poolAddress,
  WETH_GATEWAY: wethGatewayAddress,
});

/*
- @param `user` The ethereum address that will make the deposit 
- @param `amount` The amount to be deposited, -1 to repay max aToken balance or max debt balance without dust (whichever is lowest)
- @param `reserve` The ethereum address of the reserve 
- @param `rateMode` The debt type to repay, stable (InterestRate.Stable) or variable (InterestRate.Variable)
*/
const txs: EthereumTransactionTypeExtended[] = await pool.repayWithATokens({
  user,
  amount,
  reserve,
  reateMode,
});

Submit transaction as shown here


withdraw (V3)

Withdraws the underlying asset of an aToken asset.

Sample Code
import { Pool } from '@aave/contract-helpers';

const pool = new Pool(provider, {
  POOL: poolAddress,
  WETH_GATEWAY: wethGatewayAddress,
});

/*
- @param `user` The ethereum address that will make the deposit 
- @param `reserve` The ethereum address of the reserve 
- @param `amount` The amount to be deposited 
- @param `aTokenAddress` The aToken to redeem for underlying asset
- @param @optional `onBehalfOf` The ethereum address for which user is depositing. It will default to the user address
*/
const txs: EthereumTransactionTypeExtended[] = await pool.withdraw({
  user,
  reserve,
  amount,
  aTokenAddress,
  onBehalfOf,
});

Submit transaction as shown here


swapBorrowRateMode (V3)

Borrowers can use this function to swap between stable and variable borrow rate modes

Sample Code
import { Pool, InterestRate } from '@aave/contract-helpers';

const pool = new Pool(provider, {
  POOL: poolAddress,
  WETH_GATEWAY: wethGatewayAddress,
});

/*
- @param `user` The ethereum address that will make the deposit 
- @param `reserve` The ethereum address of the reserve 
- @param `interestRateMode` The rate mode to swap to, stable (InterestRate.Stable) or variable (InterestRate.Variable) 
*/
const txs: EthereumTransactionTypeExtended[] = await pool.swapBorrowRateMode({
  user,
  reserve,
  amount,
  onBehalfOf,
});

Submit transaction as shown here


setUsageAsCollateral (V3)

Allows depositors to enable or disable a specific deposit as collateral

Sample Code
import { Pool } from '@aave/contract-helpers';

const pool = new Pool(provider, {
  POOL: poolAddress,
  WETH_GATEWAY: wethGatewayAddress,
});

/*
- @param `user` The ethereum address that will make the deposit 
- @param `reserve` The ethereum address of the reserve 
- @param `usageAsCollateral` Boolean, true if the user wants to use the deposit as collateral, false otherwise
*/
const txs: EthereumTransactionTypeExtended[] = await pool.setUsageAsCollateral({
  user,
  reserve,
  usageAsCollateral,
});

Submit transaction as shown here


liquidationCall (V3)

Users can invoke this function to liquidate an undercollateralized position

Sample Code
import { Pool } from '@aave/contract-helpers';

const pool = new Pool(provider, {
  POOL: poolAddress,
  WETH_GATEWAY: wethGatewayAddress,
});

/*
- @param `liquidator` The ethereum address that will liquidate the position 
- @param `liquidatedUser` The address of the borrower 
- @param `debtReserve` The ethereum address of the principal reserve 
- @param `collateralReserve` The address of the collateral to liquidated 
- @param `purchaseAmount` The amount of principal that the liquidator wants to repay 
- @param @optional `getAToken` Boolean to indicate if the user wants to receive the aToken instead of the asset. Defaults to false
*/
const txs: EthereumTransactionTypeExtended[] = lendingPool.liquidationCall({
  liquidator,
  liquidatedUser,
  debtReserve,
  collateralReserve,
  purchaseAmount,
  getAToken,
});

Submit transaction(s) as shown here


swapCollateral (V3)

Utilizes flashloan to swap to a different collateral asset

Sample Code
import { Pool } from '@aave/contract-helpers';

const pool = new Pool(provider, {
  POOL: poolAddress,
  SWAP_COLLATERAL_ADAPTER: swapCollateralAdapterAddress,
  WETH_GATEWAY: wethGatewayAddress,
});

/*
- @param `user` The ethereum address that will liquidate the position 
- @param @optional `flash` If the transaction will be executed through a flashloan(true) or will be done directly through the adapters(false). Defaults to false 
- @param `fromAsset` The ethereum address of the asset you want to swap 
- @param `fromAToken` The ethereum address of the aToken of the asset you want to swap 
- @param `toAsset` The ethereum address of the asset you want to swap to (get) 
- @param `fromAmount` The amount you want to swap 
- @param `toAmount` The amount you want to get after the swap 
- @param `maxSlippage` The max slippage that the user accepts in the swap 
- @param @optional `permitSignature` A permit signature of the tx. Only needed when previously signed (Not needed at the moment). 
- @param `swapAll` Bool indicating if the user wants to swap all the current collateral 
- @param @optional `onBehalfOf` The ethereum address for which user is swapping. It will default to the user address 
- @param @optional `referralCode` Integrators are assigned a referral code and can potentially receive rewards. It defaults to 0 (no referrer) 
- @param @optional `useEthPath` Boolean to indicate if the swap will use an ETH path. Defaults to false
*/
const txs: EthereumTransactionTypeExtended[] = await lendingPool.swapCollateral(
  {
    user,
    flash,
    fromAsset,
    fromAToken,
    toAsset,
    fromAmount,
    toAmount,
    maxSlippage,
    permitSignature,
    swapAll,
    onBehalfOf,
    referralCode,
    useEthPath,
  },
);

Submit transaction(s) as shown here


repayWithCollateral (V3)

Allows a borrower to repay the open debt with their collateral

Sample Code
import { Pool } from '@aave/contract-helpers';

const pool = new Pool(provider, {
  POOL: poolAddress,
  REPAY_WITH_COLLATERAL_ADAPTER: repayWithCollateralAddress,
  WETH_GATEWAY: wethGatewayAddress,
});

/*
- @param `user` The ethereum address that will liquidate the position 
- @param `fromAsset` The ethereum address of the asset you want to repay with (collateral) 
- @param `fromAToken` The ethereum address of the aToken of the asset you want to repay with (collateral) 
- @param `assetToRepay` The ethereum address of the asset you want to repay 
- @param `repayWithAmount` The amount of collateral you want to repay the debt with
- @param `repayAmount` The amount of debt you want to repay 
- @param `permitSignature` A permit signature of the tx. Optional
- @param @optional `repayAllDebt` Bool indicating if the user wants to repay all current debt. Defaults to false 
- @param `rateMode` //Enum indicating the type of the interest rate of the collateral
- @param @optional `onBehalfOf` The ethereum address for which user is swapping. It will default to the user address 
- @param @optional `referralCode` Integrators are assigned a referral code and can potentially receive rewards. It defaults to 0 (no referrer) 
- @param @optional `flash` If the transaction will be executed through a flashloan(true) or will be done directly through the adapters(false). Defaults to false 
- @param @optional `useEthPath` Boolean to indicate if the swap will use an ETH path. Defaults to false
*/
const txs: EthereumTransactionTypeExtended[] =
  await lendingPool.repayWithCollateral({
    user,
    fromAsset,
    fromAToken,
    assetToRepay,
    repayWithAmount,
    repayAmount,
    permitSignature,
    repayAllDebt,
    rateMode,
    onBehalfOf,
    referralCode,
    flash,
    useEthPath,
  });

Submit transaction(s) as shown here


setUserEMode

Function to enable eMode on a user account IF conditions are met:

To enable, pass categoryId of desired eMode (1 = stablecoins), can only be enabled if a users currently borrowed assets are ALL within this eMode category To disable, pass categoryId of 0, can only be disabled if new LTV will not leave user undercollateralized

Sample Code
import { Pool } from '@aave/contract-helpers';

const pool = new Pool(provider, {
  POOL: poolAddress,
  WETH_GATEWAY: wethGatewayAddress,
});

/*
- @param `user` The ethereum address that will make the deposit 
- @param `categoryId` number representing the eMode to switch to, 0 = disable, 1 = stablecoins
*/
const txs: EthereumTransactionTypeExtended[] = await pool.setUserEMode({
  user,
  categoryId,
});

Submit transaction as shown here


Lending Pool V2

Object that contains all the necessary methods to create Aave V2 lending pool transactions

depositBundle

A bundle method for deposit, which supplies the underlying asset into the Pool reserve. For every token that is supplied, a corresponding amount of aTokens is minted.

The bundle method generates the deposit tx data and approval tx data (if required).

Sample Code
import { LendingPoolBundle } from '@aave/contract-helpers';

const lendingPoolBundle = new LendingPoolBundle(provider, {
  LENDING_POOL: lendingPoolAddress,
  WETH_GATEWAY: wethGatewayAddress,
});

/*
- @param `user` The ethereum address that will make the deposit
- @param `reserve` The ethereum address of the reserve
- @param `amount` The amount to be deposited
- @param @optional `onBehalfOf` The ethereum address for which user is depositing. It will default to the user address
*/
const depositBundle: ActionBundle = await lendingPoolBundle.depositBundle({
  user,
  reserve,
  amount,
  onBehalfOf,
});

// Submit bundle components as shown in #bundle-methods section

</details>

<br />

### deposit

Deposits the underlying asset into the reserve. For every token that is
deposited, a corresponding amount of aTokens is minted

<details>
  <summary>Sample Code</summary>

```ts
import { LendingPool } from '@aave/contract-helpers';

const lendingPool = new LendingPool(provider, {
  LENDING_POOL: lendingPoolAddress,
  WETH_GATEWAY: wethGatewayAddress,
});

/*
- @param `user` The ethereum address that will make the deposit
- @param `reserve` The ethereum address of the reserve
- @param `amount` The amount to be deposited
- @param @optional `onBehalfOf` The ethereum address for which user is depositing. It will default to the user address
*/
const txs: EthereumTransactionTypeExtended[] = await lendingPool.deposit({
  user,
  reserve,
  amount,
  onBehalfOf,
});

Submit transaction(s) as shown here


borrow

Borrow an amount of reserve asset.

User must have a collateralized position (i.e. aTokens in their wallet)

Sample Code
import { LendingPool, InterestRate } from '@aave/contract-helpers';

const lendingPool = new LendingPool(provider, {
  LENDING_POOL: lendingPoolAddress,
  WETH_GATEWAY: wethGatewayAddress,
});

/*
- @param `user` The ethereum address that will receive the borrowed amount 
- @param `reserve` The ethereum address of the reserve asset 
- @param `amount` The amount to be borrowed, in human readable units (e.g. 2.5 ETH)
- @param `interestRateMode`//Whether the borrow will incur a stable (InterestRate.Stable) or variable (InterestRate.Variable) interest rate
- @param @optional `debtTokenAddress` The ethereum address of the debt token of the asset you want to borrow. Only needed if the reserve is ETH mock address 
- @param @optional `onBehalfOf` The ethereum address for which user is borrowing. It will default to the user address 
*/
const txs: EthereumTransactionTypeExtended[] = await lendingPool.borrow({
  user,
  reserve,
  amount,
  interestRateMode,
  debtTokenAddress,
  onBehalfOf,
  referralCode,
});

Submit transaction as shown here


repay

Repays a borrow on the specific reserve, for the specified amount (or for the whole amount, if (-1) is specified). the target user is defined by onBehalfOf. If there is no repayment on behalf of another account, onBehalfOf must be equal to user

If the user is not approved, an approval transaction will also be returned

Sample Code
import { LendingPool, InterestRate } from '@aave/contract-helpers';

const lendingPool = new LendingPool(provider, {
  LENDING_POOL: lendingPoolAddress,
  WETH_GATEWAY: wethGatewayAddress,
});

/*
- @param `user` The ethereum address that repays 
- @param `reserve` The ethereum address of the reserve on which the user borrowed
- @param `amount` The amount to repay, or (-1) if the user wants to repay everything
- @param `interestRateMode` // Whether stable (InterestRate.Stable) or variable (InterestRate.Variable) debt will be repaid
- @param @optional `onBehalfOf` The ethereum address for which user is repaying. It will default to the user address
*/
const txs: EthereumTransactionTypeExtended[] = lendingPool.repay({
  user,
  reserve,
  amount,
  interestRateMode,
  onBehalfOf,
});

Submit transaction(s) as shown here


withdraw

Withdraws the underlying asset of an aToken asset.

Sample Code
import { LendingPool } from '@aave/contract-helpers';

const lendingPool = new LendingPool(provider, {
  LENDING_POOL: lendingPoolAddress,
  WETH_GATEWAY: wethGatewayAddress,
});

/*
- @param `user` The ethereum address that will receive the aTokens 
- @param `reserve` The ethereum address of the reserve asset 
- @param `amount` The amount of aToken being redeemed 
- @param @optional `aTokenAddress` The ethereum address of the aToken. Only needed if the reserve is ETH mock address 
- @param @optional `onBehalfOf` The amount of aToken being redeemed. It will default to the user address
*/
const txs: EthereumTransactionTypeExtended[] = lendingPool.withdraw({
  user,
  reserve,
  amount,
  aTokenAddress,
  onBehalfOf,
});

Submit transaction as shown here


swapBorrowRateMode

Borrowers can use this function to swap between stable and variable borrow rate modes.

Sample Code
import { LendingPool, InterestRate } from '@aave/contract-helpers';

const lendingPool = new LendingPool(provider, {
  LENDING_POOL: lendingPoolAddress,
});

/*
- @param `user` The ethereum address that wants to swap rate modes 
- @param `reserve` The address of the reserve on which the user borrowed 
- @param `interestRateMode` //Whether the borrow will incur a stable (InterestRate.Stable) or variable (InterestRate.Variable) interest rate
*/
const txs: EthereumTransactionTypeExtended[] = lendingPool.swapBorrowRateMode({
  user,
  reserve,
  interestRateMode,
});

Submit transaction as shown here


setUsageAsCollateral

Allows depositors to enable or disable a specific deposit as collateral

Sample Code
import { LendingPool, InterestRate } from '@aave/contract-helpers';

const lendingPool = new LendingPool(provider, {
  LENDING_POOL: lendingPoolAddress,
});

/*
- @param `user` The ethereum address that enables or disables the deposit as collateral
- @param `reserve` The ethereum address of the reserve 
- @param `useAsCollateral` Boolean, true if the user wants to use the deposit as collateral, false otherwise
*/
const txs: EthereumTransactionTypeExtended[] = lendingPool.setUsageAsCollateral(
  {
    user,
    reserve,
    usageAsCollateral,
  },
);

Submit transaction as shown here


liquidationCall

Users can invoke this function to liquidate an undercollateralized position.

Sample Code
import { LendingPool } from '@aave/contract-helpers';

const lendingPool = new LendingPool(provider, {
  LENDING_POOL: lendingPoolAddress,
  WETH_GATEWAY: wethGatewayAddress,
});

/*
- @param `liquidator` The ethereum address that will liquidate the position 
- @param `liquidatedUser` The address of the borrower 
- @param `debtReserve` The ethereum address of the principal reserve 
- @param `collateralReserve` The address of the collateral to liquidated 
- @param `purchaseAmount` The amount of principal that the liquidator wants to repay
- @param @optional `getAToken` Boolean to indicate if the user wants to receive the aToken instead of the asset. Defaults to false
*/
const txs: EthereumTransactionTypeExtended[] = lendingPool.liquidationCall({
  liquidator,
  liquidatedUser,
  debtReserve,
  collateralReserve,
  purchaseAmount,
  getAToken,
});

Submit transaction(s) as shown here


swapCollateral

Allows users to swap a collateral to another asset

Sample Code
import {
  LendingPool,
  InterestRate,
  PermitSignature,
} from '@aave/contract-helpers';

const lendingPool = new LendingPool(provider, {
  LENDING_POOL: lendingPoolAddress,
  SWAP_COLLATERAL_ADAPTER: swapCollateralAdapterAddress,
  WETH_GATEWAY: wethGatewayAddress,
});

/*
- @param `user` The ethereum address that will liquidate the position 
- @param @optional `flash` If the transaction will be executed through a flashloan(true) or will be done directly through the adapters(false). Defaults to false 
- @param `fromAsset` The ethereum address of the asset you want to swap 
- @param `fromAToken` The ethereum address of the aToken of the asset you want to swap
- @param `toAsset` The ethereum address of the asset you want to swap to (get) 
- @param `fromAmount` The amount you want to swap 
- @param `toAmount` The amount you want to get after the swap 
- @param `maxSlippage` The max slippage that the user accepts in the swap 
- @param @optional `permitSignature` A permit signature of the tx. Only needed when previously signed (Not needed at the moment).
- @param `swapAll` Bool indicating if the user wants to swap all the current collateral
- @param @optional `onBehalfOf` The ethereum address for which user is swapping. It will default to the user address
- @param @optional `referralCode` Integrators are assigned a referral code and can potentially receive rewards. It defaults to 0 (no referrer)
- @param @optional `useEthPath` Boolean to indicate if the swap will use an ETH path. Defaults to false
*/
const txs: EthereumTransactionTypeExtended[] = await lendingPool.swapCollateral(
  {
    user,
    flash,
    fromAsset,
    fromAToken,
    toAsset,
    fromAmount,
    toAmount,
    maxSlippage,
    permitSignature,
    swapAll,
    onBehalfOf,
    referralCode,
    useEthPath,
  },
);

Submit transaction(s) as shown here


repayWithCollateral

Allows a borrower to repay the open debt with their collateral

Sample Code
import {
  LendingPool,
  InterestRate,
  PermitSignature,
} from '@aave/contract-helpers';

const lendingPool = new LendingPool(provider, {
  LENDING_POOL: lendingPoolAddress,
  REPAY_WITH_COLLATERAL_ADAPTER: repayWithCollateralAddress,
  WETH_GATEWAY: wethGatewayAddress,
});

/*
- @param `user` The ethereum address that will liquidate the position 
- @param `fromAsset` The ethereum address of the asset you want to repay with (collateral)
- @param `fromAToken` The ethereum address of the aToken of the asset you want to repay with (collateral)
- @param `assetToRepay` The ethereum address of the asset you want to repay 
- @param `repayWithAmount` The amount of collateral you want to repay the debt with
- @param `repayAmount` The amount of debt you want to repay 
- @param `permitSignature` A permit signature of the tx. Optional
- @param @optional `repayAllDebt` Bool indicating if the user wants to repay all current debt. Defaults to false
- @param `rateMode` //Enum indicating the type of the interest rate of the collateral
- @param @optional `onBehalfOf` The ethereum address for which user is swapping. It will default to the user address
- @param @optional `referralCode` Integrators are assigned a referral code and can potentially receive rewards. It defaults to 0 (no referrer)
- @param @optional `flash` If the transaction will be executed through a flashloan(true) or will be done directly through the adapters(false). Defaults to false
- @param @optional `useEthPath` Boolean to indicate if the swap will use an ETH path. Defaults to false
*/
const txs: EthereumTransactionTypeExtended[] =
  await lendingPool.repayWithCollateral({
    user,
    fromAsset,
    fromAToken,
    assetToRepay,
    repayWithAmount,
    repayAmount,
    permitSignature,
    repayAllDebt,
    rateMode,
    onBehalfOf,
    referralCode,
    flash,
    useEthPath,
  });

Submit transaction(s) as shown here


Governance V2

Example of how to use functions of the Aave governance service

import { AaveGovernanceService } from '@aave/contract-helpers';

const httpProvider = new Web3.providers.HttpProvider(
  process.env.ETHEREUM_URL || 'https://kovan.infura.io/v3/<project_id>',
);

const governanceService = new AaveGovernanceService(httpProvider, {
  GOVERNANCE_ADDRESS: aaveGovernanceV2Address,
  GOVERNANCE_HELPER_ADDRESS: aaveGovernanceV2HelperAddress,
  ipfsGateway: IPFS_ENDPOINT,
});

create

Creates a Proposal (needs to be validated by the Proposal Validator)

Sample Code
import { AaveGovernanceService } from '@aave/contract-helpers';

const governanceService = new AaveGovernanceService(rpcProvider, {
  GOVERNANCE_ADDRESS: aaveGovernanceV2Address,
  GOVERNANCE_HELPER_ADDRESS: aaveGovernanceV2HelperAddress,
  ipfsGateway: IPFS_ENDPOINT,
});

/*
- @param `user` The ethereum address that will create the proposal
- @param `targets` list of contracts called by proposal's associated transactions
- @param `values` list of value in wei for each proposal's associated transaction
- @param `signatures` list of function signatures (can be empty) to be used when created the callData
- @param `calldatas` list of calldatas: if associated signature empty, calldata ready, else calldata is arguments
- @param `withDelegatecalls` boolean, true = transaction delegatecalls the target, else calls the target
- @param `ipfsHash` IPFS hash of the proposal
- @param `executor` The ExecutorWithTimelock contract that will execute the proposal: ExecutorType.Short or ExecutorType.Long
*/
const tx = governanceService.create({
  user,
  targets,
  values,
  signatures,
  calldatas,
  withDelegatecalls,
  ipfsHash,
  executor,
});

Submit transaction as shown here


cancel

Cancels a Proposal. Callable by the guardian with relaxed conditions, or by anybody if the conditions of cancellation on the executor are fulfilled

Sample Code
import { AaveGovernanceService } from '@aave/contract-helpers';

const governanceService = new AaveGovernanceService(rpcProvider, {
  GOVERNANCE_ADDRESS: aaveGovernanceV2Address,
  GOVERNANCE_HELPER_ADDRESS: aaveGovernanceV2HelperAddress,
  ipfsGateway: IPFS_ENDPOINT,
});

/*
- @param `user` The ethereum address that will create the proposal
- @param `proposalId` Id of the proposal we want to queue
*/
const tx = governanceService.cancel({ user, proposalId });

Submit transaction as shown here


queue

Queue the proposal (If Proposal Succeeded)

Sample Code
import { AaveGovernanceService } from '@aave/contract-helpers';

const governanceService = new AaveGovernanceService(rpcProvider, {
  GOVERNANCE_ADDRESS: aaveGovernanceV2Address,
  GOVERNANCE_HELPER_ADDRESS: aaveGovernanceV2HelperAddress,
  ipfsGateway: IPFS_ENDPOINT,
});

/*
- @param `user` The ethereum address that will create the proposal
- @param `proposalId` Id of the proposal we want to queue
*/
const tx = governanceService.queue({ user, proposalId });

Submit transaction as shown here


execute

Execute the proposal (If Proposal Queued)

Sample Code
import { AaveGovernanceService } from '@aave/contract-helpers';

const governanceService = new AaveGovernanceService(rpcProvider, {
  GOVERNANCE_ADDRESS: aaveGovernanceV2Address,
  GOVERNANCE_HELPER_ADDRESS: aaveGovernanceV2HelperAddress,
  ipfsGateway: IPFS_ENDPOINT,
});

/*
- @param `user` The ethereum address that will create the proposal
- @param `proposalId` Id of the proposal we want to execute
*/
const tx = governanceService.execute({ user, proposalId });

Submit transaction as shown here


submitVote

Function allowing msg.sender to vote for/against a proposal

Sample Code
import { AaveGovernanceService } from '@aave/contract-helpers';

const governanceService = new AaveGovernanceService(rpcProvider, {
  GOVERNANCE_ADDRESS: aaveGovernanceV2Address,
  GOVERNANCE_HELPER_ADDRESS: aaveGovernanceV2HelperAddress,
  ipfsGateway: IPFS_ENDPOINT,
});

/*
- @param `user` The ethereum address that will create the proposal 
- @param `proposalId` Id of the proposal we want to vote 
- @param `support` Bool indicating if you are voting in favor (true) or against (false)
*/
const tx = governanceService.submitVote({ user, proposalId, support });

Submit transaction as shown here


delegate

Method for the user to delegate voting and proposition power to the chosen address

Sample Code
import { GovernancePowerDelegationTokenService } from '@aave/contract-helpers';

const powerDelegation = new GovernancePowerDelegationTokenService(rpcProvider);

/*
- @param `user` The ethereum address that will create the proposal 
- @param `delegatee` The ethereum address to which the user wants to delegate proposition power and voting power
- @param `governanceToken` The ethereum address of the governance token
*/
const tx = powerDelegation.delegate({ user, delegatee, governanceToken });

Submit transaction as shown here


delegateByType

Method for the user to delegate voting or proposition power to the chosen address

Sample Code
import { GovernancePowerDelegationTokenService } from '@aave/contract-helpers';

const powerDelegation = new GovernancePowerDelegationTokenService(rpcProvider);

/*
- @param `user` The ethereum address that will create the proposal 
- @param `delegatee` The ethereum address to which the user wants to delegate proposition power and voting power
- @param `delegationType` The type of the delegation the user wants to do: voting power ('0') or proposition power ('1')
- @param `governanceToken` The ethereum address of the governance token
*/
const tx = powerDelegation.delegateByType({
  user,
  delegatee,
  delegationType,
  governanceToken,
});

Submit transaction as shown here


Faucets

To use the testnet faucets which are compatible with Aave:

mint

Mint tokens for the usage on the Aave protocol on a test network. The amount of minted tokens is fixed and depends on the token

Sample Code
import { FaucetService } from '@aave/contract-helpers';

const faucetService = new FaucetService(provider, faucetAddress);

/*
- @param `userAddress` The ethereum address of the wallet the minted tokens will go
- @param `reserve` The ethereum address of the token you want to mint 
- @param `tokenSymbol` The symbol of the token you want to mint
*/
const tx = faucet.mint({ userAddress, reserve, tokenSymbol });

Submit transaction as shown here


Credit Delegation

Credit delegation is performed on the debtToken contract through the approveDelegation function, which approves a spender to borrow a specified amount of that token.

Accessing delegated credit is done by passing the delegator address as the onBehalfOf parameter when calling borrow on the Pool (V3) or LendingPool (V2).

approveDelegation

Sample Code
import { BaseDebtToken, ERC20Service } from '@aave/contract-helpers';
import { ethers } from 'ethers';

// Sample public RPC address for querying polygon mainnet
const provider = new ethers.providers.JsonRpcProvider(
  'https://polygon-rpc.com',
);

const delegationServicePolygonV2USDC = new BaseDebtToken(
  provider,
  new ERC20Service(provider), // This parameter will be removed in future utils version
);

const approveDelegation = delegationServicePolygonV2USDC.approveDelegation({
  user: '...', /// delegator
  delegatee: '...',
  debtTokenAddress: '...', // can be any V2 or V3 debt token
  amount: 1, // in decimals of underlying token
});

Submit transaction as shown here


New Transaction Methods

The @aave/contract-helpers package is currently undergoing a refactor to simplify the end-to-end process of building transactions. This section is a work in progress and working and will be updated as new methods are added.

Setup

The samples given below will also use the following packages:

npm i @aave/contract-helpers @bgd-labs/aave-address-book ethers@5

Samples

Getting Started

New transaction methods are accessible from the PoolBundle and LendingPool objects. The following script demonstrates how to initialize these objects and the functions which are available.

const ethers = require("ethers");
const markets = require("@bgd-labs/aave-address-book");
const { PoolBundle, LendingPoolBundle } = require("@aave/contract-helpers");

// Create provider
// Can use custom RPC orR a local fork network from tenderly, ganache, foundry, hardhat, etc. for testing
const provider = ethers.getDefaultProvider("homestead");

function getPoolBundle(v2, marketKey) {
  if (v2) {
    return new LendingPoolBundle(provider, {
      LENDING_POOL: markets[marketKey].POOL,
      WETH_GATEWAY: markets[marketKey].WETH_GATEWAY,
    });
  } else {
    return new PoolBundle(provider, {
      POOL: markets[marketKey].POOL,
      WETH_GATEWAY: markets[marketKey].WETH_GATEWAY,
    });
  }
}

console.log("Available markets", markets);

// V2 + V3 Methods

async function getApprovedAmount(v2, marketKey, user, token) {
  const poolBundle = getPoolBundle(v2, marketKey);
  if (v2) {
    try {
      const approvedAmount =
        await poolBundle.depositTxBuilder.getApprovedAmount({
          user,
          token,
        });
      return approvedAmount;
    } catch (error) {
      console.error("Error fetching approved amount", error);
    }
  } else {
    try {
      const approvedAmount = await poolBundle.supplyTxBuilder.getApprovedAmount(
        {
          user,
          token,
        }
      );
      return approvedAmount;
    } catch (error) {
      console.error("Error fetching approved amount", error);
    }
  }
}

function generateApprovalTx(user, token, amount, marketKey) {
  const poolBundle = getPoolBundle(v2, marketKey);
  if (v2) {
    try {
      const approvedAmount = poolBundle.depositTxBuilder.generateApprovalTx({
        user,
        token,
        amount,
      });
      return approvedAmount;
    } catch (error) {
      console.error("Error fetching approved amount", error);
    }
  } else {
    try {
      const approvedAmount = poolBundle.supplyTxBuilder.generateApprovalTx({
        user,
        token,
        amount,
      });
      return approvedAmount;
    } catch (error) {
      console.error("Error fetching approved amount", error);
    }
  }
}

function generateSupplyTx(v2, user, token, amount, marketKey) {
  const poolBundle = getPoolBundle(v2, marketKey);
  if (v2) {
    try {
      const txData = poolBundle.depositTxBuilder.generateTxData({
        user,
        reserve: token,
        amount,
      });
      return txData;
    } catch (error) {
      console.error("Error generating supply tx data", error);
    }
  } else {
    try {
      const txData = poolBundle.supplyTxBuilder.generateTxData({
        user,
        reserve: token,
        amount,
      });
      return txData;
    } catch (error) {
      console.error("Errorgenerating deposit tx data", error);
    }
  }
}

// V3 Methods

function generateSupplyWithPermitTx(
  user,
  token,
  amount,
  signature,
  marketKey,
  deadline
) {
  const poolBundle = getPoolBundle(false, marketKey);
  try {
    const txData = poolBundle.supplyTxBuilder.generateSignedTxData({
      user,
      reserve: token,
      amount,
      signature,
      deadline,
    });
    return txData;
  } catch (error) {
    console.error("Errorgenerating deposit tx data", error);
  }
}

async function generateSupplySignatureRequest(user, token, marketKey, amount) {
  const spender = markets[marketKey].POOL;
  const tokenERC20Service = new ERC20Service(provider);
  const tokenERC2612Service = new ERC20_2612Service(provider);
  const { name } = await tokenERC20Service.getTokenData(token);
  const { chainId } = await provider.getNetwork();
  const nonce = await tokenERC2612Service.getNonce({
    token,
    owner: user,
  });
  const deadline = Math.floor(Date.now() / 1000 + 3600).toString();
  const typeData = {
    types: {
      EIP712Domain: [
        { name: "name", type: "string" },
        { name: "version", type: "string" },
        { name: "chainId", type: "uint256" },
        { name: "verifyingContract", type: "address" },
      ],
      Permit: [
        { name: "owner", type: "address" },
        { name: "spender", type: "address" },
        { name: "value", type: "uint256" },
        { name: "nonce", type: "uint256" },
        { name: "deadline", type: "uint256" },
      ],
    },
    primaryType: "Permit",
    domain: {
      name,
      version: "1",
      chainId,
      verifyingContract: token,
    },
    message: {
      owner: user,
      spender: spender,
      value: amount,
      nonce,
      deadline,
    },
  };
  return JSON.stringify(typeData);
}

Complete CLI Example

The following is a script which generates a command line interface to interact with the V2 and V3 Ethereum markets. This can be used to generate txns or used as a reference for how to integrate new transaction methods.

const ethers = require("ethers");
const markets = require("@bgd-labs/aave-address-book");
const {
  PoolBundle,
  LendingPoolBundle,
  ERC20Service,
  ERC20_2612Service,
  UiPoolDataProvider,
  ChainId,
} = require("@aave/contract-helpers");
const readline = require("readline");

// Create provider and connect wallet
// Can use a local fork network from tenderly, ganache, foundry, hardhat, etc. for testing
const provider = ethers.getDefaultProvider("homestead");

function getPoolBundle(v2, marketKey) {
  if (v2) {
    return new LendingPoolBundle(provider, {
      LENDING_POOL: markets[marketKey].POOL,
      WETH_GATEWAY: markets[marketKey].WETH_GATEWAY,
    });
  } else {
    return new PoolBundle(provider, {
      POOL: markets[marketKey].POOL,
      WETH_GATEWAY: markets[marketKey].WETH_GATEWAY,
    });
  }
}

async function getApprovedAmount(v2, marketKey, user, token) {
  const poolBundle = getPoolBundle(v2, marketKey);
  if (v2) {
    try {
      const approvedAmount =
        await poolBundle.depositTxBuilder.getApprovedAmount({
          user,
          token,
        });
      return approvedAmount;
    } catch (error) {
      console.error("Error fetching approved amount", error);
    }
  } else {
    try {
      const approvedAmount = await poolBundle.supplyTxBuilder.getApprovedAmount(
        {
          user,
          token,
        }
      );
      return approvedAmount;
    } catch (error) {
      console.error("Error fetching approved amount", error);
    }
  }
}

function isValidUint256(input) {
  try {
    const inputBN = ethers.BigNumber.from(input);
    const zero = ethers.BigNumber.from(0);
    const maxUint256 = ethers.constants.MaxUint256;

    if (inputBN.gte(zero) && inputBN.lte(maxUint256)) {
      return true;
    } else {
      return false;
    }
  } catch (error) {
    return false;
  }
}

function generateApprovalTx(user, token, amount, marketKey) {
  const poolBundle = getPoolBundle(v2, marketKey);
  if (v2) {
    try {
      const approvedAmount = poolBundle.depositTxBuilder.generateApprovalTx({
        user,
        token,
        amount,
      });
      return approvedAmount;
    } catch (error) {
      console.error("Error fetching approved amount", error);
    }
  } else {
    try {
      const approvedAmount = poolBundle.supplyTxBuilder.generateApprovalTx({
        user,
        token,
        amount,
      });
      return approvedAmount;
    } catch (error) {
      console.error("Error fetching approved amount", error);
    }
  }
}

function generateSupplyTx(v2, user, token, amount, marketKey) {
  const poolBundle = getPoolBundle(v2, marketKey);
  if (v2) {
    try {
      const txData = poolBundle.depositTxBuilder.generateTxData({
        user,
        reserve: token,
        amount,
      });
      return txData;
    } catch (error) {
      console.error("Error generating supply tx data", error);
    }
  } else {
    try {
      const txData = poolBundle.supplyTxBuilder.generateTxData({
        user,
        reserve: token,
        amount,
      });
      return txData;
    } catch (error) {
      console.error("Errorgenerating deposit tx data", error);
    }
  }
}

function generateSupplyWithPermitTx(
  user,
  token,
  amount,
  signature,
  marketKey,
  deadline
) {
  const poolBundle = getPoolBundle(false, marketKey);
  try {
    const txData = poolBundle.supplyTxBuilder.generateSignedTxData({
      user,
      reserve: token,
      amount,
      signature,
      deadline,
    });
    return txData;
  } catch (error) {
    console.error("Errorgenerating deposit tx data", error);
  }
}

async function generateSupplySignatureRequest(user, token, marketKey, amount) {
  const spender = markets[marketKey].POOL;
  const tokenERC20Service = new ERC20Service(provider);
  const tokenERC2612Service = new ERC20_2612Service(provider);
  const { name } = await tokenERC20Service.getTokenData(token);
  const { chainId } = await provider.getNetwork();
  const nonce = await tokenERC2612Service.getNonce({
    token,
    owner: user,
  });
  const deadline = Math.floor(Date.now() / 1000 + 3600).toString();
  const typeData = {
    types: {
      EIP712Domain: [
        { name: "name", type: "string" },
        { name: "version", type: "string" },
        { name: "chainId", type: "uint256" },
        { name: "verifyingContract", type: "address" },
      ],
      Permit: [
        { name: "owner", type: "address" },
        { name: "spender", type: "address" },
        { name: "value", type: "uint256" },
        { name: "nonce", type: "uint256" },
        { name: "deadline", type: "uint256" },
      ],
    },
    primaryType: "Permit",
    domain: {
      name,
      version: "1",
      chainId,
      verifyingContract: token,
    },
    message: {
      owner: user,
      spender: spender,
      value: amount,
      nonce,
      deadline,
    },
  };
  return JSON.stringify(typeData);
}

const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
});

function marketContinue(marketName) {
  console.log("Press enter to continue");
  let load = true;
  rl.on("line", () => {
    if (load) {
      marketOptions(marketName);
      load = false;
    }
  });
}

async function marketOptions(marketName) {
  const v2 = marketName.includes("V2");
  console.log("\n");
  console.log("Type the number of action to perform and press Enter");
  console.log("1. Print market addresses");
  console.log("2. Print reserves info");
  console.log("3. Print user info");
  console.log("4. Check approved amount to supply");
  console.log("5. Build approval tx");
  if (v2) {
    console.log("6. Build deposit tx");
  } else {
    console.log("6. Build supply tx");
    console.log("7. Build signature request");
    console.log("8. Build supplyWithPermit tx data");
  }
  console.log(`${v2 ? "7" : "9"}. <- Back`);
  console.log("\n");

  rl.question("Your choice: ", async (answer) => {
    const choice = parseInt(answer);
    switch (choice) {
      case 1:
        const marketAddresses = markets[marketName];
        console.log(marketAddresses);
        console.log("\n");
        marketContinue(marketName);
        break;
      case 2:
        console.log("Fetching reserves...");
        try {
          // Create UiPoolDataProvider object for fetching protocol reserves data
          const uiPoolDataProvider = new UiPoolDataProvider({
            uiPoolDataProviderAddress: v2
              ? markets.AaveV2Ethereum.UI_POOL_DATA_PROVIDER
              : markets.AaveV3Ethereum.UI_POOL_DATA_PROVIDER,
            provider,
            chainId: ChainId.mainnet,
          });
          const reserves = await uiPoolDataProvider.getReservesHumanized({
            lendingPoolAddressProvider: v2
              ? markets.AaveV2Ethereum.POOL_ADDRESSES_PROVIDER
              : markets.AaveV3Ethereum.POOL_ADDRESSES_PROVIDER,
          });
          console.log(reserves);
          console.log("\n");
        } catch (error) {
          console.log("Error fetching pool reserves", error);
        } finally {
          marketContinue(marketName);
        }
        break;
      case 3:
        console.log("Enter an ethereum address");
        rl.question("Input: ", async (answer) => {
          if (!ethers.utils.isAddress(answer)) {
            console.log("Not a valid ethereum address");
            marketContinue(marketName);
          } else {
            console.log("Fetching user reserves...");
            try {
              // Create UiPoolDataProvider object for fetching protocol reserves data
              const uiPoolDataProvider = new UiPoolDataProvider({
                uiPoolDataProviderAddress: v2
                  ? markets.AaveV2Ethereum.UI_POOL_DATA_PROVIDER
                  : markets.AaveV3Ethereum.UI_POOL_DATA_PROVIDER,
                provider,
                chainId: ChainId.mainnet,
              });
              const reserves = await uiPoolDataProvider.getReservesHumanized({
                lendingPoolAddressProvider: v2
                  ? markets.AaveV2Ethereum.POOL_ADDRESSES_PROVIDER
                  : markets.AaveV3Ethereum.POOL_ADDRESSES_PROVIDER,
              });
              console.log(reserves);
              console.log("\n");
            } catch (error) {
              console.log("Error fetching pool reserves", error);
            } finally {
              marketContinue(marketName);
            }
          }
        });
        break;
      case 4:
        console.log("Enter a user address");
        rl.question("Input: ", async (user) => {
          if (!ethers.utils.isAddress(user)) {
            console.log("Not a valid ethereum address");
            marketContinue(marketName);
          } else {
            console.log("Input address of underyling token to suply");
            rl.question("Input: ", async (token) => {
              if (!ethers.utils.isAddress(token)) {
                console.log("Not a valid ethereum address");
                marketContinue(marketName);
              } else {
                const approvedAmount = await getApprovedAmount(
                  v2,
                  marketName,
                  user,
                  token
                );
                console.log(`Approved amount: ${approvedAmount}`);
                console.log("\n");
                console.log(
                  "Note: an approval amount of -1 represents a uint256.max approval or an asset that doesn't require approval (base assets)"
                );
                console.log("\n");
                marketContinue(marketName);
              }
            });
          }
        });
        break;
      case 5:
        console.log("Enter a user address");
        rl.question("Input: ", async (user) => {
          if (!ethers.utils.isAddress(user)) {
            console.log("Not a valid ethereum address");
            marketContinue(marketName);
          } else {
            console.log("Input address of underyling token to suply");
            rl.question("Input: ", async (token) => {
              if (!ethers.utils.isAddress(token)) {
                console.log("Not a valid ethereum address");
                marketContinue(marketName);
              } else {
                console.log("Input amount to supply, in native token decimals");
                rl.question("Input: ", async (amount) => {
                  if (isValidUint256(amount)) {
                    const txData = generateApprovalTx(
                      user,
                      token,
                      amount,
                      marketName
                    );
                    console.log(txData);
                    console.log("\n");
                    marketContinue(marketName);
                  } else {
                    console.log(
                      "Invalid amount input, must be number between 0 and uint256.max"
                    );
                  }
                });
              }
            });
          }
        });
        break;
      case 6:
        console.log("Enter a user address");
        rl.question("Input: ", async (user) => {
          if (!ethers.utils.isAddress(user)) {
            console.log("Not a valid ethereum address");
            marketContinue(marketName);
          } else {
            console.log("Input address of underyling token to suply");
            rl.question("Input: ", async (token) => {
              if (!ethers.utils.isAddress(token)) {
                console.log("Not a valid ethereum address");
                marketContinue(marketName);
              } else {
                console.log("Input amount to supply, in native token decimals");
                rl.question("Input: ", async (amount) => {
                  if (isValidUint256(amount)) {
                    const txData = generateSupplyTx(
                      v2,
                      user,
                      token,
                      amount,
                      marketName
                    );
                    console.log(txData);
                    console.log("\n");
                    marketContinue(marketName);
                  } else {
                    console.log(
                      "Invalid amount input, must be number between 0 and uint256.max"
                    );
                    marketContinue(marketName);
                  }
                });
              }
            });
          }
        });
        break;
      case 7:
        if (v2) {
          promptUser();
          break;
        }
        console.log("Enter a user address");
        rl.question("Input: ", async (user) => {
          if (!ethers.utils.isAddress(user)) {
            console.log("Not a valid ethereum address");
            marketContinue(marketName);
          } else {
            console.log("Input address of underyling token to suply");
            rl.question("Input: ", async (token) => {
              if (!ethers.utils.isAddress(token)) {
                console.log("Not a valid ethereum address");
                marketContinue(marketName);
              } else {
                console.log("Input amount to supply, in native token decimals");
                rl.question("Input: ", async (amount) => {
                  if (isValidUint256(amount)) {
                    const dataToSign = await generateSupplySignatureRequest(
                      user,
                      token,
                      marketName,
                      amount
                    );
                    console.log(`Data to sign: ${dataToSign}`);
                    console.log("\n");
                    marketContinue(marketName);
                  } else {
                    console.log(
                      "Invalid amount input, must be number between 0 and uint256.max"
                    );
                    marketContinue(marketName);
                  }
                });
              }
            });
          }
        });
        break;

      case 8:
        if (v2) {
          console.log("action not implemented");
        }
        console.log("Enter a user address");
        rl.question("Input: ", async (user) => {
          if (!ethers.utils.isAddress(user)) {
            console.log("Not a valid ethereum address");
            marketContinue(marketName);
          } else {
            console.log("Input address of underyling token to suply");
            rl.question("Input: ", async (token) => {
              if (!ethers.utils.isAddress(token)) {
                console.log("Not a valid ethereum address");
                marketContinue(marketName);
              } else {
                console.log("Input amount to supply, in native token decimals");
                rl.question("Input: ", async (amount) => {
                  if (isValidUint256(amount)) {
                    console.log("Input signature");
                    const deadline = Math.floor(
                      Date.now() / 1000 + 3600
                    ).toString();
                    rl.question("Input: ", async (signature) => {
                      const txData = generateSupplyWithPermitTx(
                        user,
                        token,
                        amount,
                        signature,
                        marketName,
                        deadline
                      );
                      console.log(txData);
                      console.log("\n");
                      marketContinue(marketName);
                    });
                  } else {
                    console.log(
                      "Invalid amount input, must be number between 0 and uint256.max"
                    );
                  }
                });
              }
            });
          }
        });
        break;
      case 9:
        promptUser();
        break;
      default:
        console.log("action not implemented");
        marketContinue(marketName);
        break;
    }
  });
}

function promptUser() {
  console.log("1. AaveV3Ethereum");
  console.log("2. AaveV2Ethereum");
  console.log("3. Exit");
  console.log("\n");
  console.log(
    "Type the number of the market you would like to interact with and press Enter."
  );

  rl.question("Your choice: ", async (answer) => {
    const choice = parseInt(answer);

    if (choice === 1) {
      console.log("You selected AaveV3Ethereum");
      marketOptions("AaveV3Ethereum");
    } else if (choice === 2) {
      console.log("You selected AaveV2Ethereum");
      marketOptions("AaveV2Ethereum");
    } else if (choice === 3) {
      rl.close();
    } else {
      console.log("Invalid market selection");
      promptUser();
    }
  });
}

function welcome() {
  console.log("Welcome to");
  console.log(`

     ######   ##     ##  #######   ######  ########     ######  ##       ####
    ##    ##  ##     ## ##     ## ##    ##    ##       ##    ## ##        ##
    ##        ##     ## ##     ## ##          ##       ##       ##        ##
    ##   #### ######### ##     ##  ######     ##       ##       ##        ##
    ##    ##  ##     ## ##     ##       ##    ##       ##       ##        ##
    ##    ##  ##     ## ##     ## ##    ##    ##       ##    ## ##        ##
     ######   ##     ##  #######   ######     ##        ######  ######## ####

    `);
  console.log("Press enter to begin");
  let load = true;
  rl.on("line", () => {
    if (load) {
      promptUser();
      load = false;
    }
  });
}

welcome();

aave-utilities's People

Contributors

0xnook avatar alderian avatar defispartan avatar dependabot[bot] avatar dghelm avatar drewcook avatar foodaka avatar grothem avatar joaquinbattilana avatar joshstevens19 avatar kartojal avatar rex4539 avatar sakulstra avatar satanworker avatar sendra avatar volt62 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

aave-utilities's Issues

tsdocs comments

We should provide tsdocs for non self explanatory properties to enable helpful comments by IDEs.

We should first fix the cases we already have and from here on make it a requirements in reviews. We might wanna add some contribution guidelines to account for such things.

image

Add UiPoolDataProvider to contract-helpers

Feature

Create the data module of tx-builder and add the different data getters for UiPoolDataProvider. (Keep in mind that it should use the new version of the provider, the one that has not incentives data on the reserves)

Move configs to independent files

Currently we have most configs in packag.json - we had to learn that#s confusing is people didn't seem to understand where lint & jest rules came from.
Let's move it to a config file or folder

Add Fei incentives as native

Background: They used the wrong debt token which has the incentives controller as private. This means they cannot get the incentives controller from the token and therefore cannot display the correct apr.

Hardcode Fei incentives so it is as if it was native incentive, so it can show apr % and can be claimed directly from app.aave.com

This is needed because the FEI debtToken that is incentiviced is not from the latest version of tokens, so we can not get the incentive information automatically from the UiIncentivesProvider contract

Simplify parameters

Currently the data functions require an excessive number of inputs, this should be re-designed to be easier to integrate.

Suggestion:

An aaveDataProvider object that can be initialized with a network and provider, and supports the following functions

userSummary(userAddress, marketAddress?, assetAddress?)

reserveSummary(marketAddress?, assetAddress?)

where marketAddress = lendingPoolAddressProvider for the market, assetAddress = underlyingAssetAddress, and market/asset default to all if not specified.

use ethereum-abi-types-generator instead of typechain ones

TypeChain is awesome but at the end of the day, it adds bundle size to the output. Every TypeChain factory IS a class and you have to call it like this.contractFactory.connect < this ADDS bundle size to the output. If you look what is in some of these factories it has LONG abi strings etc which can eat up bundle size for sure! Just look at tx-builder > contract-types that's 428KB of stuff that could be added to the output of the bundle.

I promote we use https://github.com/joshstevens19/ethereum-abi-types-generator < was built for this exact reason you just use native ethers interface and everything in the generated types are interfaces meaning once compiled no bundle size is added to the output (as interfaces are not real). It also does other things typechain does not > https://github.com/joshstevens19/ethereum-abi-types-generator#ethereum-abi-types-generator-vs-typechain

On top of this I see in the project you have got the return types of the contracts tx-builder > types this will all be redundant with this package as it exposes them all for you without you having to recreate it.

not the end of the world but if our aim is bundle size aka smallest package going then this could be a good place to start.

Integrate ethereum-multicall into the lib

We tend to do separate JSONRPC async calls even if we are calling the same contract for example

image

This will call the same contract twice once to get the decimals and another time to get the allowance. We can bulk these together into 1 JSONRPC call meaning we doing less calls and quicker responses.

metric shows that a JSONRPC request most the times the network coming back eats up most the response time

say you got 3 jsonrpc calls they will probs take 200MS each = 600MS (as it awaits for response)
if you batch this in 1 single JSONRPC call the result tends to be 200MS or close to = x3 improvement in speed.

We can batch together more then just a single contract as well say a method calls 4-5 different contracts we can use this to batch it in 1 JSONRPC again. This can really improve speed and bills from the node provider.

Open source lib which I support which we could use for this -
https://github.com/joshstevens19/ethereum-multicall

Update Caching-service with new Helper Contracts

Feature Request

Update the caching-service with the new UiPoolDataProvider (without the reserve incentives)

Add the new IncentivesProvider helper contract to the caching-service so as to query with different frequency (15 - 20 secs) than the reserves quering.

The data provided by this should be usable with the new aave-js lib

fix lerna / migrate away from lerna

Feature Request

As you can see in our ci workflow we're having multiple issues with lerna right now.
lerna/lerna#2788
lerna/lerna#1893
Lerna seems to be no longer properly maintained: lerna/lerna#1893
Might make sense to put some effort into fixing them/maintaining a fork as prs at lerna seem to be no longer to be accepted.

Alternatives to consider

BaseCurrency

Currently we use both USD and ETH in he library.
As the baseCurrency is variable though we shouldn't be opinionated on the currency imo (especially now as it might change).

  • In reserves the priceInEth will essentially be tokenPriceInReferenceCurrency (perhaps we find a better name, but that's what it is, might be eth/usd/avax, we don't know we don't care)
  • Instead of usdPriceEth we have a usdPriceReferenceCurrency (for usd feeds this will be one)
  • The only addition we'll have is a referencePriceDecimals field which oppose to our current static 18 decimals needs to be used when doing stuff with the former usdPriceEth

Feel free to suggest better parameter naming

expose aave (helper) addresses

Issue

Currently there's no easy way to figure out the aave addresses (aka there's no package one can import to get the currently up to date addresses). Also there's no easy way to get notified in case an address was updated.

What currently get's closest to this is:
https://github.com/aave/aave-addresses with jsons per network https://aave.github.io/aave-addresses/kovan.json

This is lacking helper contracts though as they are not registered at the protocol.

Proposed solution

For helper contracts I think it would be reasonable to have a static addresses = {[Network]: string} on the service so that people can simply access the address.

Alternative solution

We could also have a @aave/constants package or similar containing the addresses. This package could contain all the "generatable addresses" and all the helper contract addresses.

Support multiple IncentiveController's per network

Currently each network only has one associated reward asset and IncentivesController in the aave-ui. That#s a oversimplification that was true, but is no longer true.

In theory every subtoken (a/s/v) can have it's own incentiveController and we should support that.

Refactor formatUserSummary

1.) Break logic in generate-raw-summary and generate-raw-reserve down further.

  • Separate files for formatting the ReserveData, ComputedUserReserve, and summary fields to reduce complexity and required parameters for each file

2.) Remove any modifications to ReserveData in format-user-reserve, should all be handled in previous steps

3.) Unit test

  • calculate-rewards
  • calculate-supplies
  • generate-raw-user-reserve *once finished
  • generate-raw-user-summary *once finished
  • format-user-reserve *once finished
  • format-user-summary

Remove incentive calculation from generalized methods.

Currently formatReserves does a lot besides formatting the reserve. Reward incentive calculation should not be part of it, because:
a) most reserves don't have rewards
b) you might not be interested in rewards
c) in the future we might have multiple rewards per reserve

Therefore my suggestion would be to not do it at all in formatReserves and move the responsibility to userland via calculateAPYs or similar. Once we know exactly how this will work in the future we might add an additional helper method to calculate multiple incentives at once, but that's not needed yet.

  • remove incentive calculation from formatReserve
  • remove incentive calculation from formatUserReserve
  • expose helper methods to do it on demand for a reserve or userReserve

Move Incentives from UiPoolDataProvider to new UiIncentivesDataProvider contract

Remove the return of the reserve incentives from the UiPoolDataProvider.

Use current deployed version as base: https://github.com/aave/protocol-v2/blob/feat/split-ui-dataprovider-logic/contracts/misc/UiPoolDataProvider.sol

move from 139 - 160 (reserve incentives)
move from 187 - 200 (user reserve incentives)

Also move emissionEndTimestamp (given by incentivesController)
Also move userUnclaimedRewards (incentive rewards unclaimed by the user)

We need to Add also an extra check to instead of passing or hardcoding the incentives controller, we should get it from the a/s/v tokens (as they are initialized with it) this will give us also the possibility to operate with incentive controller by token instead of by network. Take into account that having incentivesController by token we will also need to get emissionEndTimestamp by token (not sure if this will have reaple effect. I think it shouldn't as if multiple tokens have same incentivesController the emissionEndTimestamp will be the same)

userUnclaimedRewards will also be affected by the incentivesController by token. If we have different IncentivesControllers then we provably will need to add all the userUnclaimedRewards but if they are the same then we shouldn't as userUnclaimedRewards accounts for all the unclaimedRewards of the different token incentives. Maybe we could calculate teh unclaimed rewards by getting for every asset the method getUserAssetData, and reproducing smart contract calls in the lib, this way we could query for every token without the need to add extra logic in the contract. (So yeah, take a deeper look at that part)

Helpers refactor

Change lendingPool name to lendingPoolAddressProvider

Change to typechain

Remove TheGraph in as many places as possible

Obviously, TheGraph is good to do some queries but on some cases we do not need to use the graph. Even more nothing stopping us create solidity helper contracts to avoid fees altogether this would improve:

  • charges and fees on TheGraph
  • not dependent on them going down and use not working
  • flexibility with responses from the helper sol contract
  • naturally faster and onchain real-time data (not dependent on TheGraph indexing it slower etc)

A lot of cases we don't even need a helper sol we can use multicall and the standard onchain contract to lookup the data.

required timestamps

In the current version of @aave/protocol-js we have a lot of non required timestamps where we then default to Date.now() or similar.

This caused a few issues in the past with wrong system clocks and shit like that. While making these field required won't necessarily solve these issues it will make debugging them easier.

Imo low level utility library should be as explicit as possible - if a method needs the currentTimestamp to perform it's math it should be a required field, not optional.

This was previously discussed and decided - if you disagree though, feel free to comment.

UiPoolDataProvider multi Base Currency Feeds

Feature

We need to update the UiPoolDataProvider so it takes into account the base currency of the market. this is the version we should branch of: https://github.com/aave/protocol-v2/tree/feat/split-ui-dataprovider-logic

To do this we need to get the information of the AaveOracle.
0 - get AaveOracle from PoolAddressProvider
1 - get Base currency information from AaveOracle:

  • We have two versions of AaveOracle so we need to account for that when getting the info from there:
    - Check if we can get BASE_CURRENCY (new version). if we can:
    - check if its 0x0 (usd case)
    - if its erc20 address also get BASE_UNITS (decimals)
    - if we can't get BASE_CURRENCY (means we are on old version). Then get WETH constant. get decimals from WETH return
    2 - get / return the base currency price in usd(provably by passing the base currency address to the oracle). In case of 0x0 (usd) return 1
    3- get / return native network currency in usd price (we could hardcode the chainlink pricefeed). This is so we can display correct usd pricing for gas estimations
    4- return base currency decimals. In case of Usd 8

formatUserSummary doesn't return availableBorrowsUSD

Bug Report

formatUserSummary doesn't return availableBorrowsUSD but all other usd values

Package name / version

0.15

Expected behavior

Method should return either all or no usd values, but not mixed content.

Transaction builder for lending pool

Background:
The tx builder in AAVEJS. A private package that we open sourced. Migrated https://github.com/aave/aave-js/tree/master/src/tx-builder into Aave JS.

If you want to interact with the protocol there are specific requirements. A lot of these methods have many parameters. Ie specify which pool, credit delegation, borrow, flashloan, etc.

The tx builder allows you to expose an easy to use and type interface to interact with the Aave protocol.

  • Deposit
  • Borrow
  • ....

Problem: This was built for Aave v1 and for graph data. Most times you get data from RPC and not the graph data. Currently we are transforming the slim RPC data back to nested graph structure so can use older helper functions. They will split the up the configuration file https://github.com/aave/aave-js/blob/master/src/tx-builder/config/defaultConfig.ts#L3 . Plan is to expose single service for lending pool, incentives, governance, staking, swapping.

Define namespaces

Namespaces = beautiful codebase to work with mainly because its easy to get to certain things. for example on v1 tx builder you have a file called computations-and-formatting.ts and this has many functions in it. If we can expose a clean interface to interact with it this means the learning curve for the user is a lot less.

stuff like

aave.v1.calculate.compoundedInterest
aave.v1.calculate.healthFactorFromBalances
aave.v1.calculate.availableBorrowsETH
aave.v1.format.reserves
aave.v1.format.userSummaryData

etc etc! you want to do this in a partial import way so would be more like:

import { calculate as v1Calculate, format as v1Format } from 'aavejs/v1'

v1Calculate.compoundedInterest
v1Calculate.healthFactorFromBalances
v1Calculate.availableBorrowsETH
v1Format.reserves
v1Format.userSummaryData

The aim here is to make your tech easy for everyone to understand, starting with the interface just like a REST API make it human-readable and its a great start

Contract types should be its own package

These are all generated files aka they should not be touched manually they are autogenerated we should move them out of touch so they don't randomly get changed. Going forward we should sync with the contracts and make on push generate these typings and upload them to the shared package.

Multipackage releases

Currently aave-js is a single pakage shipping everything included although you might never need it.
Therefore it would be great if our new package structure would allow publishing @aave/tx-builder and perhaps more from a single repo @aave/calculation-utils.

In the past i've been using lerna for these kind of things, but there might be newer/cooler stuff with native typescript support.
Example of a lerna monrorepo: visx

Document process for fetching and formatting user and reserves data

Add a guide to the README explaining the process used on the Aave frontend to fetch and format pool data using the UiPoolDataProvider contract and formatReserves / formatUserSummaryData helper functions.

Add UiPoolDataProvider contract code and addresses to the GitBook docs.

Create complete class typing's for everything which holds an important that it must be a certain format/value etc

So when we are dealing with something that if something goes wrong with the value or the value wrong someone could execute something very bad proper class types normally come in nice. What I mean by this is let's take userId as an example you probably wouldn't do it for this but just to explain what I mean and assume its a guid, instead of string it be a UserId class aka:

export function formatUserSummaryData(
  poolReservesData: ReserveData[],
  rawUserReserves: UserReserveData[],
  userId: string,
  usdPriceEth: BigNumberValue,
  currentTimestamp: number

turns into

export function formatUserSummaryData(
  poolReservesData: ReserveData[],
  rawUserReserves: UserReserveData[],
  userId: UserId,
  usdPriceEth: BigNumberValue,
  currentTimestamp: number

Now on this class, it validates and throws runtime errors if it doesn't match the time on new User(xxxxx) stopping anything bad from happening. You could write it similar to this:

export default class UserId extends BaseType<Guid, string, string> {
  private _value: string | undefined;

  constructor(value: string) {
    super(new.target.name, value);
  }

  public equals(other: Guid): boolean {
    return other.value === this.value;
  }

  protected validate(): void {
    if (typeof this._value !== 'string' || !Utils.isGuid(this._value)) {
      throw Errors.runtimeErrorWithLogger(
        this._log,
        'String supplied is not a guid string',
        this._value,
      );
    }
  }

  protected setValue(value: string): void {
    if (typeof value === 'string') {
      this._value = value;
    }
  }

  public get value(): string {
    if (!this._value) {
      throw this.valueNotSetError();
    }
    return this._value;
  }
}
export default abstract class BaseType<TClass, TValue, TSetValue> {
  protected _log: Logger;
  constructor(className: string, value: TSetValue) {
    this._log = new Logger(className);

    // set the value
    this.setValue(value);
    // force the validation to always fire straight away on creation of any classes
    // which extend the Base
    this.validate();
  }

  /**
   * Gets the value
   */
  public abstract get value(): TValue;

  /**
   * Validate the class
   */
  protected abstract validate(): void;

  /**
   * Sets the value
   */
  protected abstract setValue(value: TSetValue): void;

  /**
   * Is equal to
   * @param other The other class
   */
  public abstract equals(other: TClass): boolean;

  /**
   * Value not set error
   */
  protected valueNotSetError(): Error {
    return Errors.runtimeErrorWithLogger(this._log, 'Value set is undefined');
  }
}

Now as you use TS you start forgetting to check certain details and this brings in this approach.

Simple POC for subgraph design

Background: We are using subgraphs for different things. We use it for different versions. Some issues

  • Data lives in the graph and we dont have direct access to it. On graph you dont have a way to query the last 60 days. There are limits on how you can query. Before we were building services to index the graph data.
  • The graph is slow to fetch data. Ampl is broken because we dont track rebase events.
    If we now fix this there is no testing framework. Thus we need to resync the graph which takes a month. Basically doesnt scale with the data and users we have.

Achieve: How can we replace this service?
Alternatives: Bitquery?

Tx-Builder Initializer

Feature Tx-Builder

Port and adapt the tx-builder initializations to be used with different importable modules:

  • lendingPool
  • Governance
  • Staking
  • Data

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.