@wormhole-foundation/sdk-evm-tokenbridge
Version:
SDK for EVM chains, used in conjunction with @wormhole-foundation/sdk
111 lines • 5.99 kB
JavaScript
import { isNative, serialize, toNative, nativeChainIds, toChainId, } from '@wormhole-foundation/sdk-connect';
import { EvmAddress, EvmPlatform, EvmUnsignedTransaction, WETH_CONTRACTS, addChainId, addFrom, } from '@wormhole-foundation/sdk-evm';
import { ethers_contracts } from './index.js';
import '@wormhole-foundation/sdk-evm-core';
import { EvmWormholeCore } from '@wormhole-foundation/sdk-evm-core';
export class EvmAutomaticTokenBridge {
network;
chain;
provider;
contracts;
tokenBridgeRelayer;
tokenBridge;
core;
chainId;
constructor(network, chain, provider, contracts) {
this.network = network;
this.chain = chain;
this.provider = provider;
this.contracts = contracts;
if (network === 'Devnet')
throw new Error('AutomaticTokenBridge not supported on Devnet');
this.chainId = nativeChainIds.networkChainToNativeChainId.get(network, chain);
const tokenBridgeAddress = this.contracts.tokenBridge;
if (!tokenBridgeAddress)
throw new Error(`Wormhole Token Bridge contract for domain ${chain} not found`);
this.tokenBridge = ethers_contracts.Bridge__factory.connect(tokenBridgeAddress, provider);
const relayerAddress = this.contracts.tokenBridgeRelayer;
if (!relayerAddress)
throw new Error(`Wormhole Token Bridge Relayer contract for domain ${chain} not found`);
this.tokenBridgeRelayer =
ethers_contracts.TokenBridgeRelayer__factory.connect(relayerAddress, provider);
this.core = new EvmWormholeCore(network, chain, provider, contracts);
}
async *redeem(sender, vaa) {
const senderAddr = new EvmAddress(sender).toString();
const txReq = await this.tokenBridgeRelayer.completeTransferWithRelay.populateTransaction(serialize(vaa));
yield this.createUnsignedTx(addFrom(txReq, senderAddr), 'TokenBridgeRelayer.completeTransferWithRelay');
}
static async fromRpc(provider, config) {
const [network, chain] = await EvmPlatform.chainFromRpc(provider);
const conf = config[chain];
if (conf.network !== network)
throw new Error(`Network mismatch: ${conf.network} != ${network}`);
return new EvmAutomaticTokenBridge(network, chain, provider, conf.contracts);
}
//alternative naming: initiateTransfer
async *transfer(sender, recipient, token, amount, nativeGas) {
const senderAddr = new EvmAddress(sender).toString();
const recipientChainId = toChainId(recipient.chain);
const messageFee = await this.core.getMessageFee();
const recipientAddress = recipient.address
.toUniversalAddress()
.toUint8Array();
const nativeTokenGas = nativeGas ? nativeGas : 0n;
if (isNative(token)) {
const txReq = await this.tokenBridgeRelayer.wrapAndTransferEthWithRelay.populateTransaction(nativeTokenGas, recipientChainId, recipientAddress, 0, // skip batching
{ value: amount + messageFee });
yield this.createUnsignedTx(addFrom(txReq, senderAddr), 'TokenBridgeRelayer.wrapAndTransferETHWithRelay');
}
else {
//TODO check for ERC-2612 (permit) support on token?
const tokenAddr = new EvmAddress(token).toString();
const tokenContract = EvmPlatform.getTokenImplementation(this.provider, tokenAddr);
const allowance = await tokenContract.allowance(senderAddr, this.tokenBridgeRelayer.target);
if (allowance < amount) {
const txReq = await tokenContract.approve.populateTransaction(this.tokenBridgeRelayer.target, amount);
yield this.createUnsignedTx(addFrom(txReq, senderAddr), 'AutomaticTokenBridge.Approve');
}
const txReq = await this.tokenBridgeRelayer.transferTokensWithRelay.populateTransaction(tokenAddr, amount, nativeTokenGas, recipientChainId, recipientAddress, 0, { value: messageFee });
yield this.createUnsignedTx(addFrom(txReq, senderAddr), 'TokenBridgeRelayer.TransferTokensWithRelay');
}
}
async getRelayerFee(destination, token) {
const destChainId = toChainId(destination);
const srcTokenAddress = await this.tokenAddress(token);
const tokenContract = EvmPlatform.getTokenImplementation(this.provider, srcTokenAddress);
const decimals = await tokenContract.decimals();
return await this.tokenBridgeRelayer.calculateRelayerFee(destChainId, srcTokenAddress, decimals);
}
// Return the amount of native gas that will be
// received when the incoming bridge transfer is redeemed
// Note: for a quote, this should be called on the destination chain
async nativeTokenAmount(token, amount) {
const address = await this.tokenAddress(token);
return this.tokenBridgeRelayer.calculateNativeSwapAmountOut(address, amount);
}
async maxSwapAmount(token) {
const address = await this.tokenAddress(token);
return this.tokenBridgeRelayer.calculateMaxSwapAmountIn(address);
}
async getRegisteredTokens() {
const tokens = await this.tokenBridgeRelayer.getAcceptedTokensList();
return tokens.map((address) => toNative(this.chain, address));
}
async isRegisteredToken(token) {
const address = await this.tokenAddress(token);
return await this.tokenBridgeRelayer.isAcceptedToken(address);
}
async tokenAddress(token) {
return isNative(token)
? await this.getWeth()
: new EvmAddress(token).toString();
}
async getWeth() {
return WETH_CONTRACTS[this.network]?.[this.chain] ?? this.tokenBridge.WETH();
}
createUnsignedTx(txReq, description, parallelizable = false) {
return new EvmUnsignedTransaction(addChainId(txReq, this.chainId), this.network, this.chain, description, parallelizable);
}
}
//# sourceMappingURL=automaticTokenBridge.js.map