@wormhole-foundation/sdk-evm-portico
Version:
SDK for EVM chains, used in conjunction with @wormhole-foundation/sdk
192 lines • 9.88 kB
JavaScript
"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