@wormhole-foundation/sdk-connect
Version:
The core package for the Connect SDK, used in conjunction with 1 or more of the chain packages
224 lines • 11.6 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.AutomaticPorticoRoute = exports.BPS_PER_HUNDRED_PERCENT = exports.MAX_SLIPPAGE_BPS = exports.SLIPPAGE_BPS = void 0;
const sdk_base_1 = require("@wormhole-foundation/sdk-base");
const route_js_1 = require("../route.js");
const index_js_1 = require("./../../index.js");
exports.SLIPPAGE_BPS = 15n; // 0.15%
exports.MAX_SLIPPAGE_BPS = 100n; // 1%
exports.BPS_PER_HUNDRED_PERCENT = 10000n;
class AutomaticPorticoRoute extends route_js_1.AutomaticRoute {
static NATIVE_GAS_DROPOFF_SUPPORTED = false;
static meta = {
name: "AutomaticPortico",
};
static supportedNetworks() {
return ["Mainnet"];
}
static supportedChains(network) {
if (index_js_1.contracts.porticoContractChains.has(network)) {
return index_js_1.contracts.porticoContractChains.get(network);
}
return [];
}
static async supportedDestinationTokens(sourceToken, fromChain, toChain) {
const [, srcTokenAddress] = (0, index_js_1.resolveWrappedToken)(fromChain.network, fromChain.chain, sourceToken);
const tokenAddress = (0, index_js_1.canonicalAddress)(srcTokenAddress);
const pb = await fromChain.getPorticoBridge();
// Make sure the source token is supported
if (!pb.supportedTokens().some((t) => (0, index_js_1.canonicalAddress)(t.token) === tokenAddress)) {
return [];
}
try {
// The highway token that will be used to bridge
const transferrableToken = await pb.getTransferrableToken(tokenAddress);
// Make sure it exists on the destination chain
await index_js_1.TokenTransfer.lookupDestinationToken(fromChain, toChain, transferrableToken);
}
catch {
return [];
}
// Find the destination token(s) in the same group
const toPb = await toChain.getPorticoBridge();
const tokens = toPb.supportedTokens();
const { tokenMap } = toChain.config;
const group = pb.getTokenGroup(tokenAddress);
return tokens
.filter((t) => (t.group === group ||
// ETH/WETH supports wrapping/unwrapping
(t.group === "ETH" && group === "WETH") ||
(t.group === "WETH" && group === "ETH")) &&
(!tokenMap || sdk_base_1.filters.byAddress(tokenMap, (0, index_js_1.canonicalAddress)(t.token))))
.map((t) => t.token);
}
getDefaultOptions() {
return {};
}
async validate(request, params) {
try {
if (!AutomaticPorticoRoute.supportedChains(request.fromChain.network).includes(request.fromChain.chain) ||
!AutomaticPorticoRoute.supportedChains(request.toChain.network).includes(request.toChain.chain)) {
throw new Error("Protocol not supported");
}
const { fromChain, toChain, source, destination } = request;
const { network } = fromChain;
// This may be "native" but we want the token that can actually be bridged
const [, sourceToken] = (0, index_js_1.resolveWrappedToken)(network, fromChain.chain, source.id);
const [, destinationToken] = (0, index_js_1.resolveWrappedToken)(network, toChain.chain, destination.id);
const fromPb = await fromChain.getPorticoBridge();
const toPb = await toChain.getPorticoBridge();
const canonicalSourceToken = await fromPb.getTransferrableToken((0, index_js_1.canonicalAddress)(sourceToken));
const canonicalDestinationToken = await toPb.getTransferrableToken((0, index_js_1.canonicalAddress)(destinationToken));
const validatedParams = {
amount: params.amount,
options: params.options ?? this.getDefaultOptions(),
normalizedParams: {
amount: request.parseAmount(params.amount),
canonicalSourceToken,
canonicalDestinationToken,
sourceToken,
destinationToken,
},
};
return { valid: true, params: validatedParams };
}
catch (e) {
return { valid: false, error: e, params: params };
}
}
async quote(request, params) {
try {
const swapAmounts = await this.fetchSwapQuote(request, params);
// destination token may have a different number of decimals than the source token
// so we need to scale the amounts to the token with the most decimals
// before comparing them
const maxDecimals = Math.max(request.source.decimals, request.destination.decimals);
const scaledAmount = index_js_1.amount.units(index_js_1.amount.scale(params.normalizedParams.amount, maxDecimals));
const scaledMinAmountFinish = index_js_1.amount.units(index_js_1.amount.scale(index_js_1.amount.fromBaseUnits(swapAmounts.minAmountFinish, request.destination.decimals), maxDecimals));
// if the slippage is more than 100bps, this likely means that the pools are unbalanced
if (scaledMinAmountFinish <
scaledAmount - (scaledAmount * exports.MAX_SLIPPAGE_BPS) / exports.BPS_PER_HUNDRED_PERCENT)
throw new Error("Slippage too high");
const pb = await request.toChain.getPorticoBridge();
const fee = await pb.quoteRelay(params.normalizedParams.canonicalDestinationToken.address, params.normalizedParams.destinationToken.address);
const details = {
swapAmounts,
relayerFee: fee,
};
const destinationAmount = details.swapAmounts.minAmountFinish - fee;
if (destinationAmount < 0n) {
return {
success: false,
error: new Error(`Amount too low for slippage and fee, would result in negative destination amount (${destinationAmount})`),
};
}
return (await request.displayQuote({
sourceToken: {
token: params.normalizedParams.sourceToken,
amount: index_js_1.amount.units(params.normalizedParams.amount),
},
destinationToken: {
token: params.normalizedParams.destinationToken,
amount: details.swapAmounts.minAmountFinish - fee,
},
relayFee: {
token: params.normalizedParams.destinationToken,
amount: fee,
},
eta: sdk_base_1.finality.estimateFinalityTime(request.fromChain.chain),
expires: sdk_base_1.time.expiration(0, 5, 0),
}, params, details));
}
catch (e) {
return {
success: false,
error: e,
};
}
}
async initiate(request, sender, quote, to) {
const { params, details } = quote;
const sourceToken = request.source.id.address;
const destToken = request.destination.id;
const fromPorticoBridge = await request.fromChain.getPorticoBridge();
const tokenGroup = fromPorticoBridge.getTokenGroup(sourceToken.toString());
const toPorticoBridge = await request.toChain.getPorticoBridge();
const destPorticoAddress = toPorticoBridge.getPorticoAddress(tokenGroup);
const xfer = fromPorticoBridge.transfer(index_js_1.Wormhole.parseAddress(sender.chain(), sender.address()), to, sourceToken, index_js_1.amount.units(params.normalizedParams.amount), destToken, destPorticoAddress, details);
const txids = await (0, index_js_1.signSendWait)(request.fromChain, xfer, sender);
const receipt = {
originTxs: txids,
state: index_js_1.TransferState.SourceInitiated,
from: request.fromChain.chain,
to: request.toChain.chain,
};
return receipt;
}
async *track(receipt, timeout) {
if ((0, index_js_1.isSourceInitiated)(receipt) || (0, index_js_1.isSourceFinalized)(receipt)) {
const { txid } = receipt.originTxs[receipt.originTxs.length - 1];
const vaa = await this.wh.getVaa(txid, "PorticoBridge:Transfer", timeout);
if (!vaa)
throw new Error("No VAA found for transaction: " + txid);
const msgId = {
chain: vaa.emitterChain,
emitter: vaa.emitterAddress,
sequence: vaa.sequence,
};
receipt = {
...receipt,
state: index_js_1.TransferState.Attested,
attestation: {
id: msgId,
attestation: vaa,
},
};
yield receipt;
}
if ((0, index_js_1.isAttested)(receipt)) {
const toChain = this.wh.getChain(receipt.to);
const toPorticoBridge = await toChain.getPorticoBridge();
const isCompleted = await toPorticoBridge.isTransferCompleted(receipt.attestation.attestation);
if (isCompleted) {
receipt = {
...receipt,
state: index_js_1.TransferState.DestinationFinalized,
};
yield receipt;
}
}
// TODO: handle swap failed case (highway token received)
yield receipt;
}
async complete(signer, receipt) {
if (!(0, index_js_1.isAttested)(receipt))
throw new Error("Source must be attested");
const toChain = await this.wh.getChain(receipt.to);
const toPorticoBridge = await toChain.getPorticoBridge();
const sender = index_js_1.Wormhole.chainAddress(signer.chain(), signer.address());
const xfer = toPorticoBridge.redeem(sender.address, receipt.attestation.attestation);
return await (0, index_js_1.signSendWait)(toChain, xfer, signer);
}
async fetchSwapQuote(request, params) {
const fromPb = await request.fromChain.getPorticoBridge();
const xferAmount = index_js_1.amount.units(params.normalizedParams.amount);
const tokenGroup = fromPb.getTokenGroup((0, index_js_1.canonicalAddress)(params.normalizedParams.sourceToken));
const startQuote = await fromPb.quoteSwap(params.normalizedParams.sourceToken.address, params.normalizedParams.canonicalSourceToken.address, tokenGroup, xferAmount);
const startSlippage = (startQuote * exports.SLIPPAGE_BPS) / exports.BPS_PER_HUNDRED_PERCENT;
if (startSlippage >= startQuote)
throw new Error("Start slippage too high");
const toPb = await request.toChain.getPorticoBridge();
const minAmountStart = startQuote - startSlippage;
const finishQuote = await toPb.quoteSwap(params.normalizedParams.canonicalDestinationToken.address, params.normalizedParams.destinationToken.address, tokenGroup, minAmountStart);
const finishSlippage = (finishQuote * exports.SLIPPAGE_BPS) / exports.BPS_PER_HUNDRED_PERCENT;
if (finishSlippage >= finishQuote)
throw new Error("Finish slippage too high");
const minAmountFinish = finishQuote - finishSlippage;
return {
minAmountStart: minAmountStart,
minAmountFinish: minAmountFinish,
};
}
}
exports.AutomaticPorticoRoute = AutomaticPorticoRoute;
//# sourceMappingURL=automatic.js.map
;