import { Signature, splitSignature } from '@ethersproject/bytes'; import { DeploylessViewerClient } from 'deployless-view'; import { BigNumber, BigNumberish, BytesLike, constants, ethers, providers, utils, } from 'ethers'; import BaseService from '../commons/BaseService'; import { eEthereumTxType, EthereumTransactionTypeExtended, ProtocolAction, tEthereumAddress, transactionType, } from '../commons/types'; import { API_ETH_MOCK_ADDRESS, DEFAULT_APPROVE_AMOUNT, getTxValue, MarketplaceIdMap, valueToWei, } from '../commons/utils'; import { LPValidatorV3 } from '../commons/validators/methodValidators'; import { is0OrPositiveAmount, isEthAddress, isEthAddressArray, isPositiveAmount, isPositiveOrMinusOneAmount, isValidTokenIdArray, } from '../commons/validators/paramValidators'; import { ERC20_2612Interface, ERC20_2612Service } from '../erc20-2612'; import { ERC20Service, IERC20ServiceInterface } from '../erc20-contract'; import { ERC721Service, IERC721ServiceInterface } from '../erc721-contract'; import { WETHGatewayInterface, WETHGatewayService, } from '../wethgateway-contract'; import { WPunkGatewayInterface, WPunkGatewayService, } from '../wpunkgateway-contract'; import { LPAcceptBidWithCredit, LPAuction, LPBatchBuyWithCredit, LPBorrowParamsType, LPBuyWithCredit, LPERC721LiquidationCall, LPFlashClaim, LPLiquidationCall, LPLiquidationERC721, LPRepayParamsType, LPRepayWithPermitParamsType, LPRepayWithPTokensType, LPSetERC721UsageAsCollateral, LPSetUsageAsCollateral, LPSignERC20ApprovalType, LPSupplyERC721ParamsType, LPSupplyParamsType, LPSupplyWithPermitType, LPWithdrawERC721ParamsType, LPWithdrawParamsType, } from './lendingPoolTypes'; import { ApeCoinStaking, DataTypes, IPool } from './typechain/IPool'; import { IPool__factory } from './typechain/IPool__factory'; import { Seaport } from './typechain/Seaport'; import { SeaportFactory } from './typechain/SeaportFactory'; import ApeCompoundStrategyStruct = DataTypes.ApeCompoundStrategyStruct; const APE_COIN_DECIMAL = 18; export type BatchEventResultMap = Record< number, { fulfilled: boolean; rejected: boolean } >; export type BatchEventResultCallback = ( resultMap: BatchEventResultMap, finished: boolean, ) => void; export interface PoolInterface { supply: ( args: LPSupplyParamsType, ) => Promise; supplyERC721: ( args: LPSupplyERC721ParamsType, ) => Promise; signERC20Approval: (args: LPSignERC20ApprovalType) => Promise; supplyWithPermit: ( args: LPSupplyWithPermitType, ) => Promise; withdraw: ( args: LPWithdrawParamsType, ) => Promise; withdrawERC721: ( args: LPWithdrawERC721ParamsType, ) => Promise; borrow: ( args: LPBorrowParamsType, ) => Promise; repay: ( args: LPRepayParamsType, ) => Promise; repayWithPermit: ( args: LPRepayWithPermitParamsType, ) => Promise; setUsageAsCollateral: ( args: LPSetUsageAsCollateral, ) => Promise; setUserUseERC721AsCollateral: ( args: LPSetERC721UsageAsCollateral, ) => Promise; flashClaim: ( args: LPFlashClaim, ) => Promise; buyWithCredit: ( args: LPBuyWithCredit, ) => Promise; batchBuyWithCredit: ( args: LPBatchBuyWithCredit, ) => Promise; startAuction: (args: LPAuction) => Promise; endAuction: (args: LPAuction) => Promise; liquidationERC721: ( args: LPLiquidationERC721, ) => Promise; } export type LendingPoolMarketConfigV3 = { POOL: tEthereumAddress; WETH_GATEWAY?: tEthereumAddress; CrytoPunkAddress?: tEthereumAddress; WpunkGatewayAddress?: tEthereumAddress; }; export type FulfillmentComponent = { orderIndex: number; itemIndex: number; }; export type Fulfillment = { offerComponents: FulfillmentComponent[]; considerationComponents: FulfillmentComponent[]; }; export class Pool extends BaseService implements PoolInterface { readonly erc20Service: IERC20ServiceInterface; readonly erc721Service: IERC721ServiceInterface; readonly poolAddress: string; readonly crytoPunkAddress: string; readonly wethGatewayService: WETHGatewayInterface; readonly wpunkGatewayService: WPunkGatewayInterface; readonly erc20_2612Service: ERC20_2612Interface; constructor( provider: providers.Provider, lendingPoolConfig?: LendingPoolMarketConfigV3, ) { super(provider, IPool__factory); const { POOL, WETH_GATEWAY, WpunkGatewayAddress, CrytoPunkAddress } = lendingPoolConfig ?? {}; this.poolAddress = POOL ?? ''; this.crytoPunkAddress = CrytoPunkAddress ?? ''; // initialize services this.erc20_2612Service = new ERC20_2612Service(provider); this.erc20Service = new ERC20Service(provider); this.erc721Service = new ERC721Service(provider); this.wethGatewayService = new WETHGatewayService(provider, WETH_GATEWAY); this.wpunkGatewayService = new WPunkGatewayService( provider, WpunkGatewayAddress, CrytoPunkAddress, ); } @LPValidatorV3 public async supply( @isEthAddress('user') @isEthAddress('reserve') @isPositiveAmount('amount') @isEthAddress('onBehalfOf') { user, reserve, amount, onBehalfOf, referralCode }: LPSupplyParamsType, ): Promise { if (reserve.toLowerCase() === API_ETH_MOCK_ADDRESS.toLowerCase()) { return this.wethGatewayService.depositETH({ user, amount, onBehalfOf, referralCode, }); } const { isApproved, approve, decimalsOf }: IERC20ServiceInterface = this.erc20Service; const txs: EthereumTransactionTypeExtended[] = []; const reserveDecimals: number = await decimalsOf(reserve); const convertedAmount: string = valueToWei(amount, reserveDecimals); const approved = await isApproved({ token: reserve, user, spender: this.poolAddress, amount, }); if (!approved) { const approveTx: EthereumTransactionTypeExtended = approve({ user, token: reserve, spender: this.poolAddress, amount: DEFAULT_APPROVE_AMOUNT, }); txs.push(approveTx); } const lendingPoolContract: IPool = this.getContractInstance( this.poolAddress, ); const txCallback: () => Promise = this.generateTxCallback({ rawTxMethod: async () => lendingPoolContract.populateTransaction.supply( reserve, convertedAmount, onBehalfOf ?? user, referralCode ?? '0', ), from: user, value: getTxValue(reserve, convertedAmount), }); txs.push({ tx: txCallback, txType: eEthereumTxType.DLP_ACTION, gas: this.generateTxPriceEstimation( txs, txCallback, ProtocolAction.supply, ), }); return txs; } @LPValidatorV3 public async supplyERC721( @isEthAddress('user') @isEthAddress('reserve') @isEthAddress('onBehalfOf') @isValidTokenIdArray('token_ids') { user, reserve, token_ids, onBehalfOf, referralCode, }: LPSupplyERC721ParamsType, ): Promise { const txs: EthereumTransactionTypeExtended[] = []; const { isApprovedForAll, setApprovalForAll }: IERC721ServiceInterface = this.erc721Service; const allApproved = await isApprovedForAll({ token: reserve, user, spender: this.poolAddress, token_ids, }); if (!allApproved) { const approveTx: EthereumTransactionTypeExtended = setApprovalForAll({ token: reserve, user, spender: this.poolAddress, }); txs.push(approveTx); } const lendingPoolContract: IPool = this.getContractInstance( this.poolAddress, ); const params: Array<{ tokenId: BigNumberish; useAsCollateral: boolean }> = []; for (const value of token_ids) { params.push({ tokenId: value, useAsCollateral: true, }); } const txCallback: () => Promise = this.generateTxCallback({ rawTxMethod: async () => lendingPoolContract.populateTransaction.supplyERC721( reserve, params, onBehalfOf ?? user, referralCode ?? '0', ), from: user, }); txs.push({ tx: txCallback, txType: eEthereumTxType.DLP_ACTION, gas: this.generateTxPriceEstimation( txs, txCallback, ProtocolAction.supplyErc721, ), }); return txs; } // Sign permit supply @LPValidatorV3 public async signERC20Approval( @isEthAddress('user') @isEthAddress('reserve') @isPositiveOrMinusOneAmount('amount') { user, reserve, amount, deadline }: LPSignERC20ApprovalType, ): Promise { const { getTokenData, isApproved } = this.erc20Service; const { name, decimals } = await getTokenData(reserve); const convertedAmount = amount === '-1' ? constants.MaxUint256.toString() : valueToWei(amount, decimals); const approved = await isApproved({ token: reserve, user, spender: this.poolAddress, amount, }); if (approved) { return ''; } const { chainId } = await this.provider.getNetwork(); const nonce = await this.erc20_2612Service.getNonce({ token: reserve, owner: user, }); if (nonce === null) { return ''; } const version = await this.erc20_2612Service.getVersion({ token: reserve, }); if (version === null) { return ''; } 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, chainId, verifyingContract: reserve, }, message: { owner: user, spender: this.poolAddress, value: convertedAmount, nonce, deadline, }, }; return JSON.stringify(typeData); } @LPValidatorV3 public async supplyWithPermit( @isEthAddress('user') @isEthAddress('reserve') @isEthAddress('onBehalfOf') @isPositiveAmount('amount') @isPositiveAmount('referralCode') { user, reserve, onBehalfOf, amount, referralCode, signature, deadline, }: LPSupplyWithPermitType, ): Promise { const txs: EthereumTransactionTypeExtended[] = []; const { decimalsOf } = this.erc20Service; const poolContract: IPool = this.getContractInstance(this.poolAddress); const stakedTokenDecimals: number = await decimalsOf(reserve); const convertedAmount: string = valueToWei(amount, stakedTokenDecimals); // const sig: Signature = utils.splitSignature(signature); const sig: Signature = splitSignature(signature); const txCallback: () => Promise = this.generateTxCallback({ rawTxMethod: async () => poolContract.populateTransaction.supplyWithPermit( reserve, convertedAmount, onBehalfOf ?? user, referralCode ?? 0, deadline, sig.v, sig.r, sig.s, ), from: user, }); txs.push({ tx: txCallback, txType: eEthereumTxType.DLP_ACTION, gas: this.generateTxPriceEstimation(txs, txCallback), }); return txs; } @LPValidatorV3 public async withdraw( @isEthAddress('user') @isEthAddress('reserve') @isPositiveOrMinusOneAmount('amount') @isEthAddress('onBehalfOf') @isEthAddress('aTokenAddress') { user, reserve, amount, onBehalfOf, aTokenAddress }: LPWithdrawParamsType, ): Promise { if (reserve.toLowerCase() === API_ETH_MOCK_ADDRESS.toLowerCase()) { if (!aTokenAddress) { throw new Error( 'To withdraw ETH you need to pass the aWETH token address', ); } return this.wethGatewayService.withdrawETH({ user, amount, onBehalfOf, aTokenAddress, }); } const { decimalsOf }: IERC20ServiceInterface = this.erc20Service; const decimals: number = await decimalsOf(reserve); const convertedAmount: string = amount === '-1' ? constants.MaxUint256.toString() : valueToWei(amount, decimals); const poolContract: IPool = this.getContractInstance(this.poolAddress); const txCallback: () => Promise = this.generateTxCallback({ rawTxMethod: async () => poolContract.populateTransaction.withdraw( reserve, convertedAmount, onBehalfOf ?? user, ), from: user, action: ProtocolAction.withdraw, }); return [ { tx: txCallback, txType: eEthereumTxType.DLP_ACTION, gas: this.generateTxPriceEstimation( [], txCallback, ProtocolAction.withdraw, ), }, ]; } @LPValidatorV3 public async withdrawERC721( @isEthAddress('user') @isEthAddress('reserve') @isValidTokenIdArray('token_ids') @isEthAddress('onBehalfOf') @isEthAddress('xTokenAddress') { user, reserve, token_ids, onBehalfOf, xTokenAddress, }: LPWithdrawERC721ParamsType, ): Promise { if (reserve.toLowerCase() === this.crytoPunkAddress.toLowerCase()) { if (!xTokenAddress) { throw new Error( 'To withdraw CryptoPunk you need to pass the xCryptoPunk token address', ); } return this.wpunkGatewayService.withdrawPunk({ user, token_ids, xpunkAddress: xTokenAddress, onBehalfOf, }); } const poolContract: IPool = this.getContractInstance(this.poolAddress); const txCallback: () => Promise = this.generateTxCallback({ rawTxMethod: async () => poolContract.populateTransaction.withdrawERC721( reserve, token_ids, onBehalfOf ?? user, ), from: user, action: ProtocolAction.withdrawErc721, }); return [ { tx: txCallback, txType: eEthereumTxType.DLP_ACTION, gas: this.generateTxPriceEstimation( [], txCallback, ProtocolAction.withdraw, ), }, ]; } @LPValidatorV3 public async borrow( @isEthAddress('user') @isEthAddress('reserve') @isPositiveAmount('amount') @isEthAddress('debtTokenAddress') @isEthAddress('onBehalfOf') { user, reserve, amount, debtTokenAddress, onBehalfOf, referralCode, }: LPBorrowParamsType, ): Promise { if (reserve.toLowerCase() === API_ETH_MOCK_ADDRESS.toLowerCase()) { if (!debtTokenAddress) { throw new Error( `To borrow ETH you need to pass the stable or variable WETH debt Token Address corresponding the interestRateMode`, ); } return this.wethGatewayService.borrowETH({ user, amount, debtTokenAddress, referralCode, }); } const { decimalsOf }: IERC20ServiceInterface = this.erc20Service; const reserveDecimals = await decimalsOf(reserve); const formatAmount: string = valueToWei(amount, reserveDecimals); const poolContract = this.getContractInstance(this.poolAddress); const txCallback: () => Promise = this.generateTxCallback({ rawTxMethod: async () => poolContract.populateTransaction.borrow( reserve, formatAmount, referralCode ?? 0, onBehalfOf ?? user, ), from: user, }); return [ { tx: txCallback, txType: eEthereumTxType.DLP_ACTION, gas: this.generateTxPriceEstimation([], txCallback), }, ]; } @LPValidatorV3 public async repay( @isEthAddress('user') @isEthAddress('reserve') @isPositiveOrMinusOneAmount('amount') @isEthAddress('onBehalfOf') { user, reserve, amount, onBehalfOf }: LPRepayParamsType, ): Promise { if (reserve.toLowerCase() === API_ETH_MOCK_ADDRESS.toLowerCase()) { return this.wethGatewayService.repayETH({ user, amount, onBehalfOf, }); } const txs: EthereumTransactionTypeExtended[] = []; const { decimalsOf }: IERC20ServiceInterface = this.erc20Service; const poolContract = this.getContractInstance(this.poolAddress); const { populateTransaction }: IPool = poolContract; const decimals: number = await decimalsOf(reserve); const convertedAmount: string = amount === '-1' ? constants.MaxUint256.toString() : valueToWei(amount, decimals); const txCallback: () => Promise = this.generateTxCallback({ rawTxMethod: async () => populateTransaction.repay(reserve, convertedAmount, onBehalfOf ?? user), from: user, value: getTxValue(reserve, convertedAmount), }); txs.push({ tx: txCallback, txType: eEthereumTxType.DLP_ACTION, gas: this.generateTxPriceEstimation( txs, txCallback, ProtocolAction.repay, ), }); return txs; } @LPValidatorV3 public async repayWithPTokens( @isEthAddress('user') @isEthAddress('reserve') @isPositiveOrMinusOneAmount('amount') { user, amount, reserve }: LPRepayWithPTokensType, ): Promise { if (reserve.toLowerCase() === API_ETH_MOCK_ADDRESS.toLowerCase()) { throw new Error( 'Can not repay with aTokens with eth. Should be WETH instead', ); } const txs: EthereumTransactionTypeExtended[] = []; const { decimalsOf }: IERC20ServiceInterface = this.erc20Service; const poolContract = this.getContractInstance(this.poolAddress); const { populateTransaction }: IPool = poolContract; const decimals: number = await decimalsOf(reserve); const convertedAmount: string = amount === '-1' ? constants.MaxUint256.toString() : valueToWei(amount, decimals); const txCallback: () => Promise = this.generateTxCallback({ rawTxMethod: async () => populateTransaction.repayWithPTokens(reserve, convertedAmount), from: user, value: getTxValue(reserve, convertedAmount), }); txs.push({ tx: txCallback, txType: eEthereumTxType.DLP_ACTION, gas: this.generateTxPriceEstimation( txs, txCallback, ProtocolAction.repay, ), }); return txs; } @LPValidatorV3 public async repayWithPermit( @isEthAddress('user') @isEthAddress('reserve') @isPositiveOrMinusOneAmount('amount') @isEthAddress('onBehalfOf') { user, reserve, amount, onBehalfOf, signature, deadline, }: LPRepayWithPermitParamsType, ): Promise { const txs: EthereumTransactionTypeExtended[] = []; const { decimalsOf }: IERC20ServiceInterface = this.erc20Service; const poolContract = this.getContractInstance(this.poolAddress); const { populateTransaction }: IPool = poolContract; const decimals: number = await decimalsOf(reserve); const sig: Signature = utils.splitSignature(signature); const convertedAmount: string = amount === '-1' ? constants.MaxUint256.toString() : valueToWei(amount, decimals); const txCallback: () => Promise = this.generateTxCallback({ rawTxMethod: async () => populateTransaction.repayWithPermit( reserve, convertedAmount, onBehalfOf ?? user, deadline, sig.v, sig.r, sig.s, ), from: user, value: getTxValue(reserve, convertedAmount), }); txs.push({ tx: txCallback, txType: eEthereumTxType.DLP_ACTION, gas: this.generateTxPriceEstimation( txs, txCallback, ProtocolAction.repay, ), }); return txs; } @LPValidatorV3 public async setUsageAsCollateral( @isEthAddress('user') @isEthAddress('reserve') { user, reserve, usageAsCollateral }: LPSetUsageAsCollateral, ): Promise { const poolContract: IPool = this.getContractInstance(this.poolAddress); const txCallback: () => Promise = this.generateTxCallback({ rawTxMethod: async () => poolContract.populateTransaction.setUserUseERC20AsCollateral( reserve, usageAsCollateral, ), from: user, }); return [ { tx: txCallback, txType: eEthereumTxType.DLP_ACTION, gas: this.generateTxPriceEstimation([], txCallback), }, ]; } @LPValidatorV3 public async setUserUseERC721AsCollateral( @isEthAddress('user') @isEthAddress('reserve') { user, reserve, tokenIds, usageAsCollateral, }: LPSetERC721UsageAsCollateral, ): Promise { const poolContract: IPool = this.getContractInstance(this.poolAddress); const txCallback: () => Promise = this.generateTxCallback({ rawTxMethod: async () => poolContract.populateTransaction.setUserUseERC721AsCollateral( reserve, tokenIds, usageAsCollateral, ), from: user, }); return [ { tx: txCallback, txType: eEthereumTxType.DLP_ACTION, gas: this.generateTxPriceEstimation([], txCallback), }, ]; } @LPValidatorV3 public async liquidationCall( @isEthAddress('liquidator') @isEthAddress('liquidatedUser') @isEthAddress('debtReserve') @isEthAddress('collateralReserve') @isPositiveAmount('purchaseAmount') { liquidator, liquidatedUser, debtReserve, collateralReserve, purchaseAmount, getAToken, liquidateAll, }: LPLiquidationCall, ): Promise { const txs: EthereumTransactionTypeExtended[] = []; const { isApproved, approve, decimalsOf }: IERC20ServiceInterface = this.erc20Service; const approved = await isApproved({ token: debtReserve, user: liquidator, spender: this.poolAddress, amount: purchaseAmount, }); if (!approved) { const approveTx: EthereumTransactionTypeExtended = approve({ user: liquidator, token: debtReserve, spender: this.poolAddress, amount: DEFAULT_APPROVE_AMOUNT, }); txs.push(approveTx); } let convertedAmount = constants.MaxUint256.toString(); if (!liquidateAll) { const reserveDecimals = await decimalsOf(debtReserve); convertedAmount = valueToWei(purchaseAmount, reserveDecimals); } const poolContract = this.getContractInstance(this.poolAddress); const txCallback: () => Promise = this.generateTxCallback({ rawTxMethod: async () => poolContract.populateTransaction.liquidateERC20( collateralReserve, debtReserve, liquidatedUser, convertedAmount, getAToken ?? false, ), from: liquidator, value: getTxValue(debtReserve, convertedAmount), }); txs.push({ tx: txCallback, txType: eEthereumTxType.DLP_ACTION, gas: this.generateTxPriceEstimation( txs, txCallback, ProtocolAction.liquidationCall, ), }); return txs; } @LPValidatorV3 public async liquidationERC721Call( @isEthAddress('liquidator') @isEthAddress('liquidatedUser') @isEthAddress('debtReserve') @isEthAddress('collateralReserve') @isPositiveAmount('purchaseAmount') { liquidator, liquidatedUser, debtReserve, collateralReserve, collateralTokenId, purchaseAmount, getNToken, liquidateAll, }: LPERC721LiquidationCall, ): Promise { const txs: EthereumTransactionTypeExtended[] = []; const { isApproved, approve, decimalsOf }: IERC20ServiceInterface = this.erc20Service; const approved = await isApproved({ token: debtReserve, user: liquidator, spender: this.poolAddress, amount: purchaseAmount, }); if (!approved) { const approveTx: EthereumTransactionTypeExtended = approve({ user: liquidator, token: debtReserve, spender: this.poolAddress, amount: DEFAULT_APPROVE_AMOUNT, }); txs.push(approveTx); } let convertedAmount = constants.MaxUint256.toString(); if (!liquidateAll) { const reserveDecimals = await decimalsOf(debtReserve); convertedAmount = valueToWei(purchaseAmount, reserveDecimals); } const poolContract = this.getContractInstance(this.poolAddress); const txCallback: () => Promise = this.generateTxCallback({ rawTxMethod: async () => poolContract.populateTransaction.liquidateERC721( collateralReserve, liquidatedUser, collateralTokenId, convertedAmount, getNToken ?? false, ), from: liquidator, value: getTxValue(debtReserve, convertedAmount), }); txs.push({ tx: txCallback, txType: eEthereumTxType.DLP_ACTION, gas: this.generateTxPriceEstimation( txs, txCallback, ProtocolAction.liquidationCall, ), }); return txs; } @LPValidatorV3 public async flashClaim( @isEthAddress('user') @isEthAddress('receiver') @isEthAddressArray('nftAssets') { user, receiver, nftAssets, tokenIds, airdropContractAddr, airdropTokenTypes, airdropTokenAddrs, airdropTokenIds, airdropEncodedData, }: LPFlashClaim, ): Promise { const txs: EthereumTransactionTypeExtended[] = []; const poolContract: IPool = this.getContractInstance(this.poolAddress); const params: string = utils.defaultAbiCoder.encode( ['uint256[]', 'address[]', 'uint256[]', 'address', 'bytes'], [ airdropTokenTypes, airdropTokenAddrs, airdropTokenIds, airdropContractAddr, airdropEncodedData, ], ); const txCallback: () => Promise = this.generateTxCallback({ rawTxMethod: async () => poolContract.populateTransaction.flashClaim( receiver, nftAssets, tokenIds, params, ), from: user, }); txs.push({ tx: txCallback, txType: eEthereumTxType.DLP_ACTION, gas: this.generateTxPriceEstimation( txs, txCallback, ProtocolAction.flashClaim, ), }); return txs; } @LPValidatorV3 public async getUserAccountData( @isEthAddress('user') { user }: { user: string }, ) { const poolContract: IPool = this.getContractInstance(this.poolAddress); const res = await poolContract.getUserAccountData(user); return res; } @LPValidatorV3 public async buyWithCredit( @isEthAddress('marketProtocolAddr') @isEthAddress('onBehalfOf') @is0OrPositiveAmount('buyNowAmount') @is0OrPositiveAmount('payLaterAmount') { marketProtocolAddr, marketPlaceType, marketProtocolData, onBehalfOf, buyNowAmount, payLaterAmount, }: LPBuyWithCredit, ): Promise { // currently we only support buy from third-party platform const txs: EthereumTransactionTypeExtended[] = []; const poolContract: IPool = this.getContractInstance(this.poolAddress); const seaportContract: Seaport = SeaportFactory.connect( marketProtocolAddr, this.provider, ); const encodedData = seaportContract.interface.encodeFunctionData( 'fulfillAdvancedOrder', [ marketProtocolData, [], marketProtocolData.parameters.conduitKey, poolContract.address, ], ); // sign is not needed const payLaterUnsignedSig = { v: 0, r: constants.HashZero, s: constants.HashZero, }; const txCallback: () => Promise = this.generateTxCallback({ rawTxMethod: async () => poolContract.populateTransaction.buyWithCredit( MarketplaceIdMap[marketPlaceType], `0x${encodedData.slice(10)}`, { token: ethers.constants.AddressZero, // for ETH, you should put 0x00000..., the order of tokens must be the same with consideration amount: utils.parseEther(payLaterAmount), orderId: marketProtocolData.signature, ...payLaterUnsignedSig, }, 0, // referralCode ), from: onBehalfOf, value: utils.parseEther(buyNowAmount).toString(), }); const gas = this.generateTxPriceEstimation( txs, txCallback, ProtocolAction.buyWithCredit, ); txs.push({ tx: txCallback, txType: eEthereumTxType.DLP_ACTION, gas, }); return txs; } @LPValidatorV3 public async batchBuyWithCredit( @isEthAddress('onBehalfOf') @is0OrPositiveAmount('buyNowAmount') { marketProtocolAddrs, marketPlaceTypes, marketProtocolData, onBehalfOf, buyNowAmount, payLaterAmount, }: LPBatchBuyWithCredit, ): Promise { const txs: EthereumTransactionTypeExtended[] = []; const poolContract: IPool = this.getContractInstance(this.poolAddress); const encodedDataList = marketProtocolData.map((item, index) => { const marketProtocolAddr = marketProtocolAddrs[index]; const seaportContract: Seaport = SeaportFactory.connect( marketProtocolAddr, this.provider, ); return `0x${seaportContract.interface .encodeFunctionData('fulfillAdvancedOrder', [ item, [], item.parameters.conduitKey, poolContract.address, ]) .slice(10)}`; }); // sign is not needed const payLaterUnsignedSig = { v: 0, r: constants.HashZero, s: constants.HashZero, }; const txCallback: () => Promise = this.generateTxCallback({ rawTxMethod: async () => poolContract.populateTransaction.batchBuyWithCredit( marketPlaceTypes.map(item => MarketplaceIdMap[item]), encodedDataList, marketProtocolData.map((item, index) => ({ token: ethers.constants.AddressZero, // for ETH, you should put 0x00000..., the order of tokens must be the same with consideration amount: utils.parseEther(payLaterAmount[index]), orderId: item.signature, ...payLaterUnsignedSig, })), 0, // referralCode ), from: onBehalfOf, value: utils.parseEther(buyNowAmount).toString(), }); const gas = this.generateTxPriceEstimation( txs, txCallback, ProtocolAction.batchBuyWithCredit, ); txs.push({ tx: txCallback, txType: eEthereumTxType.DLP_ACTION, gas, }); return txs; } @LPValidatorV3 public async acceptBidWithCredit( @isEthAddress('marketProtocolAddr') @isEthAddress('onBehalfOf') { orderProtocolData, acceptProtocolData, marketProtocolAddr, creditData, onBehalfOf, isCollectionBid, nftId, }: LPAcceptBidWithCredit, ): Promise { const txs: EthereumTransactionTypeExtended[] = []; const poolContract: IPool = this.getContractInstance(this.poolAddress); const seaportContract: Seaport = SeaportFactory.connect( marketProtocolAddr, this.provider, ); const toFulfillmentComponents = (arr: number[][]): FulfillmentComponent[] => arr.map(([orderIndex, itemIndex]) => ({ orderIndex, itemIndex })); const toFulfillment = ( offerArr: number[][], considerationsArr: number[][], ): Fulfillment => ({ offerComponents: toFulfillmentComponents(offerArr), considerationComponents: toFulfillmentComponents(considerationsArr), }); const bidOrderConsideration = orderProtocolData.parameters.consideration; const fulfillment = [ // seller fee [[[0, 0]], [[1, 0]]], // creator fee bidOrderConsideration.length >= 2 && [[[0, 0]], [[0, 1]]], // platform fee bidOrderConsideration.length >= 3 && [[[0, 0]], [[0, 2]]], // nft transter [[[1, 0]], [[0, 0]]], ] .filter((v): v is number[][][] => Boolean(v)) .map(([offerArr, considerationArr]) => toFulfillment(offerArr, considerationArr), ); const criteriaResolvers = isCollectionBid ? [ { orderIndex: 0, side: 1, index: 0, identifier: BigNumber.from(nftId), criteriaProof: [], }, ] : []; const encodedData = seaportContract.interface.encodeFunctionData( 'matchAdvancedOrders', [[orderProtocolData, acceptProtocolData], criteriaResolvers, fulfillment], ); const txCallback: () => Promise = this.generateTxCallback({ rawTxMethod: async () => poolContract.populateTransaction.acceptBidWithCredit( MarketplaceIdMap.PARASPACE_SEAPORT_V11_ID, `0x${encodedData.slice(10)}`, creditData, onBehalfOf, 0, // referralCode ), from: onBehalfOf, }); const gas = this.generateTxPriceEstimation( txs, txCallback, ProtocolAction.acceptBidWithCredit, ); txs.push({ tx: txCallback, txType: eEthereumTxType.DLP_ACTION, gas, }); return txs; } @LPValidatorV3 public async startAuction( @isEthAddress('user') { user, collateralAsset, collateralTokenId, from }: LPAuction, ): Promise { const txs: EthereumTransactionTypeExtended[] = []; const poolContract: IPool = this.getContractInstance(this.poolAddress); const txCallback: () => Promise = this.generateTxCallback({ rawTxMethod: async () => poolContract.populateTransaction.startAuction( user, collateralAsset, collateralTokenId, ), from, }); txs.push({ tx: txCallback, txType: eEthereumTxType.DLP_ACTION, gas: this.generateTxPriceEstimation( txs, txCallback, ProtocolAction.startAuction, ), }); return txs; } @LPValidatorV3 public async endAuction( @isEthAddress('user') { user, collateralAsset, collateralTokenId, from }: LPAuction, ): Promise { const txs: EthereumTransactionTypeExtended[] = []; const poolContract: IPool = this.getContractInstance(this.poolAddress); const txCallback: () => Promise = this.generateTxCallback({ rawTxMethod: async () => poolContract.populateTransaction.endAuction( user, collateralAsset, collateralTokenId, ), from, }); txs.push({ tx: txCallback, txType: eEthereumTxType.DLP_ACTION, gas: this.generateTxPriceEstimation( txs, txCallback, ProtocolAction.endAuction, ), }); return txs; } @LPValidatorV3 public async setAuctionValidityTime( @isEthAddress('user') { user, from }: { user: tEthereumAddress; from: tEthereumAddress }, ): Promise { const txs: EthereumTransactionTypeExtended[] = []; const poolContract: IPool = this.getContractInstance(this.poolAddress); const txCallback: () => Promise = this.generateTxCallback({ rawTxMethod: async () => poolContract.populateTransaction.setAuctionValidityTime(user), from, }); txs.push({ tx: txCallback, txType: eEthereumTxType.DLP_ACTION, gas: this.generateTxPriceEstimation( txs, txCallback, ProtocolAction.setAuctionValidityTime, ), }); return txs; } @LPValidatorV3 public async liquidationERC721( @isEthAddress('user') { from, user, collateralAsset, collateralTokenId, liquidationAmount, receiveNToken, }: LPLiquidationERC721, ): Promise { const txs: EthereumTransactionTypeExtended[] = []; const poolContract: IPool = this.getContractInstance(this.poolAddress); const txCallback: () => Promise = this.generateTxCallback({ rawTxMethod: async () => poolContract.populateTransaction.liquidateERC721( collateralAsset, user, collateralTokenId, liquidationAmount, receiveNToken, ), from, }); txs.push({ tx: txCallback, txType: eEthereumTxType.DLP_ACTION, gas: this.generateTxPriceEstimation( txs, txCallback, ProtocolAction.liquidationERC721, ), }); return txs; } @LPValidatorV3 public async getUserConfiguration( @isEthAddress('user') { user }: { user: tEthereumAddress }, ) { const poolContract: IPool = this.getContractInstance(this.poolAddress); return poolContract.getUserConfiguration(user); } @LPValidatorV3 public async getAuctionData({ asset, tokenId, }: { asset: string; tokenId: string; }) { const poolContract: IPool = this.getContractInstance(this.poolAddress); return poolContract.getAuctionData(asset, tokenId); } @LPValidatorV3 public decreaseLiquidity({ asset, tokenId, liquidityDecrease, amount0Min, amount1Min, onBehalfOf, receiveETHAsWETH, }: { asset: string; tokenId: string; liquidityDecrease: string; amount0Min: string; amount1Min: string; onBehalfOf: string; receiveETHAsWETH: boolean; }): EthereumTransactionTypeExtended { const contract = this.getContractInstance(this.poolAddress); const txCallback: () => Promise = this.generateTxCallback({ rawTxMethod: async () => contract.populateTransaction.decreaseUniswapV3Liquidity( asset, tokenId, liquidityDecrease, amount0Min, amount1Min, receiveETHAsWETH, ), from: onBehalfOf, }); return { tx: txCallback, txType: eEthereumTxType.DECREASE_LIQUIDITY, gas: this.generateTxPriceEstimation([], txCallback), }; } @LPValidatorV3 public depositApeCoin( @isEthAddress('address') @isEthAddress('user') @is0OrPositiveAmount('borrowAmount') @is0OrPositiveAmount('cashAmount') @is0OrPositiveAmount('tokenId') { address, apeCoinAddress, tokenId, borrowAmount, cashAmount, bakcTokenId, bakcBorrowAmount, bakcCashAmount, user, }: { address: string; apeCoinAddress: string; tokenId: number; borrowAmount: number; cashAmount: number; bakcTokenId?: number; bakcBorrowAmount?: number; bakcCashAmount?: number; user: string; }, ): EthereumTransactionTypeExtended { const parsedBorrowAmount = ethers.utils.parseUnits( String(borrowAmount), APE_COIN_DECIMAL, ); const parsedCashAmount = ethers.utils.parseUnits( String(cashAmount), APE_COIN_DECIMAL, ); const parsedBakcBorrowAmount = ethers.utils.parseUnits( String(bakcBorrowAmount ?? 0), APE_COIN_DECIMAL, ); const parsedBakcCashAmount = ethers.utils.parseUnits( String(bakcCashAmount ?? 0), APE_COIN_DECIMAL, ); const contract = this.getContractInstance(this.poolAddress); // BAYC/MAYC Pool const nftAmount = parsedBorrowAmount.add(parsedCashAmount); const nfts = nftAmount.isZero() ? [] : [ { tokenId, amount: nftAmount, }, ]; // BAKC Pool const pairs = bakcTokenId ? [ { mainTokenId: tokenId, bakcTokenId, amount: parsedBakcBorrowAmount.add(parsedBakcCashAmount), }, ] : []; const txCallback: () => Promise = this.generateTxCallback({ rawTxMethod: async () => contract.populateTransaction.borrowApeAndStake( { nftAsset: address, borrowAsset: apeCoinAddress, borrowAmount: parsedBorrowAmount.add(parsedBakcBorrowAmount), cashAmount: parsedCashAmount.add(parsedBakcCashAmount), }, nfts, pairs, ), from: user, }); return { tx: txCallback, txType: eEthereumTxType.DLP_ACTION, gas: this.generateTxPriceEstimation([], txCallback), }; } @LPValidatorV3 public withdrawApeCoin( @isEthAddress('address') @isEthAddress('user') address: string, nfts: ApeCoinStaking.SingleNftStruct[], user: string, ): EthereumTransactionTypeExtended { const contract = this.getContractInstance(this.poolAddress); const param = nfts.map(({ tokenId, amount }) => ({ tokenId, amount: ethers.utils.parseUnits(String(amount), APE_COIN_DECIMAL), })); const txCallback: () => Promise = this.generateTxCallback({ rawTxMethod: async () => contract.populateTransaction.withdrawApeCoin(address, param), from: user, }); return { tx: txCallback, txType: eEthereumTxType.DLP_ACTION, gas: this.generateTxPriceEstimation([], txCallback), }; } @LPValidatorV3 public claimApecoin( @isEthAddress('address') @isEthAddress('user') @isValidTokenIdArray('tokenIds') address: string, tokenIds: number[], user: string, ) { const contract = this.getContractInstance(this.poolAddress); const txCallback: () => Promise = this.generateTxCallback({ rawTxMethod: async () => contract.populateTransaction.claimApeCoin(address, tokenIds), from: user, }); return { tx: txCallback, txType: eEthereumTxType.DLP_ACTION, gas: this.generateTxPriceEstimation([], txCallback), }; } @LPValidatorV3 public unstakeApePositionAndRepay( @isEthAddress('address') @isEthAddress('user') @is0OrPositiveAmount('tokenId') address: string, tokenId: number, user: string, ) { const contract = this.getContractInstance(this.poolAddress); const txCallback: () => Promise = this.generateTxCallback({ rawTxMethod: async () => contract.populateTransaction.unstakeApePositionAndRepay( address, tokenId, ), from: user, }); return { tx: txCallback, txType: eEthereumTxType.DLP_ACTION, gas: this.generateTxPriceEstimation([], txCallback), }; } @LPValidatorV3 public withdrawBAKC( @isEthAddress('address') @isEthAddress('user') address: string, user: string, bakcPaired: { mainTokenId: number; bakcTokenId: number; amount: number; isUncommit: boolean; }, ) { const contract = this.getContractInstance(this.poolAddress); const { mainTokenId, bakcTokenId, amount, isUncommit } = bakcPaired; const txCallback: () => Promise = this.generateTxCallback({ rawTxMethod: async () => contract.populateTransaction.withdrawBAKC(address, [ { mainTokenId, bakcTokenId, isUncommit, amount: ethers.utils.parseUnits(String(amount), APE_COIN_DECIMAL), }, ]), from: user, }); return { tx: txCallback, txType: eEthereumTxType.DLP_ACTION, gas: this.generateTxPriceEstimation([], txCallback), }; } @LPValidatorV3 public claimBAKC( @isEthAddress('address') @isEthAddress('user') address: string, user: string, bakcPaired: Array<{ mainTokenId: number; bakcTokenId: number; }>, ) { const contract = this.getContractInstance(this.poolAddress); const txCallback: () => Promise = this.generateTxCallback({ rawTxMethod: async () => contract.populateTransaction.claimBAKC(address, bakcPaired), from: user, }); return { tx: txCallback, txType: eEthereumTxType.DLP_ACTION, gas: this.generateTxPriceEstimation([], txCallback), }; } public async getUserApeCompoundStrategy(user: string) { const contract = this.getContractInstance(this.poolAddress); return contract.getUserApeCompoundStrategy(user); } /** * * @param strategy * { * ty: 0; * swapTokenOut: 0 ; * uint256 swapPercent: 0.6 * 10000; * } */ public setApeCompoundStrategy( strategy: ApeCompoundStrategyStruct, user: string, ) { const contract = this.getContractInstance(this.poolAddress); const txCallback: () => Promise = this.generateTxCallback({ rawTxMethod: async () => contract.populateTransaction.setApeCompoundStrategy(strategy), from: user, }); return { tx: txCallback, txType: eEthereumTxType.OTHERS, gas: this.generateTxPriceEstimation([], txCallback), }; } public movePositionFromBendDAO(loanIds: string[], user: string) { const contract = this.getContractInstance(this.poolAddress); const txCallback: () => Promise = this.generateTxCallback({ rawTxMethod: async () => contract.populateTransaction.movePositionFromBendDAO(loanIds), from: user, }); return { tx: txCallback, txType: eEthereumTxType.OTHERS, gas: this.generateTxPriceEstimation([], txCallback), }; } public async getReserveData(asset: string) { const instance: IPool = this.getContractInstance(this.poolAddress); return instance.getReserveData(asset); } public async initiateBlurExchangeRequest( blurBuyWithCreditRequestStructs: DataTypes.BlurBuyWithCreditRequestStruct[], user: string, ) { const contract = this.getContractInstance(this.poolAddress); const txCallback: () => Promise = this.generateTxCallback({ rawTxMethod: async () => contract.populateTransaction.initiateBlurExchangeRequest( blurBuyWithCreditRequestStructs, ), from: user, }); return { tx: txCallback, txType: eEthereumTxType.OTHERS, gas: this.generateTxPriceEstimation([], txCallback), }; } public listenBlurExchangeRequestResultEvent( list: Array<{ collection: string; tokenId: number }>, callback: BatchEventResultCallback, ) { const contract = this.getContractInstance(this.poolAddress); const resultMap = list.reduce[0]>( (acc, each) => { acc[each.tokenId] = { fulfilled: false, rejected: false, }; return acc; }, {}, ); let counter = 0; contract.on( // eslint-disable-next-line new-cap,max-params contract.filters.BlurExchangeRequestFulfilled(), ( _initiator, _paymentToken, _listingPrice, _borrowAmount, collection, tokenId, ) => { if ( list.findIndex( each => each.tokenId.toString() === tokenId.toString() && each.collection === collection, ) !== -1 ) { resultMap[tokenId.toNumber()].fulfilled = true; counter++; if (counter === list.length) { contract.removeAllListeners(); } callback(resultMap, counter === list.length); } }, ); contract.on( // eslint-disable-next-line new-cap,max-params contract.filters.BlurExchangeRequestRejected(), ( _initiator, _paymentToken, _listingPrice, _borrowAmount, collection, tokenId, ) => { if ( list.findIndex( each => each.tokenId.toString() === tokenId.toString() && each.collection === collection, ) !== -1 ) { resultMap[tokenId.toNumber()].rejected = true; counter++; if (counter === list.length) { contract.removeAllListeners(); } callback(resultMap, counter === list.length); } }, ); } public async getReserveDataInBatch(assets: string[]) { const instance: IPool = this.getContractInstance(this.poolAddress); const d = new DeploylessViewerClient(this.provider); const raw = (await d.multicall( assets.map(asset => { return { target: this.poolAddress, callData: instance.interface.encodeFunctionData('getReserveData', [ asset, ]), }; }), )) as { resultsArray: BytesLike[]; }; return raw.resultsArray.map(each => instance.interface.decodeFunctionResult('getReserveData', each), ) as DataTypes.ReserveDataStructOutput[]; } public initiateAcceptBlurBidsRequest( requests: DataTypes.AcceptBlurBidsRequestStruct[], user: string, ) { const contract: IPool = this.getContractInstance(this.poolAddress); const txCallback: () => Promise = this.generateTxCallback({ rawTxMethod: async () => contract.populateTransaction.initiateAcceptBlurBidsRequest(requests), from: user, }); return { tx: txCallback, txType: eEthereumTxType.OTHERS, gas: this.generateTxPriceEstimation([], txCallback), }; } public fulfillAcceptBlurBidsRequest( requests: DataTypes.AcceptBlurBidsRequestStruct[], user: string, ) { const contract: IPool = this.getContractInstance(this.poolAddress); const txCallback: () => Promise = this.generateTxCallback({ rawTxMethod: async () => contract.populateTransaction.fulfillAcceptBlurBidsRequest(requests), from: user, }); return { tx: txCallback, txType: eEthereumTxType.OTHERS, gas: this.generateTxPriceEstimation([], txCallback), }; } public rejectAcceptBlurBidsRequest( requests: DataTypes.AcceptBlurBidsRequestStruct[], user: string, ) { const contract: IPool = this.getContractInstance(this.poolAddress); const txCallback: () => Promise = this.generateTxCallback({ rawTxMethod: async () => contract.populateTransaction.rejectAcceptBlurBidsRequest(requests), from: user, }); return { tx: txCallback, txType: eEthereumTxType.OTHERS, gas: this.generateTxPriceEstimation([], txCallback), }; } public async getAcceptBlurBidsRequestStatus( request: DataTypes.AcceptBlurBidsRequestStruct, ) { const contract: IPool = this.getContractInstance(this.poolAddress); return contract.getAcceptBlurBidsRequestStatus(request); } }