interchain-token-sdk
Version:
SDK for deploying and managing interchain tokens across multiple chains using Axelar network
152 lines (151 loc) • 6.63 kB
JavaScript
import { parseEventLogs, encodeFunctionData, } from 'viem';
import { INTERCHAIN_PROXY_CONTRACT_ABI, INTERCHAIN_PROXY_CONTRACT_ADDRESS, } from '../constants/constants.js';
import { validateClientNetwork } from '../utils/validateClientNetwork.js';
import { generateRandomSalt } from '../utils/salt.js';
import { AxelarQueryAPI, Environment } from '@axelar-network/axelarjs-sdk';
import { SUPPORTED_CHAINS, getChainConfig, validateChain } from '../config/chains.js';
const sdk = new AxelarQueryAPI({
environment: Environment.TESTNET,
});
export function deployInterchainTokenCall(salt, name, symbol, decimals, initialSupply, minter) {
return {
address: INTERCHAIN_PROXY_CONTRACT_ADDRESS,
abi: INTERCHAIN_PROXY_CONTRACT_ABI,
functionName: 'deployInterchainToken',
args: [salt, name, symbol, decimals, initialSupply, minter],
};
}
export function deployRemoteInterchainTokenCall(salt, destinationChain, gasValue) {
return {
address: INTERCHAIN_PROXY_CONTRACT_ADDRESS,
abi: INTERCHAIN_PROXY_CONTRACT_ABI,
functionName: 'deployRemoteInterchainToken',
args: ['', salt, '0x0000000000000000000000000000000000000000', destinationChain, gasValue],
};
}
export function getInterchainTokenDeployedFromLogs(receipt, salt) {
const eventLogs = parseEventLogs({
abi: INTERCHAIN_PROXY_CONTRACT_ABI,
logs: receipt.logs,
});
const event = eventLogs.find((log) => log.eventName === 'InterchainTokenDeployed');
if (!event) {
return undefined;
}
try {
const args = event.args;
return {
tokenId: args.tokenId,
tokenAddress: args.tokenAddress,
minter: args.minter,
name: args.name,
symbol: args.symbol,
decimals: Number(args.decimals),
salt,
};
}
catch (error) {
return undefined;
}
}
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 deployInterchainToken(name, symbol, decimals, initialSupply, minter, walletClient, publicClient) {
validateClientNetwork(publicClient);
const salt = generateRandomSalt();
const call = deployInterchainTokenCall(salt, name, symbol, decimals, initialSupply, minter);
const { request } = await publicClient.simulateContract({
...call,
account: walletClient.account,
});
const hash = await walletClient.writeContract(request);
const receipt = await publicClient.waitForTransactionReceipt({ hash });
const tokenDeployed = getInterchainTokenDeployedFromLogs(receipt, salt);
return {
hash,
tokenDeployed,
};
}
export async function deployRemoteInterchainToken(salt, destinationChain, gasValue, walletClient, publicClient) {
validateClientNetwork(publicClient);
const call = deployRemoteInterchainTokenCall(salt, destinationChain, gasValue);
const { request } = await publicClient.simulateContract({
...call,
account: walletClient.account,
});
const hash = await walletClient.writeContract(request);
const receipt = await publicClient.waitForTransactionReceipt({ hash });
const tokenDeployed = getInterchainTokenDeployedFromLogs(receipt, salt);
return {
hash,
tokenDeployed,
};
}
export async function deployInterchainTokenMulticall(name, symbol, decimals, initialSupply, minter, destinationChains, walletClient, publicClient) {
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 salt = generateRandomSalt();
const initialSupplyWithDecimals = BigInt(initialSupply) * BigInt(10) ** BigInt(decimals);
const initialDeployCall = encodeFunctionData({
abi: INTERCHAIN_PROXY_CONTRACT_ABI,
functionName: 'deployInterchainToken',
args: [salt, name, symbol, decimals, initialSupplyWithDecimals, minter],
});
const remoteDeployCalls = await Promise.all(destinationChains.map(async (chain) => {
const executeData = encodeFunctionData({
abi: INTERCHAIN_PROXY_CONTRACT_ABI,
functionName: 'deployRemoteInterchainToken',
args: ['', salt, '0x0000000000000000000000000000000000000000', chain, BigInt(0)],
});
const gasValue = await estimateRemoteDeploymentGas(currentChainName, chain, executeData);
return encodeFunctionData({
abi: INTERCHAIN_PROXY_CONTRACT_ABI,
functionName: 'deployRemoteInterchainToken',
args: ['', salt, '0x0000000000000000000000000000000000000000', chain, gasValue],
});
}));
const multicallData = [initialDeployCall, ...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);
const receipt = await publicClient.waitForTransactionReceipt({ hash });
const tokenDeployed = getInterchainTokenDeployedFromLogs(receipt, salt);
return {
hash,
tokenDeployed,
};
}
catch (error) {
throw error;
}
}