@wormhole-foundation/sdk-connect
Version:
The core package for the Connect SDK, used in conjunction with 1 or more of the chain packages
142 lines • 5.92 kB
JavaScript
import { amount, circle, contracts } from "@wormhole-foundation/sdk-base";
import { isSameToken, } from "@wormhole-foundation/sdk-definitions";
import { CircleTransfer } from "../../protocols/cctp/cctpTransfer.js";
import { TransferState } from "../../types.js";
import { Wormhole } from "../../wormhole.js";
import { AutomaticRoute } from "../route.js";
import { MinAmountError } from "../types.js";
export class AutomaticCCTPRoute extends AutomaticRoute {
static NATIVE_GAS_DROPOFF_SUPPORTED = true;
static meta = {
name: "AutomaticCCTP",
provider: "Circle",
};
static supportedNetworks() {
return ["Mainnet", "Testnet"];
}
// get the list of chains this route supports
static supportedChains(network) {
if (contracts.circleContractChains.has(network)) {
const circleSupportedChains = contracts.circleContractChains.get(network);
return circleSupportedChains.filter((c) => {
return contracts.circleContracts.get(network, c)?.wormholeRelayer;
});
}
return [];
}
// get the list of destination tokens that may be received on the destination chain
static async supportedDestinationTokens(sourceToken, fromChain, toChain) {
// Ensure the source token is USDC
const sourceChainUsdcContract = circle.usdcContract.get(fromChain.network, fromChain.chain);
if (!sourceChainUsdcContract)
return [];
if (!isSameToken(sourceToken, Wormhole.tokenId(fromChain.chain, sourceChainUsdcContract))) {
return [];
}
const { network, chain } = toChain;
if (!circle.usdcContract.has(network, chain))
return [];
return [Wormhole.chainAddress(chain, circle.usdcContract.get(network, chain))];
}
getDefaultOptions() {
return {
nativeGas: 0.0,
};
}
async validate(request, params) {
try {
const options = params.options ?? this.getDefaultOptions();
const normalizedParams = await this.normalizeTransferParams(request, params);
const validatedParams = {
normalizedParams,
options,
...params,
};
return { valid: true, params: validatedParams };
}
catch (e) {
return {
valid: false,
params,
error: e,
};
}
}
async quote(request, params) {
try {
return request.displayQuote(await CircleTransfer.quoteTransfer(request.fromChain, request.toChain, {
automatic: true,
amount: amount.units(params.normalizedParams.amount),
nativeGas: amount.units(params.normalizedParams.nativeGasAmount),
}), params);
}
catch (e) {
return {
success: false,
error: e,
};
}
}
async normalizeTransferParams(request, params) {
const amt = request.parseAmount(params.amount);
const ctb = await request.fromChain.getAutomaticCircleBridge();
const fee = await ctb.getRelayerFee(request.toChain.chain);
const minAmount = (fee * 105n) / 100n;
if (amount.units(amt) < minAmount) {
throw new MinAmountError(amount.fromBaseUnits(minAmount, amt.decimals));
}
const redeemableAmount = amount.units(amt) - fee;
const options = params.options ?? this.getDefaultOptions();
const nativeGasPerc = options.nativeGas ?? 0.0;
if (nativeGasPerc > 1.0 || nativeGasPerc < 0.0)
throw new Error("Native gas must be between 0.0 and 1.0 (0% and 100%)");
let nativeGasAmount = 0n;
if (nativeGasPerc > 0.0) {
const dcb = await request.toChain.getAutomaticCircleBridge();
let maxSwapAmount = await dcb.maxSwapAmount();
if (redeemableAmount < maxSwapAmount) {
// can't swap more than the receivable amount
maxSwapAmount = redeemableAmount;
}
const scale = 10000;
const scaledGas = BigInt(Math.floor(nativeGasPerc * scale));
// the native gas percentage is applied to the max swap amount
nativeGasAmount = (maxSwapAmount * scaledGas) / BigInt(scale);
if (nativeGasAmount === redeemableAmount && nativeGasAmount > 0n) {
// edge case: transfer will revert if the native gas amount is equal to the redeemable amount
nativeGasAmount -= 1n;
}
}
return {
fee: request.amountFromBaseUnits(fee),
amount: amt,
nativeGasAmount: request.amountFromBaseUnits(nativeGasAmount),
};
}
toTransferDetails(params, from, to) {
return {
from,
to,
amount: amount.units(params.normalizedParams.amount),
automatic: true,
nativeGas: amount.units(params.normalizedParams.nativeGasAmount),
};
}
async initiate(request, signer, quote, to) {
const { params } = quote;
let transfer = this.toTransferDetails(params, Wormhole.chainAddress(signer.chain(), signer.address()), to);
let txids = await CircleTransfer.transfer(request.fromChain, transfer, signer);
const msg = await CircleTransfer.getTransferMessage(request.fromChain, txids[txids.length - 1].txid);
return {
from: transfer.from.chain,
to: transfer.to.chain,
state: TransferState.SourceFinalized,
originTxs: txids,
attestation: { id: msg.id, attestation: { message: msg.message } },
};
}
async *track(receipt, timeout) {
yield* CircleTransfer.track(this.wh, receipt, timeout);
}
}
//# sourceMappingURL=automatic.js.map