UNPKG

@wormhole-foundation/sdk-connect

Version:

The core package for the Connect SDK, used in conjunction with 1 or more of the chain packages

169 lines 7.64 kB
import { amount, contracts } from "@wormhole-foundation/sdk-base"; import { isNative, isTokenId } from "@wormhole-foundation/sdk-definitions"; import { TokenTransfer } from "../../protocols/tokenBridge/tokenTransfer.js"; import { TransferState } from "../../types.js"; import { Wormhole } from "../../wormhole.js"; import { AutomaticRoute } from "../route.js"; import { MinAmountError } from "../types.js"; export class AutomaticTokenBridgeRoute extends AutomaticRoute { static NATIVE_GAS_DROPOFF_SUPPORTED = true; static meta = { name: "AutomaticTokenBridge", }; static supportedNetworks() { return ["Mainnet", "Testnet"]; } // get the list of chains this route supports static supportedChains(network) { if (contracts.tokenBridgeRelayerChains.has(network)) { return contracts.tokenBridgeRelayerChains.get(network); } return []; } // get the list of destination tokens that may be received on the destination chain static async supportedDestinationTokens(sourceToken, fromChain, toChain) { try { if (!isNative(sourceToken)) { const srcAtb = await fromChain.getAutomaticTokenBridge(); const srcIsAccepted = await srcAtb.isRegisteredToken(sourceToken.address); if (!srcIsAccepted) { return []; } } const expectedDestinationToken = await TokenTransfer.lookupDestinationToken(fromChain, toChain, sourceToken); const atb = await toChain.getAutomaticTokenBridge(); const acceptable = await atb.isRegisteredToken(expectedDestinationToken.address); if (!acceptable) { return []; } return [expectedDestinationToken]; } catch (e) { return []; } } getDefaultOptions() { return { nativeGas: 0.0 }; } async validate(request, params) { try { const options = params.options ?? this.getDefaultOptions(); if (options.nativeGas && (options.nativeGas > 1.0 || options.nativeGas < 0.0)) throw new Error("Native gas must be between 0.0 and 1.0 (0% and 100%)"); // native gas drop-off when the native token is the destination should be 0 const { destination } = request; if (isNative(destination.id.address) && options.nativeGas === 0.0) options.nativeGas = 0; const updatedParams = { ...params, options }; const validatedParams = { ...updatedParams, normalizedParams: await this.normalizeTransferParams(request, updatedParams), }; return { valid: true, params: validatedParams }; } catch (e) { return { valid: false, params, error: e }; } } async normalizeTransferParams(request, params) { const amt = request.parseAmount(params.amount); const inputToken = isNative(request.source.id.address) ? await request.fromChain.getNativeWrappedTokenId() : request.source.id; const atb = await request.fromChain.getAutomaticTokenBridge(); const fee = await atb.getRelayerFee(request.toChain.chain, inputToken.address); // Min amount is fee + 5% const minAmount = (fee * 105n) / 100n; if (amount.units(amt) < minAmount) { throw new MinAmountError(amount.fromBaseUnits(minAmount, amt.decimals)); } const redeemableAmount = amount.units(amt) - fee; let srcNativeGasAmount = amount.fromBaseUnits(0n, request.source.decimals); if (params.options && params.options.nativeGas > 0) { const dtb = await request.toChain.getAutomaticTokenBridge(); // the maxSwapAmount is in destination chain decimals let maxSwapAmount = await dtb.maxSwapAmount(request.destination.id.address); const redeemableAmountTruncated = amount.truncate(amount.fromBaseUnits(redeemableAmount, amt.decimals), TokenTransfer.MAX_DECIMALS); const dstDecimals = await request.toChain.getDecimals(request.destination.id.address); const dstAmountReceivable = amount.units(amount.scale(redeemableAmountTruncated, dstDecimals)); if (dstAmountReceivable < maxSwapAmount) { // can't swap more than the receivable amount maxSwapAmount = dstAmountReceivable; } const scale = 10000; const scaledGasPercent = BigInt(Math.floor(params.options.nativeGas * scale)); const dstNativeGasUnits = (maxSwapAmount * scaledGasPercent) / BigInt(scale); // the native gas percentage is applied to the max swap amount const dstNativeGasAmount = amount.fromBaseUnits(dstNativeGasUnits, request.destination.decimals); // convert the native gas amount to source chain decimals srcNativeGasAmount = amount.scale(amount.truncate(dstNativeGasAmount, TokenTransfer.MAX_DECIMALS), request.source.decimals); // can't request more gas than the redeemable amount if (amount.units(srcNativeGasAmount) > redeemableAmount) { srcNativeGasAmount = amount.fromBaseUnits(redeemableAmount, request.source.decimals); } } return { fee: amount.fromBaseUnits(fee, request.source.decimals), amount: amt, nativeGasAmount: srcNativeGasAmount, }; } async quote(request, params) { const atb = await request.fromChain.getAutomaticTokenBridge(); if (isTokenId(request.source.id)) { const isRegistered = await atb.isRegisteredToken(request.source.id.address); if (!isRegistered) { return { success: false, error: new Error("Source token is not registered"), }; } } try { let quote = await TokenTransfer.quoteTransfer(this.wh, request.fromChain, request.toChain, { automatic: true, amount: amount.units(params.normalizedParams.amount), token: request.source.id, nativeGas: amount.units(params.normalizedParams.nativeGasAmount), }); return request.displayQuote(quote, params); } catch (e) { return { success: false, error: e, }; } } async initiate(request, signer, quote, to) { const { params } = quote; const transfer = this.toTransferDetails(request, params, Wormhole.chainAddress(signer.chain(), signer.address()), to); const txids = await TokenTransfer.transfer(request.fromChain, transfer, signer); return { from: transfer.from.chain, to: transfer.to.chain, state: TransferState.SourceInitiated, originTxs: txids, }; } async *track(receipt, timeout) { try { yield* TokenTransfer.track(this.wh, receipt, timeout); } catch (e) { throw e; } } toTransferDetails(request, params, from, to) { const transfer = { from, to, automatic: true, amount: amount.units(params.normalizedParams.amount), token: request.source.id, nativeGas: amount.units(params.normalizedParams.nativeGasAmount), }; return transfer; } } //# sourceMappingURL=automatic.js.map