UNPKG

@wormhole-foundation/sdk-connect

Version:

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

628 lines 32.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.TokenTransfer = void 0; const sdk_base_1 = require("@wormhole-foundation/sdk-base"); const sdk_definitions_1 = require("@wormhole-foundation/sdk-definitions"); const common_js_1 = require("../../common.js"); const config_js_1 = require("../../config.js"); const sdk_base_2 = require("@wormhole-foundation/sdk-base"); const types_js_1 = require("../../types.js"); const whscan_api_js_1 = require("../../whscan-api.js"); const wormhole_js_1 = require("../../wormhole.js"); class TokenTransfer { wh; fromChain; toChain; // state machine tracker _state; // transfer details transfer; // txids, populated once transactions are submitted txids = []; // The corresponding vaa representing the TokenTransfer // on the source chain (if its been completed and finalized) attestations; constructor(wh, transfer, fromChain, toChain) { this._state = types_js_1.TransferState.Created; this.wh = wh; this.transfer = transfer; this.fromChain = fromChain ?? wh.getChain(transfer.from.chain); this.toChain = toChain ?? wh.getChain(transfer.to.chain); } getTransferState() { return this._state; } static async from(wh, from, timeout = 6000, fromChain, toChain) { if ((0, sdk_definitions_1.isTokenTransferDetails)(from)) { fromChain = fromChain ?? wh.getChain(from.from.chain); toChain = toChain ?? wh.getChain(from.to.chain); // throws if invalid TokenTransfer.validateTransferDetails(wh, from, fromChain, toChain); // Apply hackery from = { ...from, ...(await TokenTransfer.destinationOverrides(fromChain, toChain, from)), }; return new TokenTransfer(wh, from, fromChain, toChain); } let tt; if ((0, sdk_definitions_1.isWormholeMessageId)(from)) { tt = await TokenTransfer.fromIdentifier(wh, from, timeout); } else if ((0, sdk_definitions_1.isTransactionIdentifier)(from)) { tt = await TokenTransfer.fromTransaction(wh, from, timeout, fromChain); } else { throw new Error("Invalid `from` parameter for TokenTransfer"); } tt.fromChain = fromChain ?? wh.getChain(tt.transfer.from.chain); tt.toChain = toChain ?? wh.getChain(tt.transfer.to.chain); await tt.fetchAttestation(timeout); return tt; } // init from the seq id static async fromIdentifier(wh, id, timeout) { const vaa = await TokenTransfer.getTransferVaa(wh, id, timeout); if (!vaa) throw new Error("VAA not found"); const automatic = vaa.protocolName === "AutomaticTokenBridge"; // TODO: the `from.address` here is a lie, but we don't // immediately have enough info to get the _correct_ one let from = { chain: vaa.emitterChain, address: vaa.emitterAddress }; let { token, to } = vaa.payload; let nativeAddress; if (token.chain === from.chain) { nativeAddress = await wh.getTokenNativeAddress(from.chain, token.chain, token.address); } else { const fromChain = await wh.getChain(from.chain); const tb = await fromChain.getTokenBridge(); const wrapped = await tb.getWrappedAsset(token); nativeAddress = (0, sdk_definitions_1.toNative)(from.chain, wrapped.toString()); } const decimals = await wh.getDecimals(from.chain, nativeAddress); const scaledAmount = sdk_base_1.amount.scale(sdk_base_1.amount.fromBaseUnits(token.amount, Math.min(decimals, TokenTransfer.MAX_DECIMALS)), decimals); let nativeGasAmount = 0n; if (automatic) { nativeGasAmount = vaa.payload.payload.toNativeTokenAmount; from = { chain: vaa.emitterChain, address: vaa.payload.from }; to = { chain: vaa.payload.to.chain, address: vaa.payload.payload.targetRecipient }; } const details = { token: token, amount: sdk_base_1.amount.units(scaledAmount), from, to, automatic, nativeGas: nativeGasAmount, }; // TODO: grab at least the init tx from the api const tt = new TokenTransfer(wh, details); tt.attestations = [{ id: id, attestation: vaa }]; tt._state = types_js_1.TransferState.Attested; return tt; } static async fromTransaction(wh, from, timeout, fromChain) { fromChain = fromChain ?? wh.getChain(from.chain); const msg = await TokenTransfer.getTransferMessage(fromChain, from.txid, timeout); const tt = await TokenTransfer.fromIdentifier(wh, msg, timeout); tt.txids = [from]; return tt; } // start the WormholeTransfer by submitting transactions to the source chain // returns a transaction hash async initiateTransfer(signer) { if (this._state !== types_js_1.TransferState.Created) throw new Error("Invalid state transition in `initiateTransfer`"); this.txids = await TokenTransfer.transfer(this.fromChain, this.transfer, signer); this._state = types_js_1.TransferState.SourceInitiated; return this.txids.map(({ txid }) => txid); } // wait for the VAA to be ready // returns the sequence number async fetchAttestation(timeout) { if (this._state < types_js_1.TransferState.SourceInitiated || this._state > types_js_1.TransferState.Attested) throw new Error("Invalid state transition in `fetchAttestation`, expected at least `SourceInitiated`"); if (!this.attestations || this.attestations.length === 0) { if (this.txids.length === 0) throw new Error("No VAAs set and txids available to look them up"); // TODO: assuming the _last_ transaction in the list will contain the msg id const txid = this.txids[this.txids.length - 1]; const msgId = await TokenTransfer.getTransferMessage(this.fromChain, txid.txid, timeout); this.attestations = [{ id: msgId }]; } for (const idx in this.attestations) { // Check if we already have the VAA if (this.attestations[idx].attestation) continue; const vaa = await TokenTransfer.getTransferVaa(this.wh, this.attestations[idx].id, timeout); if (!vaa) throw new Error("VAA not found"); this.attestations[idx].attestation = vaa; } this._state = types_js_1.TransferState.Attested; if (this.attestations.length > 0) { // Check if the transfer has been completed const { attestation } = this.attestations[0]; const completed = await TokenTransfer.isTransferComplete(this.toChain, attestation); if (completed) this._state = types_js_1.TransferState.DestinationFinalized; } return this.attestations.map((vaa) => vaa.id); } // finish the WormholeTransfer by submitting transactions to the destination chain // returns a transaction hash async completeTransfer(signer) { if (this._state < types_js_1.TransferState.Attested) throw new Error("Invalid state transition, must be attested prior to calling `completeTransfer`."); if (!this.attestations) throw new Error("No VAA details available"); const { attestation } = this.attestations[0]; if (!attestation) throw new Error(`No VAA found for ${this.attestations[0].id.sequence}`); const redeemTxids = await TokenTransfer.redeem(this.toChain, attestation, signer); this.txids.push(...redeemTxids); this._state = types_js_1.TransferState.DestinationInitiated; return redeemTxids.map(({ txid }) => txid); } } exports.TokenTransfer = TokenTransfer; (function (TokenTransfer) { /** 8 is maximum precision supported by the token bridge VAA */ TokenTransfer.MAX_DECIMALS = 8; // Static method to perform the transfer so a custom RPC may be used // Note: this assumes the transfer has already been validated with `validateTransfer` async function transfer(fromChain, transfer, signer) { const senderAddress = (0, sdk_definitions_1.toNative)(signer.chain(), signer.address()); const token = (0, sdk_definitions_1.isTokenId)(transfer.token) ? transfer.token.address : transfer.token; let xfer; if (transfer.automatic) { const tb = await fromChain.getAutomaticTokenBridge(); xfer = tb.transfer(senderAddress, transfer.to, token, transfer.amount, transfer.nativeGas); } else { const tb = await fromChain.getTokenBridge(); xfer = tb.transfer(senderAddress, transfer.to, token, transfer.amount, transfer.payload); } return (0, common_js_1.signSendWait)(fromChain, xfer, signer); } TokenTransfer.transfer = transfer; // Static method to allow passing a custom RPC async function redeem(toChain, vaa, signer) { const signerAddress = (0, sdk_definitions_1.toNative)(signer.chain(), signer.address()); const xfer = vaa.protocolName === "AutomaticTokenBridge" ? (await toChain.getAutomaticTokenBridge()).redeem(signerAddress, vaa) : (await toChain.getTokenBridge()).redeem(signerAddress, vaa); return (0, common_js_1.signSendWait)(toChain, xfer, signer); } TokenTransfer.redeem = redeem; // AsyncGenerator fn that produces status updates through an async generator // eventually producing a receipt // can be called repeatedly so the receipt is updated as it moves through the // steps of the transfer async function* track(wh, receipt, timeout = config_js_1.DEFAULT_TASK_TIMEOUT, fromChain, toChain) { const start = Date.now(); const leftover = (start, max) => Math.max(max - (Date.now() - start), 0); fromChain = fromChain ?? wh.getChain(receipt.from); // Check the source chain for initiation transaction // and capture the message id if ((0, types_js_1.isSourceInitiated)(receipt)) { if (receipt.originTxs.length === 0) throw "Origin transactions required to fetch message id"; const { txid } = receipt.originTxs[receipt.originTxs.length - 1]; const msg = await TokenTransfer.getTransferMessage(fromChain, txid, leftover(start, timeout)); receipt = { ...receipt, state: types_js_1.TransferState.SourceFinalized, attestation: { id: msg }, }; yield receipt; } // If the source is finalized or in review (governor held), we need to fetch the signed attestation // (once it's available) so that we may deliver it to the destination chain // or at least track the transfer through its progress if ((0, types_js_1.isSourceFinalized)(receipt) || (0, types_js_1.isInReview)(receipt)) { if (!receipt.attestation.id) throw "Attestation id required to fetch attestation"; const { id } = receipt.attestation; const attestation = await TokenTransfer.getTransferVaa(wh, id, leftover(start, timeout)); if (attestation) { receipt = { ...receipt, attestation: { id, attestation }, state: types_js_1.TransferState.Attested, }; yield receipt; } else { // If the attestation is not found, check if the transfer is held by the governor const isEnqueued = await TokenTransfer.isTransferEnqueued(wh, id); if (isEnqueued) { receipt = { ...receipt, state: types_js_1.TransferState.InReview, }; yield receipt; } } throw new Error("Attestation not found"); } // First try to grab the tx status from the API // Note: this requires a subsequent async step on the backend // to have the dest txid populated, so it may be delayed by some time if ((0, types_js_1.isAttested)(receipt) || (0, types_js_1.isSourceFinalized)(receipt) || (0, types_js_1.isInReview)(receipt)) { if (!receipt.attestation.id) throw "Attestation id required to fetch redeem tx"; const { id } = receipt.attestation; const txStatus = await wh.getTransactionStatus(id, leftover(start, timeout)); if (txStatus && txStatus.globalTx?.destinationTx?.txHash) { const { chainId, txHash } = txStatus.globalTx.destinationTx; receipt = { ...receipt, destinationTxs: [{ chain: (0, sdk_base_1.toChain)(chainId), txid: txHash }], state: types_js_1.TransferState.DestinationInitiated, }; } yield receipt; } // Fall back to asking the destination chain if this VAA has been redeemed // Note: We do not get any destinationTxs with this method if ((0, types_js_1.isAttested)(receipt) || (0, types_js_1.isRedeemed)(receipt)) { if (!receipt.attestation.attestation) throw "Signed Attestation required to check for redeem"; if (receipt.attestation.attestation.payloadName === "AttestMeta") { throw new Error("Unable to track an AttestMeta receipt"); } let isComplete = await TokenTransfer.isTransferComplete(toChain ?? wh.getChain(receipt.attestation.attestation.payload.to.chain), receipt.attestation.attestation); if (isComplete) { receipt = { ...receipt, state: types_js_1.TransferState.DestinationFinalized, }; } yield receipt; } yield receipt; } TokenTransfer.track = track; function getReceipt(xfer) { const { transfer } = xfer; const from = transfer.from.chain; const to = transfer.to.chain; let receipt = { from: from, to: to, state: types_js_1.TransferState.Created, }; const originTxs = xfer.txids.filter((txid) => txid.chain === transfer.from.chain); if (originTxs.length > 0) { receipt = { ...receipt, state: types_js_1.TransferState.SourceInitiated, originTxs: originTxs, }; } const att = xfer.attestations && xfer.attestations.length > 0 ? xfer.attestations[0] : undefined; const attestation = att && att.id ? { id: att.id, attestation: att.attestation } : undefined; if (attestation) { if (attestation.id) { receipt = { ...receipt, state: types_js_1.TransferState.SourceFinalized, attestation: { id: attestation.id }, }; if (attestation.attestation) { receipt = { ...receipt, state: types_js_1.TransferState.Attested, attestation: { id: attestation.id, attestation: attestation.attestation }, }; } } } const destinationTxs = xfer.txids.filter((txid) => txid.chain === transfer.to.chain); if (destinationTxs.length > 0) { receipt = { ...receipt, state: types_js_1.TransferState.DestinationFinalized, destinationTxs: destinationTxs, }; } return receipt; } TokenTransfer.getReceipt = getReceipt; // Lookup the token id for the destination chain given the source chain // and token id async function lookupDestinationToken(srcChain, dstChain, token) { let lookup; const tb = await srcChain.getTokenBridge(); if ((0, sdk_definitions_1.isNative)(token.address)) { // if native, get the wrapped asset id const wrappedNative = await tb.getWrappedNative(); lookup = { chain: token.chain, address: await tb.getTokenUniversalAddress(wrappedNative), }; } else { try { // otherwise, check to see if it is a wrapped token locally let address; if (sdk_definitions_1.UniversalAddress.instanceof(token.address)) { address = (await tb.getWrappedAsset(token)); } else { address = token.address; } lookup = await tb.getOriginalAsset(address); } catch (e) { if (!e.message.includes("not a wrapped asset")) throw e; // not a from-chain native wormhole-wrapped one let address; if (sdk_definitions_1.UniversalAddress.instanceof(token.address)) { address = await tb.getTokenNativeAddress(srcChain.chain, token.address); } else { address = token.address; } lookup = { chain: token.chain, address: await tb.getTokenUniversalAddress(address) }; } } // if the token id is actually native to the destination, return it const dstTb = await dstChain.getTokenBridge(); if (lookup.chain === dstChain.chain) { const nativeAddress = await dstTb.getTokenNativeAddress(lookup.chain, lookup.address); const destWrappedNative = await dstTb.getWrappedNative(); if ((0, sdk_definitions_1.canonicalAddress)({ chain: dstChain.chain, address: destWrappedNative }) === (0, sdk_definitions_1.canonicalAddress)({ chain: dstChain.chain, address: nativeAddress })) { return { chain: dstChain.chain, address: "native" }; } return { chain: dstChain.chain, address: nativeAddress }; } // otherwise, figure out what the token address representing the wormhole-wrapped token we're transferring const dstAddress = await dstTb.getWrappedAsset(lookup); return { chain: dstChain.chain, address: dstAddress }; } TokenTransfer.lookupDestinationToken = lookupDestinationToken; async function isTransferComplete(toChain, vaa) { if (vaa.protocolName === "AutomaticTokenBridge") vaa = (0, sdk_definitions_1.deserialize)("TokenBridge:TransferWithPayload", (0, sdk_definitions_1.serialize)(vaa)); const tb = await toChain.getTokenBridge(); return tb.isTransferCompleted(vaa); } TokenTransfer.isTransferComplete = isTransferComplete; async function getTransferMessage(chain, txid, timeout) { // A Single wormhole message will be returned for a standard token transfer const whm = await wormhole_js_1.Wormhole.parseMessageFromTx(chain, txid, timeout); if (whm.length !== 1) throw new Error("Expected a single Wormhole Message, got: " + whm.length); return whm[0]; } TokenTransfer.getTransferMessage = getTransferMessage; async function getTransferVaa(wh, key, timeout) { const vaa = await wh.getVaa(key, sdk_definitions_1.TokenBridge.getTransferDiscriminator(), timeout); if (!vaa) return null; // Check if its automatic and re-de-serialize if (vaa.payloadName === "TransferWithPayload") { const { chain, address } = vaa.payload.to; const { tokenBridgeRelayer } = wh.config.chains[chain].contracts; const relayerAddress = tokenBridgeRelayer ? (0, sdk_definitions_1.toUniversal)(chain, tokenBridgeRelayer) : null; // If the target address is the relayer address, expect its an automatic token bridge vaa if (!!relayerAddress && address.equals(relayerAddress)) { return (0, sdk_definitions_1.deserialize)("AutomaticTokenBridge:TransferWithRelay", (0, sdk_definitions_1.serialize)(vaa)); } } return vaa; } TokenTransfer.getTransferVaa = getTransferVaa; async function isTransferEnqueued(wh, key) { return await wh.getIsVaaEnqueued(key); } TokenTransfer.isTransferEnqueued = isTransferEnqueued; function validateTransferDetails(wh, transfer, fromChain, toChain) { if (transfer.amount === 0n) throw new Error("Amount cannot be 0"); if (transfer.from.chain === transfer.to.chain) throw new Error("Cannot transfer to the same chain"); fromChain = fromChain ?? wh.getChain(transfer.from.chain); toChain = toChain ?? wh.getChain(transfer.to.chain); if (transfer.automatic) { if (transfer.payload) throw new Error("Payload with automatic delivery is not supported"); if (!fromChain.supportsAutomaticTokenBridge()) throw new Error(`Automatic Token Bridge not supported on ${transfer.from.chain}`); if (!toChain.supportsAutomaticTokenBridge()) throw new Error(`Automatic Token Bridge not supported on ${transfer.to.chain}`); const nativeGas = transfer.nativeGas ?? 0n; if (nativeGas > transfer.amount) throw new Error(`Native gas amount > amount (${nativeGas} > ${transfer.amount})`); } else { if (transfer.nativeGas) throw new Error("Gas Dropoff is only supported for automatic transfers"); if (!fromChain.supportsTokenBridge()) throw new Error(`Token Bridge not supported on ${transfer.from.chain}`); if (!toChain.supportsTokenBridge()) throw new Error(`Token Bridge not supported on ${transfer.to.chain}`); } } TokenTransfer.validateTransferDetails = validateTransferDetails; async function quoteTransfer(wh, srcChain, dstChain, transfer) { const srcTb = await srcChain.getTokenBridge(); let srcToken; if ((0, sdk_definitions_1.isNative)(transfer.token.address)) { srcToken = await srcTb.getWrappedNative(); } else if (sdk_definitions_1.UniversalAddress.instanceof(transfer.token.address)) { try { srcToken = (await srcTb.getWrappedAsset(transfer.token)); } catch (e) { if (!e.message.includes("not a wrapped asset")) throw e; srcToken = await srcTb.getTokenNativeAddress(srcChain.chain, transfer.token.address); } } else { srcToken = transfer.token.address; } // @ts-ignore: TS2339 const srcTokenId = wormhole_js_1.Wormhole.tokenId(srcChain.chain, srcToken.toString()); const srcDecimals = await srcChain.getDecimals(srcToken); const srcAmount = sdk_base_1.amount.fromBaseUnits(transfer.amount, srcDecimals); const srcAmountTruncated = sdk_base_1.amount.truncate(srcAmount, TokenTransfer.MAX_DECIMALS); // Ensure the transfer would not violate governor transfer limits const [tokens, limits] = await Promise.all([ (0, whscan_api_js_1.getGovernedTokens)(wh.config.api), (0, whscan_api_js_1.getGovernorLimits)(wh.config.api), ]); const warnings = []; if (limits !== null && srcChain.chain in limits && tokens !== null) { let origAsset; if ((0, sdk_definitions_1.isNative)(transfer.token.address)) { origAsset = { chain: srcChain.chain, address: await srcTb.getTokenUniversalAddress(srcToken), }; } else { try { origAsset = await srcTb.getOriginalAsset(transfer.token.address); } catch (e) { if (!e.message.includes("not a wrapped asset")) throw e; origAsset = { chain: srcChain.chain, address: await srcTb.getTokenUniversalAddress(srcToken), }; } } if (origAsset.chain in tokens && origAsset.address.toString() in tokens[origAsset.chain]) { const limit = limits[srcChain.chain]; const tokenPrice = tokens[origAsset.chain][origAsset.address.toString()]; const notionalTransferAmt = tokenPrice * sdk_base_1.amount.whole(srcAmountTruncated); if (limit.maxSize && notionalTransferAmt > limit.maxSize) { warnings.push({ type: "GovernorLimitWarning", reason: "ExceedsLargeTransferLimit", }); } if (notionalTransferAmt > limit.available) { warnings.push({ type: "GovernorLimitWarning", reason: "ExceedsRemainingNotional", }); } } } const dstToken = await TokenTransfer.lookupDestinationToken(srcChain, dstChain, transfer.token); const dstDecimals = await dstChain.getDecimals(dstToken.address); const dstAmountReceivable = sdk_base_1.amount.scale(srcAmountTruncated, dstDecimals); const eta = sdk_base_1.finality.estimateFinalityTime(srcChain.chain) + sdk_base_1.guardians.guardianAttestationEta; if (!transfer.automatic) { return { sourceToken: { token: transfer.token, amount: sdk_base_1.amount.units(srcAmountTruncated), }, destinationToken: { token: dstToken, amount: sdk_base_1.amount.units(dstAmountReceivable) }, warnings: warnings.length > 0 ? warnings : undefined, eta, expires: sdk_base_1.time.expiration(24, 0, 0), // manual transfer quote is good for 24 hours }; } // Otherwise automatic // The fee is removed from the amount transferred // quoted on the source chain const stb = await srcChain.getAutomaticTokenBridge(); const fee = await stb.getRelayerFee(dstChain.chain, srcToken); const feeAmountDest = sdk_base_1.amount.scale(sdk_base_1.amount.truncate(sdk_base_1.amount.fromBaseUnits(fee, srcDecimals), TokenTransfer.MAX_DECIMALS), dstDecimals); // nativeGas is in source chain decimals const srcNativeGasAmountRequested = transfer.nativeGas ?? 0n; // convert to destination chain decimals const dstNativeGasAmountRequested = sdk_base_1.amount.units(sdk_base_1.amount.scale(sdk_base_1.amount.truncate(sdk_base_1.amount.fromBaseUnits(srcNativeGasAmountRequested, srcDecimals), TokenTransfer.MAX_DECIMALS), dstDecimals)); // TODO: consider moving these solana specific checks to its protocol implementation const solanaMinBalanceForRentExemptAccount = 890880n; let destinationNativeGas = 0n; if (transfer.nativeGas) { const dtb = await dstChain.getAutomaticTokenBridge(); // There is a limit applied to the amount of the source // token that may be swapped for native gas on the destination const [maxNativeAmountIn, _destinationNativeGas] = await Promise.all([ dtb.maxSwapAmount(dstToken.address), // Get the actual amount we should receive dtb.nativeTokenAmount(dstToken.address, dstNativeGasAmountRequested), ]); if (dstNativeGasAmountRequested > maxNativeAmountIn) throw new Error(`Native gas amount exceeds maximum swap amount: ${sdk_base_1.amount.fmt(dstNativeGasAmountRequested, dstDecimals)}>${sdk_base_1.amount.fmt(maxNativeAmountIn, dstDecimals)}`); // when native gas is requested on solana, the amount must be at least the rent-exempt amount // or the transaction could fail if the account does not have enough lamports if ((0, sdk_base_2.chainToPlatform)(dstChain.chain) === "Solana" && _destinationNativeGas < solanaMinBalanceForRentExemptAccount) { throw new Error(`Native gas amount must be at least ${solanaMinBalanceForRentExemptAccount} lamports`); } destinationNativeGas = _destinationNativeGas; } const destAmountLessFee = sdk_base_1.amount.units(dstAmountReceivable) - dstNativeGasAmountRequested - sdk_base_1.amount.units(feeAmountDest); // when sending wsol to solana, the amount must be at least the rent-exempt amount // or the transaction could fail if the account does not have enough lamports if ((0, sdk_base_2.chainToPlatform)(dstToken.chain) === "Solana") { const nativeWrappedTokenId = await dstChain.getNativeWrappedTokenId(); const isNativeSol = (0, sdk_definitions_1.isNative)(dstToken.address) || (0, sdk_definitions_1.isSameToken)(dstToken, nativeWrappedTokenId); if (isNativeSol && destAmountLessFee < solanaMinBalanceForRentExemptAccount) { throw new Error(`Destination amount must be at least ${solanaMinBalanceForRentExemptAccount} lamports`); } } return { sourceToken: { token: transfer.token, amount: sdk_base_1.amount.units(srcAmountTruncated), }, destinationToken: { token: dstToken, amount: destAmountLessFee }, relayFee: { token: dstToken, amount: sdk_base_1.amount.units(feeAmountDest) }, destinationNativeGas, warnings: warnings.length > 0 ? warnings : undefined, eta, expires: sdk_base_1.time.expiration(0, 5, 0), // automatic transfer quote is good for 5 minutes }; } TokenTransfer.quoteTransfer = quoteTransfer; async function destinationOverrides(srcChain, dstChain, transfer) { const _transfer = { ...transfer }; // Bit of (temporary) hackery until solana contracts support being // sent a VAA with the primary address // Note: Do _not_ override if automatic or if the destination token is native // gas token if ((0, sdk_base_2.chainToPlatform)(transfer.to.chain) === "Solana" && !_transfer.automatic) { const destinationToken = await TokenTransfer.lookupDestinationToken(srcChain, dstChain, _transfer.token); if ((0, sdk_definitions_1.isNative)(destinationToken.address)) { const nativeWrappedTokenId = await dstChain.getNativeWrappedTokenId(); _transfer.to = await dstChain.getTokenAccount(_transfer.to.address, nativeWrappedTokenId.address); } else { _transfer.to = await dstChain.getTokenAccount(_transfer.to.address, destinationToken.address); } } if (_transfer.to.chain === "Sei") { if (_transfer.to.chain === "Sei" && _transfer.payload) throw new Error("Arbitrary payloads unsupported for Sei"); // For sei, we reserve the payload for a token transfer through the sei bridge. _transfer.payload = sdk_base_1.encoding.bytes.encode(JSON.stringify({ basic_recipient: { recipient: sdk_base_1.encoding.b64.encode(_transfer.to.address.toString()), }, })); const translator = dstChain.config.contracts.translator; if (translator === undefined || translator === "") throw new Error("Unexpected empty translator address"); _transfer.to = wormhole_js_1.Wormhole.chainAddress(_transfer.to.chain, translator); } return _transfer; } TokenTransfer.destinationOverrides = destinationOverrides; })(TokenTransfer || (exports.TokenTransfer = TokenTransfer = {})); //# sourceMappingURL=tokenTransfer.js.map