UNPKG

@arcana/ca-sdk

Version:

Arcana Network's chain abstraction SDK for unified balance in Web3 apps

166 lines (165 loc) 6.72 kB
import { Universe } from "@arcana/ca-common"; import Decimal from "decimal.js"; import { createPublicClient, decodeFunctionData, serializeTransaction, webSocket, } from "viem"; import { ERC20TransferABI } from "../../abi/erc20"; import { KAIA_CHAIN_ID, SOPHON_CHAIN_ID } from "../../chains"; import { AaveTokenContracts, TOKEN_MINTER_CONTRACTS, TOP_OWNER, } from "../../constants"; import { getLogger } from "../../logger"; import ERC20TransferBase from "../../requestHandlers/common/erc20.base"; import { simulateTransaction } from "../../simulate"; import { divDecimals, evmWaitForFill, getL1Fee } from "../../utils"; const logger = getLogger(); class ERC20Transfer extends ERC20TransferBase { constructor(input) { super(input); this.input = input; this.destinationUniverse = Universe.ETHEREUM; this.publicClient = createPublicClient({ transport: webSocket(this.input.chain.rpcUrls.default.webSocket[0]), }); } async simulateTx() { const { data, to } = this.input.evm.tx; const from = this.input.evm.address; const token = this.chainList.getTokenByAddress(this.input.chain.id, to); const nativeToken = this.chainList.getNativeToken(this.input.chain.id); if (!token) { return null; } const { args } = decodeFunctionData({ abi: [ERC20TransferABI], data: data ?? "0x00", }); if (this.input.chain.id === KAIA_CHAIN_ID) { this.simulateTxRes = { amount: divDecimals(args[1], token.decimals), gas: 0n, gasFee: new Decimal("0.002"), token, }; return this.simulateTxRes; } if (this.simulateTxRes) { let gasFee = 0n; if (!this.input.options.bridge) { const [{ gasPrice, maxFeePerGas }, l1Fee] = await Promise.all([ this.publicClient.estimateFeesPerGas(), getL1Fee(this.input.chain, serializeTransaction({ chainId: this.input.chain.id, data: data ?? "0x00", to: to, type: "eip1559", })), ]); const gasUnitPrice = maxFeePerGas ?? gasPrice ?? 0n; if (gasUnitPrice === 0n) { throw new Error("could not get maxFeePerGas or gasPrice from RPC"); } gasFee = this.simulateTxRes.gas * gasUnitPrice + l1Fee; } return { ...this.simulateTxRes, gasFee: divDecimals(gasFee, nativeToken.decimals), }; } const amountToAdd = new Decimal(args[1].toString()) .toHexadecimal() .split("0x")[1] .padStart(40, "0"); let txsToSimulate = []; if (AaveTokenContracts[this.input.chain.id]?.[token.symbol]) { txsToSimulate.push({ from: AaveTokenContracts[this.input.chain.id][token.symbol], input: `0xa9059cbb000000000000000000000000${from .replace("0x", "") .toLowerCase()}000000000000000000000000${amountToAdd}`, to: token.contractAddress, }); } else if (TOKEN_MINTER_CONTRACTS[this.input.chain.id]?.[token.symbol]) { txsToSimulate.push({ from: TOKEN_MINTER_CONTRACTS[this.input.chain.id]?.[token.symbol], input: `0x40c10f19000000000000000000000000${from .replace("0x", "") .toLowerCase()}000000000000000000000000000000000000000000000000000000003b9aca00`, to: token.contractAddress, }); } txsToSimulate.push({ from, input: data, to, }); if (TOP_OWNER[this.input.chain.id]?.[token.symbol]) { const ownerAddress = TOP_OWNER[this.input.chain.id][token.symbol]; txsToSimulate = [ { from: ownerAddress, input: data, to, }, ]; } const [simulation, feeData, l1Fee] = await Promise.all([ simulateTransaction(this.input.chain.id, txsToSimulate, this.input.options.networkConfig.SIMULATION_URL), this.publicClient.estimateFeesPerGas(), getL1Fee(this.input.chain, serializeTransaction({ chainId: this.input.chain.id, data: data ?? "0x00", to: to, type: "eip1559", })), ]); logger.debug("simulateTx", { feeData }); const gasUnitPrice = feeData.maxFeePerGas ?? feeData.gasPrice ?? 0n; if (gasUnitPrice === 0n) { throw new Error("could not get maxFeePerGas or gasPrice from RPC"); } let gasFee = (this.input.chain.id === SOPHON_CHAIN_ID ? BigInt(simulation.data.gas) : BigInt(simulation.data.gas_used)) * gasUnitPrice + l1Fee; logger.debug("erc20:simulateTx", { args, feeData, l1Fee, maxFeePerGas: gasUnitPrice, simulation, totalGas: gasFee, totalGasInDecimal: divDecimals(gasFee, nativeToken.decimals).toFixed(), }); if (this.input.options.bridge) { gasFee = 0n; } const amount = simulation.data.amount === "" ? args[1].toString() : simulation.data.amount; this.simulateTxRes = { amount: divDecimals(amount, token.decimals), gas: this.input.chain.id === SOPHON_CHAIN_ID ? BigInt(simulation.data.gas) : BigInt(simulation.data.gas_used), gasFee: divDecimals(gasFee, nativeToken.decimals), token, }; return this.simulateTxRes; } async waitForFill(requestHash, intentID, waitForDoubleCheckTx) { logger.debug("waitForFill", { intentID, requestHash, waitForDoubleCheckTx, }); try { await Promise.all([ waitForDoubleCheckTx(), evmWaitForFill(this.input.chainList.getVaultContractAddress(this.input.chain.id), this.publicClient, requestHash, intentID, this.input.options.networkConfig.GRPC_URL, this.input.options.networkConfig.COSMOS_URL), ]); } finally { (await this.publicClient.transport.getRpcClient()).close(); } } } export default ERC20Transfer;