UNPKG

@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
"use strict"; 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