UNPKG

@wormhole-foundation/sdk-evm-portico

Version:

SDK for EVM chains, used in conjunction with @wormhole-foundation/sdk

192 lines 9.88 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.EvmPorticoBridge = void 0; const sdk_connect_1 = require("@wormhole-foundation/sdk-connect"); const sdk_evm_1 = require("@wormhole-foundation/sdk-evm"); const ethers_1 = require("ethers"); const abis_js_1 = require("./abis.js"); const api_js_1 = require("./api.js"); const consts_js_1 = require("./consts.js"); const sdk_evm_core_1 = require("@wormhole-foundation/sdk-evm-core"); const sdk_evm_tokenbridge_1 = require("@wormhole-foundation/sdk-evm-tokenbridge"); require("@wormhole-foundation/sdk-evm-tokenbridge"); class EvmPorticoBridge { network; chain; provider; contracts; chainId; core; tokenBridge; constructor(network, chain, provider, contracts) { this.network = network; this.chain = chain; this.provider = provider; this.contracts = contracts; if (!contracts.portico) throw new Error('Unsupported chain, no contract addresses for: ' + chain); this.core = new sdk_evm_core_1.EvmWormholeCore(network, chain, provider, contracts); this.tokenBridge = new sdk_evm_tokenbridge_1.EvmTokenBridge(network, chain, provider, contracts); this.chainId = sdk_connect_1.nativeChainIds.networkChainToNativeChainId.get(network, chain); } static async fromRpc(provider, config) { const [network, chain] = await sdk_evm_1.EvmPlatform.chainFromRpc(provider); const conf = config[chain]; if (conf.network !== network) throw new Error(`Network mismatch: ${conf.network} != ${network}`); return new EvmPorticoBridge(network, chain, provider, conf.contracts); } async *transfer(sender, receiver, token, amount, destToken, destinationPorticoAddress, quote) { const { minAmountStart, minAmountFinish } = quote.swapAmounts; if (minAmountStart === 0n) throw new Error('Invalid min swap amount'); if (minAmountFinish === 0n) throw new Error('Invalid min swap amount'); const senderAddress = new sdk_evm_1.EvmAddress(sender).toString(); const [isStartTokenNative, startToken] = (0, sdk_connect_1.resolveWrappedToken)(this.network, this.chain, token); const [isFinalTokenNative, finalToken] = (0, sdk_connect_1.resolveWrappedToken)(this.network, receiver.chain, destToken); const startTokenAddress = (0, sdk_connect_1.canonicalAddress)(startToken); const cannonTokenAddress = (0, sdk_connect_1.canonicalAddress)(await this.getTransferrableToken(startTokenAddress)); const receiverAddress = (0, sdk_connect_1.canonicalAddress)(receiver); const finalTokenAddress = (0, sdk_connect_1.canonicalAddress)(finalToken); const nonce = new Date().valueOf() % 2 ** 4; const flags = sdk_connect_1.PorticoBridge.serializeFlagSet({ flags: { shouldWrapNative: isStartTokenNative, shouldUnwrapNative: isFinalTokenNative, }, recipientChain: (0, sdk_connect_1.toChainId)(receiver.chain), bridgeNonce: nonce, feeTierStart: consts_js_1.FEE_TIER, feeTierFinish: consts_js_1.FEE_TIER, padding: new Uint8Array(19), }); const transactionData = abis_js_1.porticoAbi.encodeFunctionData('start', [ [ flags, startTokenAddress.toLowerCase(), cannonTokenAddress, finalTokenAddress.toLowerCase(), receiverAddress, destinationPorticoAddress, amount.toString(), minAmountStart.toString(), minAmountFinish.toString(), quote.relayerFee.toString(), ], ]); const group = this.getTokenGroup(startToken.address.toString()); const porticoAddress = this.getPorticoAddress(group); // Approve the token if necessary if (!isStartTokenNative) yield* this.approve(startTokenAddress, senderAddress, amount, porticoAddress); const messageFee = await this.core.getMessageFee(); const tx = { to: porticoAddress, data: transactionData, value: messageFee + (isStartTokenNative ? amount : 0n), }; yield this.createUnsignedTransaction((0, sdk_evm_1.addFrom)(tx, senderAddress), 'PorticoBridge.Transfer'); } async *redeem(sender, vaa) { const recipientChain = (0, sdk_connect_1.toChain)(vaa.payload.payload.flagSet.recipientChain); const tokenAddress = vaa.payload.payload.finalTokenAddress .toNative(recipientChain) .toString(); const group = this.getTokenGroup(tokenAddress); const porticoAddress = this.getPorticoAddress(group); const contract = new ethers_1.ethers.Contract(porticoAddress, abis_js_1.porticoAbi.fragments, this.provider); const txReq = await contract .getFunction('receiveMessageAndSwap') .populateTransaction((0, sdk_connect_1.serialize)(vaa)); const address = new sdk_evm_1.EvmAddress(sender).toString(); yield this.createUnsignedTransaction((0, sdk_evm_1.addFrom)(txReq, address), 'PorticoBridge.Redeem'); } async isTransferCompleted(vaa) { const isCompleted = await this.tokenBridge.tokenBridge.isTransferCompleted((0, ethers_1.keccak256)(vaa.hash)); return isCompleted; } async quoteSwap(input, output, tokenGroup, amount) { const [, inputTokenId] = (0, sdk_connect_1.resolveWrappedToken)(this.network, this.chain, input); const [, outputTokenId] = (0, sdk_connect_1.resolveWrappedToken)(this.network, this.chain, output); const inputAddress = (0, sdk_connect_1.canonicalAddress)(inputTokenId); const outputAddress = (0, sdk_connect_1.canonicalAddress)(outputTokenId); if ((0, sdk_connect_1.isEqualCaseInsensitive)(inputAddress, outputAddress)) return amount; const quoterAddress = this.getQuoterAddress(tokenGroup); const quoter = new ethers_1.ethers.Contract(quoterAddress, abis_js_1.uniswapQuoterV2Abi.fragments, this.provider); const result = await quoter .getFunction('quoteExactInputSingle') .staticCall([inputAddress, outputAddress, amount, consts_js_1.FEE_TIER, 0]); return result[0]; } async quoteRelay(startToken, endToken) { return await api_js_1.PorticoApi.quoteRelayer(this.chain, startToken, endToken); } // For a given token, return the Wormhole-wrapped/highway token // that actually gets bridged from this chain async getTransferrableToken(address) { const token = sdk_connect_1.Wormhole.tokenId(this.chain, address); const [, wrappedToken] = (0, sdk_connect_1.resolveWrappedToken)(this.network, this.chain, token); if (this.chain === 'Ethereum') return wrappedToken; // Find the group that this token belongs to const group = Object.values(consts_js_1.supportedTokens).find((tokens) => tokens.find((t) => t.chain === this.chain && (0, sdk_connect_1.canonicalAddress)(t) === (0, sdk_connect_1.canonicalAddress)(wrappedToken))); if (!group) throw new Error(`No token group found for ${address} on ${this.chain}`); // Find the token in this group that originates on Ethereum const tokenOnEthereum = group.find((t) => t.chain === 'Ethereum'); if (!tokenOnEthereum) throw new Error(`No Ethereum origin token found for ${address} on ${this.chain}`); // Now find the corresponding Wormhole-wrapped/highway token on this chain const highwayTokenAddr = await this.tokenBridge.getWrappedAsset(tokenOnEthereum); return sdk_connect_1.Wormhole.tokenId(this.chain, highwayTokenAddr.toString()); } supportedTokens() { const result = []; for (const [group, tokens] of Object.entries(consts_js_1.supportedTokens)) { for (const token of tokens) { if (token.chain === this.chain) result.push({ group, token }); } } return result; } getTokenGroup(address) { const tokens = this.supportedTokens(); const token = tokens.find((t) => (0, sdk_connect_1.canonicalAddress)(t.token) === address); if (!token) throw new Error('Token not found'); return token.group; } async *approve(token, senderAddr, amount, contract) { const tokenContract = sdk_evm_1.EvmPlatform.getTokenImplementation(this.provider, token); const allowance = await tokenContract.allowance(senderAddr, contract); if (allowance < amount) { const txReq = await tokenContract.approve.populateTransaction(contract, amount); yield this.createUnsignedTransaction((0, sdk_evm_1.addFrom)(txReq, senderAddr), 'PorticoBridge.Approve'); } } createUnsignedTransaction(txReq, description) { return new sdk_evm_1.EvmUnsignedTransaction((0, sdk_evm_1.addChainId)(txReq, this.chainId), this.network, this.chain, description, false); } getPorticoAddress(group) { const portico = this.contracts.portico; if (group === 'USDT') { // Use PancakeSwap if available for USDT return portico.porticoPancakeSwap || portico.porticoUniswap; } return portico.porticoUniswap; } getQuoterAddress(group) { const portico = this.contracts.portico; if (group === 'USDT') { // Use PancakeSwap if available for USDT return portico.pancakeSwapQuoterV2 || portico.uniswapQuoterV2; } return portico.uniswapQuoterV2; } } exports.EvmPorticoBridge = EvmPorticoBridge; //# sourceMappingURL=bridge.js.map