UNPKG

@xlink-network/xlink-sdk

Version:
182 lines (170 loc) 5.54 kB
import { Client, encodeFunctionData, zeroAddress } from "viem" import { estimateGas, readContract } from "viem/actions" import { BridgeEndpointAbi } from "../evmUtils/contractAbi/bridgeEndpoint" import { BridgeTimeLockAbi } from "../evmUtils/contractAbi/bridgeTimeLock" import { getEVMContractCallInfo, getEVMTokenContractInfo, numberFromSolidityContractNumber, } from "../evmUtils/xlinkContractHelpers" import { BigNumber } from "../utils/BigNumber" import { UnsupportedChainError } from "../utils/errors" import { decodeHex } from "../utils/hexHelpers" import { isNotNull } from "../utils/typeHelpers" import { KnownChainId, KnownTokenId, _allKnownEVMTokens, } from "../utils/types/knownIds" import { EVMAddress, SDKNumber, toSDKNumberOrUndefined } from "./types" import { SDKGlobalContext } from "./types.internal" import { nativeCurrencyAddress } from "../evmUtils/addressHelpers" export interface TimeLockedAsset { id: string chain: KnownChainId.EVMChain token: KnownTokenId.EVMToken amount: SDKNumber releaseTime: Date } const getTimeLockContractCallInfo = async ( ctx: SDKGlobalContext, chain: KnownChainId.EVMChain, ): Promise< | undefined | { client: Client timeLockContractAddress: EVMAddress } > => { const info = await getEVMContractCallInfo(ctx, chain) if (info == null) return const timeLockContractAddress = info.timeLockContractAddress ?? (await readContract(info.client, { abi: BridgeEndpointAbi, address: info.bridgeEndpointContractAddress, functionName: "timeLock", }).catch(err => { console.groupCollapsed( `Failed to read timeLock contract address from ${info.bridgeEndpointContractAddress} (${chain})`, ) console.debug(err) console.groupEnd() return zeroAddress })) if (timeLockContractAddress === zeroAddress) return return { client: info.client, timeLockContractAddress } } export interface GetTimeLockedAssetsInput { walletAddress: EVMAddress chains: KnownChainId.EVMChain[] } export interface GetTimeLockedAssetsOutput { assets: TimeLockedAsset[] } export async function getTimeLockedAssetsFromEVM( ctx: SDKGlobalContext, input: GetTimeLockedAssetsInput, ): Promise<GetTimeLockedAssetsOutput> { const promises = input.chains.map(async chain => { const timeLockCallInfo = await getTimeLockContractCallInfo(ctx, chain) if (timeLockCallInfo == null) return [] const tokenCallInfos = ( await Promise.all( _allKnownEVMTokens.map(token => getEVMTokenContractInfo(ctx, chain, token).then(info => info == null || info.tokenContractAddress === nativeCurrencyAddress ? null : { ...info, chain, token }, ), ), ) ).filter(isNotNull) const agreements = ( await Promise.all( tokenCallInfos.map(info => readContract(timeLockCallInfo.client, { abi: BridgeTimeLockAbi, address: timeLockCallInfo.timeLockContractAddress, functionName: "agreementsByUser", args: [input.walletAddress, 0, info.tokenContractAddress, "0x"], }).then(agreementId => agreementId === 0n ? [] : [{ agreementId, info }], ), ), ) ).flat() return Promise.all( agreements.map(async ({ agreementId, info }) => { const agreement = await readContract(timeLockCallInfo.client, { abi: BridgeTimeLockAbi, address: timeLockCallInfo.timeLockContractAddress, functionName: "agreements", args: [agreementId], }) return { id: String(agreementId), chain: info.chain, token: info.token, amount: toSDKNumberOrUndefined( numberFromSolidityContractNumber(agreement[0]), ), releaseTime: new Date(agreement[6] * 1000), } }), ) }) return { assets: (await Promise.all(promises)).flat(), } } export interface ClaimTimeLockedAssetsInput { chain: KnownChainId.EVMChain walletAddress: EVMAddress lockedAssetIds: string[] sendTransaction: (tx: { from: EVMAddress to: EVMAddress data: Uint8Array recommendedGasLimit: SDKNumber }) => Promise<{ txHash: string }> } export interface ClaimTimeLockedAssetsOutput { txHash: string } export const claimTimeLockedAssetsFromEVM = async ( ctx: SDKGlobalContext, input: ClaimTimeLockedAssetsInput, ): Promise<undefined | ClaimTimeLockedAssetsOutput> => { const info = await getTimeLockContractCallInfo(ctx, input.chain) if (info == null) throw new UnsupportedChainError(input.chain) const functionData = encodeFunctionData({ abi: BridgeTimeLockAbi, functionName: "claim", args: [ input.lockedAssetIds.map(id => BigNumber.toBigInt({ roundingMode: BigNumber.roundDown }, id), ), ], }) const estimated = await estimateGas(info.client, { account: input.walletAddress, to: info.timeLockContractAddress, data: functionData, }) .then(n => BigNumber.round({ precision: 0 }, BigNumber.mul(n, 1.2))) .catch( // add a fallback in case estimate failed () => // https://bscscan.com/tx/0x28a81312ca7bc93e7ef07867c9906a41b251ea3ea630b0a4837bdb3066489b32 1 * 1e6, ) return await input.sendTransaction({ from: input.walletAddress, to: info.timeLockContractAddress, data: decodeHex(functionData), recommendedGasLimit: toSDKNumberOrUndefined(estimated), }) }