UNPKG

interchain-token-sdk

Version:

SDK for deploying and managing interchain tokens across multiple chains using Axelar network

138 lines (137 loc) 6.21 kB
import { encodeFunctionData, } from 'viem'; import { INTERCHAIN_PROXY_CONTRACT_ABI, INTERCHAIN_PROXY_CONTRACT_ADDRESS, } from '../constants/constants.js'; import { validateClientNetwork } from '../utils/validateClientNetwork.js'; import { isERC20Token } from '../utils/validateERC20Address.js'; import { validateChain, getChainConfig, SUPPORTED_CHAINS } from '../config/chains.js'; import { AxelarQueryAPI, Environment } from '@axelar-network/axelarjs-sdk'; const sdk = new AxelarQueryAPI({ environment: Environment.TESTNET }); export function registerCanonicalInterchainTokenCall(tokenAddress) { return { address: INTERCHAIN_PROXY_CONTRACT_ADDRESS, abi: INTERCHAIN_PROXY_CONTRACT_ABI, functionName: 'registerCanonicalInterchainToken', args: [tokenAddress], }; } export function deployRemoteCanonicalInterchainTokenCall(tokenAddress, destinationChain, gasValue) { return { address: INTERCHAIN_PROXY_CONTRACT_ADDRESS, abi: INTERCHAIN_PROXY_CONTRACT_ABI, functionName: 'deployRemoteCanonicalInterchainToken', args: [tokenAddress, destinationChain, gasValue], }; } export async function estimateRemoteDeploymentGas(sourceChainName, destinationChain, executeData) { try { validateChain(destinationChain); const chainConfig = getChainConfig(destinationChain); const baseGasEstimate = chainConfig.baseGasEstimate; const gasFee = await sdk.estimateGasFee(sourceChainName, destinationChain, baseGasEstimate.toString(), 1.2, 'ETH', undefined, executeData, undefined); if (typeof gasFee === 'string') { return BigInt(gasFee); } const estimatedGas = (baseGasEstimate * BigInt(120)) / BigInt(100); return estimatedGas; } catch (error) { return BigInt(600000); } } export async function registerCanonicalInterchainToken(tokenAddress, walletClient, publicClient) { validateClientNetwork(publicClient); // Validate token address first const isValid = await isERC20Token(tokenAddress, publicClient); if (!isValid) { const chainId = await publicClient.getChainId(); const chainConfig = getChainConfig(chainId.toString()); throw new Error(`${tokenAddress} not found on ${chainConfig.name}`); } const call = registerCanonicalInterchainTokenCall(tokenAddress); const { request } = await publicClient.simulateContract({ ...call, account: walletClient.account, }); const hash = await walletClient.writeContract(request); await publicClient.waitForTransactionReceipt({ hash }); return { hash, }; } export async function deployRemoteCanonicalInterchainToken(tokenAddress, destinationChain, gasValue, walletClient, publicClient) { validateClientNetwork(publicClient); const isValid = await isERC20Token(tokenAddress, publicClient); if (!isValid) { const chainId = await publicClient.getChainId(); const chainConfig = getChainConfig(chainId.toString()); throw new Error(`${tokenAddress} not found on ${chainConfig.name}`); } const call = deployRemoteCanonicalInterchainTokenCall(tokenAddress, destinationChain, gasValue); const { request } = await publicClient.simulateContract({ ...call, account: walletClient.account, }); const hash = await walletClient.writeContract(request); const receipt = await publicClient.waitForTransactionReceipt({ hash }); return { hash, receipt, }; } export async function registerAndDeployCanonicalInterchainTokenMulticall(tokenAddress, destinationChains, walletClient, publicClient) { validateClientNetwork(publicClient); // Validate token address first const isValid = await isERC20Token(tokenAddress, publicClient); if (!isValid) { const chainId = await publicClient.getChainId(); const chainConfig = getChainConfig(chainId.toString()); throw new Error(`${tokenAddress} not found on ${chainConfig.name}`); } const chainId = BigInt(await publicClient.getChainId()); const currentChain = Object.entries(SUPPORTED_CHAINS).find(([_, config]) => config.chainId === chainId); if (!currentChain) { throw new Error(`Current chain (${chainId}) is not supported. Supported chains: ${Object.entries(SUPPORTED_CHAINS) .map(([name, config]) => `${name} (chain ID: ${config.chainId})`) .join(', ')}`); } const currentChainName = currentChain[0]; // Filter out the current chain from destination chains destinationChains = destinationChains.filter((chain) => chain.toLowerCase() !== currentChainName.toLowerCase()); // Validate all destination chains destinationChains.forEach(validateChain); const initialRegisterCall = encodeFunctionData({ abi: INTERCHAIN_PROXY_CONTRACT_ABI, functionName: 'registerCanonicalInterchainToken', args: [tokenAddress], }); const remoteDeployCalls = await Promise.all(destinationChains.map(async (chain) => { const executeData = encodeFunctionData({ abi: INTERCHAIN_PROXY_CONTRACT_ABI, functionName: 'deployRemoteCanonicalInterchainToken', args: [tokenAddress, chain, BigInt(0)], }); const gasValue = await estimateRemoteDeploymentGas(currentChainName, chain, executeData); return encodeFunctionData({ abi: INTERCHAIN_PROXY_CONTRACT_ABI, functionName: 'deployRemoteCanonicalInterchainToken', args: [tokenAddress, chain, gasValue], }); })); const multicallData = [initialRegisterCall, ...remoteDeployCalls]; try { const { request } = await publicClient.simulateContract({ address: INTERCHAIN_PROXY_CONTRACT_ADDRESS, abi: INTERCHAIN_PROXY_CONTRACT_ABI, functionName: 'multicall', args: [multicallData], account: walletClient.account, }); const hash = await walletClient.writeContract(request); await publicClient.waitForTransactionReceipt({ hash }); return { hash, }; } catch (error) { throw error; } }