diff --git a/.vscode/launch.json b/.vscode/launch.json index aeaba250c..126386072 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -229,6 +229,7 @@ "redacted", "reflexer", "reserve", + "retro", "ribbon-finance", "rocket-pool", "rook", diff --git a/src/adapters/index.ts b/src/adapters/index.ts index cb66db44a..cdebc8582 100644 --- a/src/adapters/index.ts +++ b/src/adapters/index.ts @@ -158,6 +158,7 @@ import railgun from '@adapters/railgun' import redacted from '@adapters/redacted' import reflexer from '@adapters/reflexer' import reserve from '@adapters/reserve' +import retro from '@adapters/retro' import ribbonFinance from '@adapters/ribbon-finance' import rocketPool from '@adapters/rocket-pool' import rook from '@adapters/rook' @@ -395,6 +396,7 @@ export const adapters: Adapter[] = [ redacted, reflexer, reserve, + retro, ribbonFinance, rocketPool, rook, diff --git a/src/adapters/retro/index.ts b/src/adapters/retro/index.ts new file mode 100644 index 000000000..de576ddf7 --- /dev/null +++ b/src/adapters/retro/index.ts @@ -0,0 +1,10 @@ +import type { Adapter } from '@lib/adapter' + +import * as polygon from './polygon' + +const adapter: Adapter = { + id: 'retro', + polygon, +} + +export default adapter diff --git a/src/adapters/retro/polygon/balance.ts b/src/adapters/retro/polygon/balance.ts new file mode 100644 index 000000000..9422b6c2b --- /dev/null +++ b/src/adapters/retro/polygon/balance.ts @@ -0,0 +1,109 @@ +import { getPoolsBalances } from '@adapters/uniswap-v3/common/pools' +import type { Balance, BalancesContext, Contract, FarmBalance } from '@lib/adapter' +import { mapSuccessFilter } from '@lib/array' +import { multicall } from '@lib/multicall' +import type { Token } from '@lib/token' + +const abi = { + calculateRedeemOutputs: { + inputs: [{ internalType: 'uint256', name: '_amount', type: 'uint256' }], + name: 'calculateRedeemOutputs', + outputs: [{ internalType: 'uint256[]', name: '', type: 'uint256[]' }], + stateMutability: 'view', + type: 'function', + }, +} as const + +const CASH: Token = { + chain: 'polygon', + address: '0x5d066d022ede10efa2717ed3d79f22f949f8c175', + symbol: 'CASH', + decimals: 18, +} + +const redeemableContract: Contract = { + chain: 'polygon', + address: '0x2d62f6d8288994c7900e9c359f8a72e84d17bfba', +} + +const DAI: Token = { + chain: 'polygon', + address: '0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063', + decimals: 18, + symbol: 'DAI', +} + +const USDT: Token = { + chain: 'polygon', + address: '0xc2132D05D31c914a87C6611C10748AEb04B58e8F', + decimals: 6, + symbol: 'USDT', +} + +const USDC: Token = { + chain: 'polygon', + address: '0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174', + decimals: 6, + symbol: 'USDC', +} + +interface CashBalances extends FarmBalance { + cashAmount?: bigint +} + +export async function getRetroBalances( + ctx: BalancesContext, + nonFungiblePositionManager: Contract, + factory: Contract, +): Promise { + const balances: Balance[] = await getPoolsBalances(ctx, nonFungiblePositionManager, factory) + const fmtBalances: CashBalances[] = [] + + let i = 0 + while (i < balances.length) { + const balance = balances[i] + + if (balance.underlyings && balance.underlyings.length > 0) { + for (const underlying of balance.underlyings) { + if (underlying.address.toLowerCase() === CASH.address) { + fmtBalances.push({ ...(balance as CashBalances), cashAmount: (underlying as Contract).amount }) + balances.splice(i, 1) + i-- + break + } + } + } + i++ + } + + const underlyingsBalancesRes = await multicall({ + ctx, + calls: fmtBalances.map( + (balance) => ({ target: redeemableContract.address, params: [balance.cashAmount!] }) as const, + ), + abi: abi.calculateRedeemOutputs, + }) + + const fmtUnderlyings = (output: readonly bigint[]) => { + const tokens = [DAI, USDT, USDC] + + return output.map((amount, index) => ({ + ...tokens[index], + amount, + })) + } + + const fmtCash: Contract[][] = mapSuccessFilter(underlyingsBalancesRes, (res) => fmtUnderlyings(res.output)) + + for (const [index, fmtBalance] of fmtBalances.entries()) { + const fmtUnderlyingsWithoutCash = fmtBalance.underlyings?.filter( + (underlying) => underlying.address.toLowerCase() !== CASH.address, + ) + + if (fmtUnderlyingsWithoutCash) { + fmtBalance.underlyings = [...fmtUnderlyingsWithoutCash, ...fmtCash[index]] + } + } + + return [...balances, ...fmtBalances] +} diff --git a/src/adapters/retro/polygon/index.ts b/src/adapters/retro/polygon/index.ts new file mode 100644 index 000000000..c7933a536 --- /dev/null +++ b/src/adapters/retro/polygon/index.ts @@ -0,0 +1,29 @@ +import { getRetroBalances } from '@adapters/retro/polygon/balance' +import type { Contract, GetBalancesHandler } from '@lib/adapter' +import { resolveBalances } from '@lib/balance' + +export const factory: Contract = { + chain: 'ethereum', + address: '0x91e1b99072f238352f59e58de875691e20dc19c1', +} + +export const nonFungiblePositionManager: Contract = { + chain: 'ethereum', + address: '0x8aac493fd8c78536ef193882aeffeaa3e0b8b5c5', +} + +export const getContracts = async () => { + return { + contracts: { nonFungiblePositionManager }, + } +} + +export const getBalances: GetBalancesHandler = async (ctx, contracts) => { + const balances = await resolveBalances(ctx, contracts, { + nonFungiblePositionManager: (...args) => getRetroBalances(...args, factory), + }) + + return { + groups: [{ balances }], + } +}