@dojima-wallet/solver-modules
Version:
Modules of different protocols for solvers
1,277 lines (1,102 loc) • 39.3 kB
text/typescript
import {
Pool,
InterestRate,
EthereumTransactionTypeExtended,
UiPoolDataProvider,
UiIncentiveDataProvider,
ChainId,
PoolBundle,
ERC20Service,
ERC20_2612Service,
} from "@aave/contract-helpers";
import { formatReserves, formatUserSummary } from "@aave/math-utils";
import { ethers, BigNumberish } from "ethers";
import dayjs from "dayjs";
import * as markets from "@bgd-labs/aave-address-book";
// import {
// AaveToken, SupplyParams, BorrowParams, RepayParams, WithdrawParams,
// SwapRateParams, SetUsageAsCollateralParams, LiquidationParams,
// SwapCollateralParams, EModeParams, Config
// } from "../types";
export class AaveV3Client {
private provider: ethers.providers.JsonRpcProvider;
private signer: ethers.Wallet;
private chain: string;
private erc20Service: ERC20Service;
private erc2612Service: ERC20_2612Service;
constructor(privateKey: string, chain: string) {
this.chain = chain;
this.provider = new ethers.providers.JsonRpcProvider(this.getRpcUrl());
this.signer = new ethers.Wallet(privateKey, this.provider);
// Initialize ERC20 services
this.erc20Service = new ERC20Service(this.provider);
this.erc2612Service = new ERC20_2612Service(this.provider);
}
getChainId(): ChainId {
switch (this.chain) {
case "mainnet":
return ChainId.mainnet;
case "sepolia":
return ChainId.sepolia;
case "optimismSepolia":
return ChainId.optimism_sepolia;
// case 'baseSepolia':
// return ChainId.base_sepolia;
case "arbitrumSepolia":
return ChainId.arbitrum_sepolia;
default:
throw new Error(`Unsupported chain: ${this.chain}`);
}
}
getRpcUrl(): string {
switch (this.chain) {
case "mainnet":
return "https://eth-mainnet.public.blastapi.io";
case "sepolia":
return "https://ethereum-sepolia-rpc.publicnode.com";
case "optimismSepolia":
return "https://sepolia.optimism.io";
// case 'baseSepolia':
// return 'https://base-sepolia-rpc.publicnode.com';
case "arbitrumSepolia":
// return 'https://arbitrum-sepolia-rpc.publicnode.com';
// return 'https://arbitrum-sepolia.drpc.org';
return "https://arbitrum-sepolia.gateway.tenderly.co";
default:
throw new Error(`RPC URL not configured for chain: ${this.chain}`);
}
}
getMarketAddresses() {
switch (this.chain) {
case "mainnet":
return markets.AaveV3Ethereum;
case "sepolia":
return markets.AaveV3Sepolia;
case "optimismSepolia":
return markets.AaveV3OptimismSepolia;
// case 'baseSepolia':
// return markets.AaveV3BaseSepolia;
case "arbitrumSepolia":
return markets.AaveV3ArbitrumSepolia;
default:
throw new Error(`Market addresses not found for chain: ${this.chain}`);
}
}
// Methods that use contracts directly without storing them as class properties
async getFormattedReserves() {
const marketAddresses = this.getMarketAddresses();
const poolDataProvider = new UiPoolDataProvider({
uiPoolDataProviderAddress: marketAddresses.UI_POOL_DATA_PROVIDER,
provider: this.provider,
chainId: this.getChainId(),
});
// console.log("marketAddresses.UI_POOL_DATA_PROVIDER: ", marketAddresses.UI_POOL_DATA_PROVIDER);
// console.log("marketAddresses: ", marketAddresses);
// console.log("--------------\n------------------");
// console.log("poolDataProvider: ", poolDataProvider);
const reserves = await poolDataProvider.getReservesHumanized({
lendingPoolAddressProvider: marketAddresses.POOL_ADDRESSES_PROVIDER,
});
// console.log("reserves: ",reserves);
// No need to convert to string as the values are already humanized
const reservesArray = reserves.reservesData;
const baseCurrencyData = reserves.baseCurrencyData;
const currentTimestamp = dayjs().unix();
const formattedPoolReserves = formatReserves({
reserves: reservesArray,
currentTimestamp,
marketReferenceCurrencyDecimals:
baseCurrencyData.marketReferenceCurrencyDecimals,
marketReferencePriceInUsd:
baseCurrencyData.marketReferenceCurrencyPriceInUsd,
});
return formattedPoolReserves;
}
// get user summary like total collateral, debt, health factor etc.
async getFormattedUserSummary(userAddress: string) {
const marketAddresses = this.getMarketAddresses();
const poolDataProvider = new UiPoolDataProvider({
uiPoolDataProviderAddress: marketAddresses.UI_POOL_DATA_PROVIDER,
provider: this.provider,
chainId: this.getChainId(),
});
try {
const { reservesData, baseCurrencyData } =
await poolDataProvider.getReservesHumanized({
lendingPoolAddressProvider: marketAddresses.POOL_ADDRESSES_PROVIDER,
});
const userReserves = await poolDataProvider.getUserReservesHumanized({
lendingPoolAddressProvider: marketAddresses.POOL_ADDRESSES_PROVIDER,
user: userAddress,
});
// Convert BigNumber values to strings before formatting
const safeReservesData = reservesData.map((reserve) => ({
...reserve,
liquidityRate: reserve.liquidityRate.toString(),
variableBorrowRate: reserve.variableBorrowRate.toString(),
// Remove stableBorrowRate as it's not in the type
}));
const currentTimestamp = dayjs().unix();
const formattedReserves = formatReserves({
reserves: safeReservesData,
currentTimestamp,
marketReferenceCurrencyDecimals:
baseCurrencyData.marketReferenceCurrencyDecimals,
marketReferencePriceInUsd:
baseCurrencyData.marketReferenceCurrencyPriceInUsd,
});
return formatUserSummary({
currentTimestamp,
marketReferencePriceInUsd:
baseCurrencyData.marketReferenceCurrencyPriceInUsd,
marketReferenceCurrencyDecimals:
baseCurrencyData.marketReferenceCurrencyDecimals,
userReserves: userReserves.userReserves,
formattedReserves,
userEmodeCategoryId: userReserves.userEmodeCategoryId,
});
} catch (error) {
console.error("Error in getFormattedUserSummary:", error);
throw error;
}
}
async getReserveIncentives() {
const marketAddresses = this.getMarketAddresses();
const incentiveDataProvider = new UiIncentiveDataProvider({
uiIncentiveDataProviderAddress:
marketAddresses.UI_INCENTIVE_DATA_PROVIDER,
provider: this.provider,
chainId: this.getChainId(),
});
return await incentiveDataProvider.getReservesIncentivesDataHumanized({
lendingPoolAddressProvider: marketAddresses.POOL_ADDRESSES_PROVIDER,
});
}
async getUserIncentives(userAddress: string) {
const marketAddresses = this.getMarketAddresses();
const incentiveDataProvider = new UiIncentiveDataProvider({
uiIncentiveDataProviderAddress:
marketAddresses.UI_INCENTIVE_DATA_PROVIDER,
provider: this.provider,
chainId: this.getChainId(),
});
return await incentiveDataProvider.getUserReservesIncentivesDataHumanized({
lendingPoolAddressProvider: marketAddresses.POOL_ADDRESSES_PROVIDER,
user: userAddress,
});
}
async getApprovedAmount(token: string): Promise<ethers.BigNumber> {
try {
const marketAddresses = this.getMarketAddresses();
const poolBundle = new PoolBundle(this.provider, {
POOL: marketAddresses.POOL,
WETH_GATEWAY: marketAddresses.WETH_GATEWAY,
});
const approvedAmount = await poolBundle.supplyTxBuilder.getApprovedAmount(
{
user: this.signer.address,
token,
}
);
return ethers.BigNumber.from(approvedAmount);
} catch (error) {
console.error("Error fetching approved amount", error);
throw error;
}
}
async generateApprovalTx(token: string, amount: string) {
try {
const marketAddresses = this.getMarketAddresses();
const poolBundle = new PoolBundle(this.provider, {
POOL: marketAddresses.POOL,
WETH_GATEWAY: marketAddresses.WETH_GATEWAY,
});
const approvedAmount = poolBundle.supplyTxBuilder.generateTxData({
user: this.signer.address,
reserve: token,
amount,
});
return approvedAmount;
} catch (error) {
console.error("Error generating approval tx", error);
throw error;
}
}
async generateSupplyTx(token: string, amount: string) {
try {
const marketAddresses = this.getMarketAddresses();
const poolBundle = new PoolBundle(this.provider, {
POOL: marketAddresses.POOL,
WETH_GATEWAY: marketAddresses.WETH_GATEWAY,
});
return poolBundle.supplyTxBuilder.generateTxData({
user: this.signer.address,
reserve: token,
amount,
});
} catch (error) {
console.error("Error generating supply tx data", error);
throw error;
}
}
async generateSupplyWithPermitTx(
token: string,
amount: string,
signature: string,
deadline: string
) {
try {
const marketAddresses = this.getMarketAddresses();
const poolBundle = new PoolBundle(this.provider, {
POOL: marketAddresses.POOL,
WETH_GATEWAY: marketAddresses.WETH_GATEWAY,
});
return poolBundle.supplyTxBuilder.generateSignedTxData({
user: this.signer.address,
reserve: token,
amount,
signature,
deadline,
});
} catch (error) {
console.error("Error generating supply with permit tx data", error);
throw error;
}
}
async signERC20Approval(token: string, amount: string, deadline: string) {
try {
const marketAddresses = this.getMarketAddresses();
const pool = new Pool(this.provider, {
POOL: marketAddresses.POOL,
WETH_GATEWAY: marketAddresses.WETH_GATEWAY,
});
const dataToSign: string = await pool.signERC20Approval({
user: this.signer.address,
reserve: token,
amount,
deadline,
});
// console.log("dataToSign: ", dataToSign);
const signature = await this.provider.send("eth_signTypedData_v4", [
this.signer.address,
dataToSign,
]);
// console.log("signature: ", signature);
return signature;
} catch (error) {
console.error("Error generating supply with permit tx data", error);
throw error;
}
}
async generateSupplySignatureRequest(token: string, amount: string) {
try {
const marketAddresses = this.getMarketAddresses();
const spender = marketAddresses.POOL;
const { name } = await this.erc20Service.getTokenData(token);
const { chainId } = await this.provider.getNetwork();
const nonce = await this.erc2612Service.getNonce({
token,
owner: this.signer.address,
});
const deadline = Math.floor(Date.now() / 1000 + 3600).toString();
return {
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: this.signer.address,
spender,
value: amount,
nonce,
deadline,
},
deadline,
};
} catch (error) {
console.error("Error generating signature request", error);
throw error;
}
}
// Update the supplyETHAsCollateral method to use PoolBundle
// async supplyETHAsCollateral(amount: string): Promise<string> {
// try {
// const marketAddresses = this.getMarketAddresses();
// const poolBundle = new PoolBundle(this.provider, {
// POOL: marketAddresses.POOL,
// WETH_GATEWAY: marketAddresses.WETH_GATEWAY
// });
// const txData = await this.generateSupplyTx(
// marketAddresses.WETH_TOKEN,
// amount
// );
// const tx = await this.signer.sendTransaction(txData);
// return tx.hash;
// } catch (error) {
// console.error("Error in supplyETHAsCollateral:", error);
// throw error;
// }
// }
// async enableAssetAsCollateral(assetAddress: string): Promise<string> {
// try {
// const marketAddresses = this.getMarketAddresses();
// const pool = new Pool(this.provider, {
// POOL: marketAddresses.POOL,
// WETH_GATEWAY: marketAddresses.WETH_GATEWAY
// });
// const enableCollateralTxs = await pool.setUsageAsCollateral({
// user: this.signer.address,
// reserve: assetAddress,
// usageAsCollateral: true,
// });
// const tx = await this.signer.sendTransaction(enableCollateralTxs[0]);
// return tx.hash;
// } catch (error) {
// console.error("Error in enableAssetAsCollateral:", error);
// throw error;
// }
// }
// this function is called on clicking Supply button
async supplyBundle(reserve: string, amount: string, onBehalfOf?: string) {
try {
const marketAddresses = this.getMarketAddresses();
const poolBundle = new PoolBundle(this.provider, {
POOL: marketAddresses.POOL,
WETH_GATEWAY: marketAddresses.WETH_GATEWAY,
});
// Check if the reserve is ETH
const wethAddress = marketAddresses.ASSETS.WETH.UNDERLYING;
// console.log("wethAddress: ", wethAddress);
const isETH =
reserve.toLowerCase() === "eth" ||
reserve.toLowerCase() === wethAddress.toLowerCase();
if (isETH) {
// For ETH deposits, use the WETH Gateway's depositETH function
const wethGateway = new ethers.Contract(
marketAddresses.WETH_GATEWAY,
[
"function depositETH(address pool, address onBehalfOf, uint16 referralCode) external payable",
],
this.signer
);
const tx = await wethGateway.depositETH(
marketAddresses.POOL,
onBehalfOf || this.signer.address,
0, // referralCode
{ value: amount }
);
await tx.wait();
return tx.hash;
}
// Check if approval is needed
const currentApproval =
await poolBundle.supplyTxBuilder.getApprovedAmount({
user: this.signer.address,
token: reserve,
});
// console.log("currentApproval: ", currentApproval);
let txHash: string;
// Extract the amount from the approval response
const approvedAmount = currentApproval.amount;
// if (ethers.BigNumber.from(approvedAmount).lt(ethers.BigNumber.from(amount))) {
// // Generate approval transaction using ERC20Service
// const approvalTx = this.erc20Service.approve({
// user: this.signer.address,
// token: reserve,
// spender: marketAddresses.POOL,
// amount,
// });
// // Submit approval transaction
// const approvalResponse = await this.signer.sendTransaction(approvalTx);
// await approvalResponse.wait();
// }
// Generate and submit supply transaction
// arbitrumSepolia this approval is not needed but in sepolia it is needed
if (
ethers.BigNumber.from(approvedAmount).lt(ethers.BigNumber.from(amount))
) {
// Get the ERC-20 contract instance
const tokenContract = new ethers.Contract(
reserve,
[
"function approve(address spender, uint256 amount) public returns (bool)",
],
this.signer
);
// Send the approve transaction
const tx = await tokenContract.approve(marketAddresses.POOL, amount);
// console.log("Approval transaction sent:", tx.hash);
// Wait for the transaction to be mined
await tx.wait();
// console.log("Approval confirmed:", tx.hash);
}
const supplyTx = await poolBundle.supplyTxBuilder.generateTxData({
user: this.signer.address,
reserve,
amount,
onBehalfOf: onBehalfOf || this.signer.address,
});
const tx = await this.signer.sendTransaction(supplyTx);
txHash = tx.hash;
await tx.wait();
return txHash;
} catch (error) {
console.error("Error in supplyBundle:", error);
throw error;
}
}
async repay(
reserve: string,
amount: string,
interestRateMode: InterestRate = InterestRate.Variable,
onBehalfOf?: string
): Promise<string> {
try {
const marketAddresses = this.getMarketAddresses();
// const poolBundle = new PoolBundle(this.provider, {
// POOL: marketAddresses.POOL,
// WETH_GATEWAY: marketAddresses.WETH_GATEWAY
// });
const pool = new Pool(this.provider, {
POOL: marketAddresses.POOL,
WETH_GATEWAY: marketAddresses.WETH_GATEWAY,
});
// // Get token decimals
// const tokenContract = new ethers.Contract(
// reserve,
// ["function decimals() view returns (uint8)"],
// this.provider
// );
// const decimals = await tokenContract.decimals();
// console.log(`Token decimals: ${decimals}`);
// // Convert amount to proper decimals
// const amountWithDecimals = ethers.utils.parseUnits(amount, decimals);
// console.log(`Amount with decimals: ${amountWithDecimals.toString()}`);
// Check if approval is needed
// const currentApproval = await poolBundle.supplyTxBuilder.getApprovedAmount({
// user: this.signer.address,
// token: reserve,
// });
// console.log("currentApproval: ", currentApproval);
// removed the approval code as it is doing approval itself if allowance is not enough
// Extract the amount from the approval response
// const approvedAmount = currentApproval.amount;
// if (ethers.BigNumber.from(approvedAmount).lt(ethers.BigNumber.from(amount))) {
// // Get the ERC-20 contract instance
// const tokenContract = new ethers.Contract(
// reserve,
// ["function approve(address spender, uint256 amount) public returns (bool)"],
// this.signer
// );
// // Send the approve transaction
// const tx = await tokenContract.approve(marketAddresses.POOL, amount);
// console.log("Approval transaction sent:", tx.hash);
// // Wait for the transaction to be mined
// await tx.wait();
// console.log("Approval confirmed:", tx.hash);
// }
const repayTxs: EthereumTransactionTypeExtended[] = await pool.repay({
user: this.signer.address,
reserve,
amount: amount,
interestRateMode,
onBehalfOf: onBehalfOf || this.signer.address,
});
if (!repayTxs?.length) {
throw new Error("No transaction generated for repay");
}
// Process each transaction in sequence (approval if needed, then repay)
let lastTxHash: string;
for (const repayTx of repayTxs) {
const extendedTxData = await repayTx.tx();
const { from, ...txData } = extendedTxData;
const tx = await this.signer.sendTransaction({
...txData,
value: txData.value ? ethers.BigNumber.from(txData.value) : undefined,
});
lastTxHash = tx.hash;
await tx.wait();
}
return lastTxHash!;
} catch (error) {
console.error("Error in repay:", error);
throw error;
}
}
async repayWithAToken(
reserve: string,
amount: string,
interestRateMode: InterestRate = InterestRate.Variable
): Promise<string> {
try {
const marketAddresses = this.getMarketAddresses();
const pool = new Pool(this.provider, {
POOL: marketAddresses.POOL,
WETH_GATEWAY: marketAddresses.WETH_GATEWAY,
});
const repayTxs: EthereumTransactionTypeExtended[] =
await pool.repayWithATokens({
user: this.signer.address,
amount: amount,
reserve,
rateMode: interestRateMode,
});
if (!repayTxs?.length) {
throw new Error("No transaction generated for repay");
}
// Process each transaction in sequence (approval if needed, then repay)
let lastTxHash: string;
for (const repayTx of repayTxs) {
const extendedTxData = await repayTx.tx();
const { from, ...txData } = extendedTxData;
const tx = await this.signer.sendTransaction({
...txData,
value: txData.value ? ethers.BigNumber.from(txData.value) : undefined,
});
lastTxHash = tx.hash;
await tx.wait();
}
return lastTxHash!;
} catch (error) {
console.error("Error in repay:", error);
throw error;
}
}
async borrow(
reserve: string,
amount: string,
interestRateMode: InterestRate = InterestRate.Variable,
onBehalfOf?: string
): Promise<string> {
try {
const marketAddresses = this.getMarketAddresses();
const pool = new Pool(this.provider, {
POOL: marketAddresses.POOL,
WETH_GATEWAY: marketAddresses.WETH_GATEWAY,
});
// Use the raw amount without decimal conversion
// console.log(`Raw amount: ${amount}`);
const borrowTxs: EthereumTransactionTypeExtended[] = await pool.borrow({
user: this.signer.address,
reserve,
amount, // Use raw amount directly
interestRateMode: InterestRate.Variable,
onBehalfOf: onBehalfOf || this.signer.address,
referralCode: "0",
});
if (!borrowTxs?.length) {
throw new Error("No transaction generated for borrow");
}
// Process each transaction in sequence
let lastTxHash: string;
for (const borrowTx of borrowTxs) {
const extendedTxData = await borrowTx.tx();
const { from, ...txData } = extendedTxData;
const tx = await this.signer.sendTransaction({
...txData,
value: txData.value ? ethers.BigNumber.from(txData.value) : undefined,
});
lastTxHash = tx.hash;
await tx.wait();
}
return lastTxHash!;
} catch (error) {
console.error("Error in borrow:", error);
throw error;
}
}
async withdraw(
reserve: string,
amount: string,
aTokenAddress: string,
onBehalfOf?: string
): Promise<string> {
try {
const marketAddresses = this.getMarketAddresses();
const pool = new Pool(this.provider, {
POOL: marketAddresses.POOL,
WETH_GATEWAY: marketAddresses.WETH_GATEWAY,
});
// Check if withdrawing ETH
const wethAddress = marketAddresses.ASSETS.WETH.UNDERLYING;
const isETH =
reserve.toLowerCase() === "eth" ||
reserve.toLowerCase() === wethAddress.toLowerCase();
if (isETH) {
// For ETH withdrawals, use the WETH Gateway's withdrawETH function
const wethGateway = new ethers.Contract(
marketAddresses.WETH_GATEWAY,
[
"function withdrawETH(address pool, uint256 amount, address to) external",
],
this.signer
);
// console.log("Withdrawing ETH via WETH Gateway");
const tx = await wethGateway.withdrawETH(
marketAddresses.POOL,
amount,
onBehalfOf || this.signer.address,
{ gasLimit: 500000 }
);
await tx.wait();
return tx.hash;
}
const withdrawTxs: EthereumTransactionTypeExtended[] =
await pool.withdraw({
user: this.signer.address,
reserve,
amount,
aTokenAddress,
onBehalfOf: onBehalfOf || this.signer.address,
});
if (!withdrawTxs?.length) {
throw new Error("No transaction generated for withdraw");
}
// Process each transaction in sequence
let lastTxHash: string;
for (const withdrawTx of withdrawTxs) {
const extendedTxData = await withdrawTx.tx();
const { from, ...txData } = extendedTxData;
const tx = await this.signer.sendTransaction({
...txData,
value: txData.value ? ethers.BigNumber.from(txData.value) : undefined,
});
lastTxHash = tx.hash;
await tx.wait();
}
return lastTxHash!;
} catch (error) {
console.error("Error in withdraw:", error);
throw error;
}
}
async swapBorrowRateMode(
reserve: string,
interestRateMode: InterestRate
): Promise<string> {
try {
const marketAddresses = this.getMarketAddresses();
const pool = new Pool(this.provider, {
POOL: marketAddresses.POOL,
WETH_GATEWAY: marketAddresses.WETH_GATEWAY,
});
const swapTxs: EthereumTransactionTypeExtended[] =
await pool.swapBorrowRateMode({
user: this.signer.address,
reserve,
interestRateMode,
});
if (!swapTxs?.length) {
throw new Error("No transaction generated for swap borrow rate mode");
}
// Process each transaction in sequence
let lastTxHash: string;
for (const swapTx of swapTxs) {
const extendedTxData = await swapTx.tx();
const { from, ...txData } = extendedTxData;
const tx = await this.signer.sendTransaction({
...txData,
value: txData.value ? ethers.BigNumber.from(txData.value) : undefined,
});
lastTxHash = tx.hash;
await tx.wait();
}
return lastTxHash!;
} catch (error) {
console.error("Error in swapBorrowRateMode:", error);
throw error;
}
}
async setUsageAsCollateral(
reserve: string,
usageAsCollateral: boolean
): Promise<string> {
try {
const marketAddresses = this.getMarketAddresses();
const pool = new Pool(this.provider, {
POOL: marketAddresses.POOL,
WETH_GATEWAY: marketAddresses.WETH_GATEWAY,
});
const collateralTxs: EthereumTransactionTypeExtended[] =
await pool.setUsageAsCollateral({
user: this.signer.address,
reserve,
usageAsCollateral,
});
if (!collateralTxs?.length) {
throw new Error(
"No transaction generated for setting collateral usage"
);
}
// Process each transaction in sequence
let lastTxHash: string;
for (const collateralTx of collateralTxs) {
const extendedTxData = await collateralTx.tx();
const { from, ...txData } = extendedTxData;
const tx = await this.signer.sendTransaction({
...txData,
value: txData.value ? ethers.BigNumber.from(txData.value) : undefined,
});
lastTxHash = tx.hash;
await tx.wait();
}
return lastTxHash!;
} catch (error) {
console.error("Error in setUsageAsCollateral:", error);
throw error;
}
}
async liquidationCall(
liquidatedUser: string,
debtReserve: string,
collateralReserve: string,
purchaseAmount: string,
getAToken: boolean = false
): Promise<string> {
try {
const marketAddresses = this.getMarketAddresses();
const pool = new Pool(this.provider, {
POOL: marketAddresses.POOL,
WETH_GATEWAY: marketAddresses.WETH_GATEWAY,
});
const liquidationTxs: EthereumTransactionTypeExtended[] =
await pool.liquidationCall({
liquidator: this.signer.address,
liquidatedUser,
debtReserve,
collateralReserve,
purchaseAmount,
getAToken,
});
if (!liquidationTxs?.length) {
throw new Error("No transaction generated for liquidation call");
}
// Process each transaction in sequence
let lastTxHash: string;
for (const liquidationTx of liquidationTxs) {
const extendedTxData = await liquidationTx.tx();
const { from, ...txData } = extendedTxData;
const tx = await this.signer.sendTransaction({
...txData,
value: txData.value ? ethers.BigNumber.from(txData.value) : undefined,
});
lastTxHash = tx.hash;
await tx.wait();
}
return lastTxHash!;
} catch (error) {
console.error("Error in liquidationCall:", error);
throw error;
}
}
async supplyWithPermit(
reserve: string,
amount: string,
signature: string,
onBehalfOf?: string
): Promise<string> {
try {
const marketAddresses = this.getMarketAddresses();
const pool = new Pool(this.provider, {
POOL: marketAddresses.POOL,
WETH_GATEWAY: marketAddresses.WETH_GATEWAY,
});
const deadline = Math.floor(Date.now() / 1000 + 3600).toString(); // 1 hour from now
const supplyTxs: EthereumTransactionTypeExtended[] =
await pool.supplyWithPermit({
user: this.signer.address,
reserve,
amount,
signature,
deadline,
onBehalfOf: onBehalfOf || this.signer.address,
});
if (!supplyTxs?.length) {
throw new Error("No transaction generated for supply with permit");
}
// Process each transaction in sequence
let lastTxHash: string;
for (const supplyTx of supplyTxs) {
const extendedTxData = await supplyTx.tx();
const { from, ...txData } = extendedTxData;
const tx = await this.signer.sendTransaction({
...txData,
value: txData.value ? ethers.BigNumber.from(txData.value) : undefined,
});
lastTxHash = tx.hash;
await tx.wait();
}
return lastTxHash!;
} catch (error) {
console.error("Error in supplyWithPermit:", error);
throw error;
}
}
async repayWithPermit(
reserve: string,
amount: string,
interestRateMode: InterestRate,
signature: string,
onBehalfOf?: string
): Promise<string> {
try {
const marketAddresses = this.getMarketAddresses();
const pool = new Pool(this.provider, {
POOL: marketAddresses.POOL,
WETH_GATEWAY: marketAddresses.WETH_GATEWAY,
});
const deadline = Math.floor(Date.now() / 1000 + 3600).toString(); // 1 hour from now
const repayTxs: EthereumTransactionTypeExtended[] =
await pool.repayWithPermit({
user: this.signer.address,
reserve,
amount,
interestRateMode,
signature,
deadline,
onBehalfOf: onBehalfOf || this.signer.address,
});
if (!repayTxs?.length) {
throw new Error("No transaction generated for repay with permit");
}
// Process each transaction in sequence
let lastTxHash: string;
for (const repayTx of repayTxs) {
const extendedTxData = await repayTx.tx();
const { from, ...txData } = extendedTxData;
const tx = await this.signer.sendTransaction({
...txData,
value: txData.value ? ethers.BigNumber.from(txData.value) : undefined,
});
lastTxHash = tx.hash;
await tx.wait();
}
return lastTxHash!;
} catch (error) {
console.error("Error in repayWithPermit:", error);
throw error;
}
}
// only works for Ethereum mainnet
// async swapCollateral(
// fromAsset: string,
// fromAToken: string,
// toAsset: string,
// fromAmount: string,
// minToAmount: string,
// swapAll: boolean = false,
// flash: boolean = false,
// referralCode: string = '0',
// augustus: string,
// swapCallData: string
// ): Promise<string> {
// try {
// const marketAddresses = this.getMarketAddresses();
// const pool = new Pool(this.provider, {
// POOL: marketAddresses.POOL,
// SWAP_COLLATERAL_ADAPTER: marketAddresses.SWAP_COLLATERAL_ADAPTER,
// WETH_GATEWAY: marketAddresses.WETH_GATEWAY
// });
// const swapTxs: EthereumTransactionTypeExtended[] = await pool.swapCollateral({
// user: this.signer.address,
// flash,
// fromAsset,
// fromAToken,
// toAsset,
// fromAmount,
// minToAmount,
// // permitSignature: '', // Not needed at the moment
// swapAll,
// referralCode,
// augustus,
// swapCallData
// });
// if (!swapTxs?.length) {
// throw new Error('No transaction generated for swap collateral');
// }
// // Process each transaction in sequence
// let lastTxHash: string;
// for (const swapTx of swapTxs) {
// const extendedTxData = await swapTx.tx();
// const { from, ...txData } = extendedTxData;
// const tx = await this.signer.sendTransaction({
// ...txData,
// value: txData.value ? ethers.BigNumber.from(txData.value) : undefined,
// });
// lastTxHash = tx.hash;
// await tx.wait();
// }
// return lastTxHash!;
// } catch (error) {
// console.error("Error in swapCollateral:", error);
// throw error;
// }
// }
async setUserEMode(categoryId: number): Promise<string> {
try {
const marketAddresses = this.getMarketAddresses();
const pool = new Pool(this.provider, {
POOL: marketAddresses.POOL,
WETH_GATEWAY: marketAddresses.WETH_GATEWAY,
});
const eModeTxs: EthereumTransactionTypeExtended[] =
await pool.setUserEMode({
user: this.signer.address,
categoryId,
});
if (!eModeTxs?.length) {
throw new Error("No transaction generated for setting eMode");
}
// Process each transaction in sequence
let lastTxHash: string;
for (const eModeTx of eModeTxs) {
const extendedTxData = await eModeTx.tx();
const { from, ...txData } = extendedTxData;
const tx = await this.signer.sendTransaction({
...txData,
value: txData.value ? ethers.BigNumber.from(txData.value) : undefined,
});
lastTxHash = tx.hash;
await tx.wait();
}
return lastTxHash!;
} catch (error) {
console.error("Error in setUserEMode:", error);
throw error;
}
}
// async borrowUSDCWithETHCollateral(ethAmount: string, usdcAmount: string) {
// try {
// const marketAddresses = this.getMarketAddresses();
// // Step 1: Supply ETH
// const supplyTx = await this.supplyETHAsCollateral(ethAmount);
// console.log('ETH supplied as collateral:', supplyTx);
// // Step 2: Enable ETH as collateral
// const enableCollateralTx = await this.enableAssetAsCollateral(marketAddresses.WETH_TOKEN);
// console.log('ETH enabled as collateral:', enableCollateralTx);
// // Step 3: Borrow USDC
// const borrowTx = await this.borrowUSDC(usdcAmount);
// console.log('USDC borrowed:', borrowTx);
// return {
// supplyTx,
// enableCollateralTx,
// borrowTx
// };
// } catch (error) {
// console.error('Error in borrowUSDCWithETHCollateral:', error);
// throw error;
// }
// }
// // Helper method to check if asset is already enabled as collateral
// async isAssetEnabledAsCollateral(userAddress: string, assetAddress: string): Promise<boolean> {
// const userConfig = await this.getFormattedUserSummary(userAddress);
// const assetData = userConfig.userReserves.find(
// reserve => reserve.underlyingAsset.toLowerCase() === assetAddress.toLowerCase()
// );
// return assetData?.usageAsCollateralEnabledOnUser ?? false;
// }
async getAllWalletBalances(walletAddress: string) {
try {
// const marketAddresses = this.getMarketAddresses();
const balances: {
[key: string]: { symbol: string; balance: string; address: string };
} = {};
// Get native ETH balance
const ethBalance = await this.provider.getBalance(walletAddress);
balances["ETH"] = {
symbol: "ETH",
balance: ethers.utils.formatEther(ethBalance),
address: "ETH",
};
// Get all reserves data
const reserves = await this.getFormattedReserves();
// For each reserve, get the token balance
for (const reserve of reserves) {
try {
const tokenContract = new ethers.Contract(
reserve.underlyingAsset,
[
"function balanceOf(address) view returns (uint256)",
"function decimals() view returns (uint8)",
"function symbol() view returns (string)",
],
this.provider
);
const balance = await tokenContract.balanceOf(walletAddress);
const decimals = await tokenContract.decimals();
const symbol = await tokenContract.symbol();
// Only add to balances if there is a non-zero balance
if (!balance.isZero()) {
balances[symbol] = {
symbol: symbol,
balance: ethers.utils.formatUnits(balance, decimals),
address: reserve.underlyingAsset,
};
}
} catch (error) {
console.error(
`Error fetching balance for token ${reserve.name}:`,
error
);
// Continue with next token even if one fails
continue;
}
}
return balances;
} catch (error) {
console.error("Error in getAllWalletBalances:", error);
throw error;
}
}
// Add a method to get WETH contract
private async getWethContract() {
const wethAddress = "0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14"; // Sepolia WETH address
return new ethers.Contract(
wethAddress,
[
"function deposit() payable",
"function withdraw(uint256 amount)",
"function balanceOf(address owner) view returns (uint256)",
"function approve(address spender, uint256 amount) returns (bool)",
],
this.signer
);
}
/**
* Wrap ETH into WETH. Emits the WrapEth event when the transaction is prompted.
* @param options Parameters for wrapping ETH
* @returns Promise that resolves when the wrap is complete
*/
public async wrapETH(amountInEth: BigNumberish) {
try {
const wethContract = await this.getWethContract();
const tx = await wethContract.deposit({
value: amountInEth.toString(),
gasLimit: 100000,
});
await tx.wait();
return tx;
} catch (error) {
console.error(`Error in wraping ETH`, error);
throw error;
}
}
}