@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
JavaScript
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