@pushchain/core
Version:
Push Chain is a true universal L1 that is 100% EVM compatible. It allows developers to deploy once and make their apps instantly compatible with users from all other L1s (Ethereum, Solana, etc) with zero on-chain code change.
707 lines • 147 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Orchestrator = void 0;
const tslib_1 = require("tslib");
const anchor_1 = require("@coral-xyz/anchor");
const web3_js_1 = require("@solana/web3.js");
const viem_1 = require("viem");
const abi_1 = require("../constants/abi");
const uea_evm_1 = require("../constants/abi/uea.evm");
const chain_1 = require("../constants/chain");
const enums_1 = require("../constants/enums");
const tokens_1 = require("../constants/tokens");
const tx_1 = require("../generated/v1/tx");
const price_fetch_1 = require("../price-fetch/price-fetch");
const progress_hook_1 = tslib_1.__importDefault(require("../progress-hook/progress-hook"));
const progress_hook_types_1 = require("../progress-hook/progress-hook.types");
const push_chain_1 = require("../push-chain/push-chain");
const push_client_1 = require("../push-client/push-client");
const evm_client_1 = require("../vm-client/evm-client");
const svm_client_1 = require("../vm-client/svm-client");
const payload_builders_1 = require("./payload-builders");
class Orchestrator {
constructor(universalSigner, pushNetwork, rpcUrls = {}, printTraces = false, progressHook) {
this.universalSigner = universalSigner;
this.pushNetwork = pushNetwork;
this.rpcUrls = rpcUrls;
this.printTraces = printTraces;
this.progressHook = progressHook;
let pushChain;
if (pushNetwork === enums_1.PUSH_NETWORK.MAINNET) {
pushChain = enums_1.CHAIN.PUSH_MAINNET;
}
else if (pushNetwork === enums_1.PUSH_NETWORK.TESTNET_DONUT ||
pushNetwork === enums_1.PUSH_NETWORK.TESTNET) {
pushChain = enums_1.CHAIN.PUSH_TESTNET_DONUT;
}
else {
pushChain = enums_1.CHAIN.PUSH_LOCALNET;
}
const pushChainRPCs = this.rpcUrls[pushChain] || chain_1.CHAIN_INFO[pushChain].defaultRPC;
this.pushClient = new push_client_1.PushClient({
rpcUrls: pushChainRPCs,
network: pushNetwork,
});
}
/**
* Read-only accessors for current Orchestrator configuration
*/
getNetwork() {
return this.pushNetwork;
}
getRpcUrls() {
return this.rpcUrls;
}
getPrintTraces() {
return this.printTraces;
}
getProgressHook() {
return this.progressHook;
}
/**
* Executes an interaction on Push Chain
*/
execute(execute) {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
try {
if (execute.funds) {
if (!execute.data || execute.data === '0x') {
const chain = this.universalSigner.account.chain;
const { vm } = chain_1.CHAIN_INFO[chain];
if (!(chain === enums_1.CHAIN.ETHEREUM_SEPOLIA ||
chain === enums_1.CHAIN.ARBITRUM_SEPOLIA ||
chain === enums_1.CHAIN.BASE_SEPOLIA ||
chain === enums_1.CHAIN.BNB_TESTNET ||
chain === enums_1.CHAIN.SOLANA_DEVNET)) {
throw new Error('Funds bridging is only supported on Ethereum Sepolia, Arbitrum Sepolia, Base Sepolia, BNB Testnet, and Solana Devnet for now');
}
// Progress: Origin chain detected
this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_01, chain, this.universalSigner.account.address);
const { defaultRPC, lockerContract } = chain_1.CHAIN_INFO[chain];
const rpcUrls = this.rpcUrls[chain] || defaultRPC;
// Resolve token: default to native token based on VM (ETH for EVM, SOL for SVM)
if (!execute.funds.token) {
const available = tokens_1.MOVEABLE_TOKENS[chain] || [];
const vm = chain_1.CHAIN_INFO[chain].vm;
const preferredSymbol = vm === enums_1.VM.EVM ? 'ETH' : vm === enums_1.VM.SVM ? 'SOL' : undefined;
const nativeToken = preferredSymbol
? available.find((t) => t.symbol === preferredSymbol)
: undefined;
if (!nativeToken) {
throw new Error('Native token not configured for this chain');
}
execute.funds.token = nativeToken;
}
const amount = execute.funds.amount;
const symbol = execute.funds.token.symbol;
const bridgeAmount = amount;
const revertCFG = {
fundRecipient: this.universalSigner.account
.address,
revertMsg: '0x',
}; // typed by viem via ABI
if (vm === enums_1.VM.EVM) {
const evmClient = new evm_client_1.EvmClient({ rpcUrls });
const gatewayAddress = lockerContract;
const tokenAddr = execute.funds.token.address;
const recipient = execute.to; // funds to recipient on Push Chain
const isNative = execute.funds.token.mechanism === 'native';
const bridgeToken = execute.funds.token.mechanism === 'approve'
? tokenAddr
: '0x0000000000000000000000000000000000000000';
const { nonce, deployed } = yield this.getUeaStatusAndNonce();
const { payload: universalPayload, req } = yield this.buildGatewayPayloadAndGas(execute, nonce, 'sendFunds', bridgeAmount);
const ueaAddress = this.computeUEAOffchain();
// Compute minimal native amount to deposit for gas on Push Chain
const ueaBalanceForGas = yield this.pushClient.getBalance(ueaAddress);
const nativeAmount = yield this.calculateNativeAmountForDeposit(chain, BigInt(0), ueaBalanceForGas);
// We log the SEND_TX_03_01 here because the progress hook for gas estimation should arrive before the resolving of UEA.
this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_03_01);
this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_03_02, ueaAddress, deployed);
this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_06_01, amount, execute.funds.token.decimals, symbol);
if (vm === enums_1.VM.EVM) {
const evmClient = new evm_client_1.EvmClient({ rpcUrls });
const gatewayAddress = lockerContract;
const tokenAddr = execute.funds.token.address;
// Approve gateway to pull tokens if ERC-20 (not native sentinel)
if (execute.funds.token.mechanism === 'approve') {
yield this.ensureErc20Allowance(evmClient, tokenAddr, gatewayAddress, amount);
}
else if (execute.funds.token.mechanism === 'permit2') {
throw new Error('Permit2 is not supported yet');
}
else if (execute.funds.token.mechanism === 'native') {
// Native flow uses msg.value == bridgeAmount and bridgeToken = address(0)
}
}
let txHash;
try {
// FUNDS ONLY SELF
if (execute.to.toLowerCase() === ueaAddress.toLowerCase()) {
// const payloadBytes = this.encodeUniversalPayload(
// universalPayload as unknown as UniversalPayload
// );
// const req = this._buildUniversalTxRequest({
// recipient: zeroAddress,
// token: bridgeToken,
// amount: bridgeAmount,
// payload: '0x',
// });
// const req: UniversalTxRequest = {
// recipient: zeroAddress,
// token: bridgeToken,
// amount: bridgeAmount,
// payload: '0x',
// // payload: payloadBytes,
// revertInstruction: revertCFG,
// signatureData: '0x',
// } as unknown as never;
txHash = yield evmClient.writeContract({
abi: abi_1.UNIVERSAL_GATEWAY_V0,
address: gatewayAddress,
functionName: 'sendUniversalTx',
args: [req],
signer: this.universalSigner,
value: isNative ? bridgeAmount : BigInt(0),
});
}
else {
// FUNDS ONLY OTHER
// const payloadBytes = this.encodeUniversalPayload(
// universalPayload as unknown as UniversalPayload
// );
// const req: UniversalTxRequest = {
// recipient,
// token: bridgeToken,
// amount: bridgeAmount,
// payload: payloadBytes,
// revertInstruction: revertCFG,
// signatureData: '0x',
// } as unknown as never;
txHash = yield evmClient.writeContract({
abi: abi_1.UNIVERSAL_GATEWAY_V0,
address: gatewayAddress,
functionName: 'sendUniversalTx',
args: [req],
signer: this.universalSigner,
value: nativeAmount,
});
}
}
catch (err) {
this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_04_04);
throw err;
}
const originTx = yield this.fetchOriginChainTransactionForProgress(chain, txHash, txHash);
this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_06_02, txHash, bridgeAmount, execute.funds.token.decimals, symbol, originTx);
yield this.waitForEvmConfirmationsWithCountdown(evmClient, txHash, chain_1.CHAIN_INFO[chain].confirmations, chain_1.CHAIN_INFO[chain].timeout);
const pushChainUniversalTx = yield this.queryUniversalTxStatusFromGatewayTx(evmClient, gatewayAddress, txHash, execute.to === ueaAddress ? 'sendFunds' : 'sendTxWithFunds');
this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_06_04);
const lastPcTransaction = pushChainUniversalTx === null || pushChainUniversalTx === void 0 ? void 0 : pushChainUniversalTx.pcTx.at(-1);
const tx = yield this.pushClient.getTransaction(lastPcTransaction === null || lastPcTransaction === void 0 ? void 0 : lastPcTransaction.txHash);
const response = yield this.transformToUniversalTxResponse(tx);
// Funds Flow: Funds credited on Push Chain
this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_06_05, bridgeAmount, execute.funds.token.decimals, symbol);
this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_99_01, [response]);
return response;
}
else {
// SVM path (Solana Devnet)
const svmClient = new svm_client_1.SvmClient({ rpcUrls });
const programId = new web3_js_1.PublicKey(abi_1.SVM_GATEWAY_IDL.address);
const [configPda] = web3_js_1.PublicKey.findProgramAddressSync([(0, viem_1.stringToBytes)('config')], programId);
const [vaultPda] = web3_js_1.PublicKey.findProgramAddressSync([(0, viem_1.stringToBytes)('vault')], programId);
const [whitelistPda] = web3_js_1.PublicKey.findProgramAddressSync([(0, viem_1.stringToBytes)('whitelist')], programId);
const [rateLimitConfigPda] = web3_js_1.PublicKey.findProgramAddressSync([(0, viem_1.stringToBytes)('rate_limit_config')], programId);
const userPk = new web3_js_1.PublicKey(this.universalSigner.account.address);
const priceUpdatePk = new web3_js_1.PublicKey('7UVimffxr9ow1uXYxsr4LHAcV58mLzhmwaeKvJ1pjLiE');
// pay-with-token gas abstraction is not supported on Solana
if (execute.payGasWith !== undefined) {
throw new Error('Pay-with token is not supported on Solana');
}
let txSignature;
// SVM-specific RevertSettings: bytes must be a Buffer
const revertSvm = {
fundRecipient: userPk,
revertMsg: Buffer.from([]),
};
// New gateway expects EVM recipient as [u8; 20]
const recipientEvm20 = Array.from(Buffer.from(execute.to.slice(2).padStart(40, '0'), 'hex').subarray(0, 20));
this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_03_01);
const ueaAddress = this.computeUEAOffchain();
const { nonce, deployed } = yield this.getUeaStatusAndNonce();
this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_03_02, ueaAddress, deployed);
if (execute.funds.token.mechanism === 'native') {
// Native SOL funds-only
const [tokenRateLimitPda] = web3_js_1.PublicKey.findProgramAddressSync([(0, viem_1.stringToBytes)('rate_limit'), web3_js_1.PublicKey.default.toBuffer()], programId);
const recipientNative = execute.to === ueaAddress
? Array.from(Buffer.alloc(20, 0))
: recipientEvm20;
const reqNative = this._buildSvmUniversalTxRequest({
recipient: recipientNative,
token: web3_js_1.PublicKey.default,
amount: bridgeAmount,
payload: '0x',
fundRecipient: userPk,
signatureData: '0x',
});
txSignature = yield svmClient.writeContract({
abi: abi_1.SVM_GATEWAY_IDL,
address: programId.toBase58(),
functionName: 'sendUniversalTx',
args: [reqNative, bridgeAmount],
signer: this.universalSigner,
accounts: {
config: configPda,
vault: vaultPda,
userTokenAccount: vaultPda,
gatewayTokenAccount: vaultPda,
user: userPk,
priceUpdate: priceUpdatePk,
rateLimitConfig: rateLimitConfigPda,
tokenRateLimit: tokenRateLimitPda,
tokenProgram: new web3_js_1.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'),
systemProgram: web3_js_1.SystemProgram.programId,
},
});
}
else if (execute.funds.token.mechanism === 'approve') {
// SPL token funds-only (requires pre-existing ATAs)
const mintPk = new web3_js_1.PublicKey(execute.funds.token.address);
// Associated Token Accounts
const TOKEN_PROGRAM_ID = new web3_js_1.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA');
const ASSOCIATED_TOKEN_PROGRAM_ID = new web3_js_1.PublicKey('ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL');
const userAta = web3_js_1.PublicKey.findProgramAddressSync([
userPk.toBuffer(),
TOKEN_PROGRAM_ID.toBuffer(),
mintPk.toBuffer(),
], ASSOCIATED_TOKEN_PROGRAM_ID)[0];
const vaultAta = web3_js_1.PublicKey.findProgramAddressSync([
vaultPda.toBuffer(),
TOKEN_PROGRAM_ID.toBuffer(),
mintPk.toBuffer(),
], ASSOCIATED_TOKEN_PROGRAM_ID)[0];
const [tokenRateLimitPda] = web3_js_1.PublicKey.findProgramAddressSync([(0, viem_1.stringToBytes)('rate_limit'), mintPk.toBuffer()], programId);
if (execute.to === ueaAddress) {
const recipientSpl = Array.from(Buffer.alloc(20, 0));
const reqSpl = this._buildSvmUniversalTxRequest({
recipient: recipientSpl,
token: mintPk,
amount: bridgeAmount,
payload: '0x',
fundRecipient: userPk,
signatureData: '0x',
});
txSignature = yield svmClient.writeContract({
abi: abi_1.SVM_GATEWAY_IDL,
address: programId.toBase58(),
functionName: 'sendUniversalTx',
args: [reqSpl, BigInt(0)],
signer: this.universalSigner,
accounts: {
config: configPda,
vault: vaultPda,
userTokenAccount: userAta,
gatewayTokenAccount: vaultAta,
user: userPk,
tokenProgram: TOKEN_PROGRAM_ID,
priceUpdate: priceUpdatePk,
rateLimitConfig: rateLimitConfigPda,
tokenRateLimit: tokenRateLimitPda,
systemProgram: web3_js_1.SystemProgram.programId,
},
});
}
else {
const recipientSpl = recipientEvm20;
// vitalik
const reqSpl = this._buildSvmUniversalTxRequest({
recipient: recipientSpl,
token: mintPk,
amount: bridgeAmount,
payload: '0x',
fundRecipient: userPk,
signatureData: '0x',
});
txSignature = yield svmClient.writeContract({
abi: abi_1.SVM_GATEWAY_IDL,
address: programId.toBase58(),
functionName: 'sendUniversalTx',
args: [reqSpl, BigInt(0)],
signer: this.universalSigner,
accounts: {
config: configPda,
vault: vaultPda,
userTokenAccount: userAta,
gatewayTokenAccount: vaultAta,
user: userPk,
tokenProgram: TOKEN_PROGRAM_ID,
priceUpdate: priceUpdatePk,
rateLimitConfig: rateLimitConfigPda,
tokenRateLimit: tokenRateLimitPda,
systemProgram: web3_js_1.SystemProgram.programId,
},
});
}
}
else {
throw new Error('Unsupported token mechanism on Solana');
}
const originTx = yield this.fetchOriginChainTransactionForProgress(chain, '0x', txSignature);
this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_06_02, txSignature, bridgeAmount, execute.funds.token.decimals, symbol, originTx);
yield this.waitForSvmConfirmationsWithCountdown(svmClient, txSignature, chain_1.CHAIN_INFO[chain].confirmations, chain_1.CHAIN_INFO[chain].timeout);
// After origin confirmations, query Push Chain for UniversalTx status (SVM)
const pushChainUniversalTx = yield this.queryUniversalTxStatusFromGatewayTx(undefined, undefined, txSignature, 'sendFunds');
this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_06_04);
const lastPcTransaction = pushChainUniversalTx === null || pushChainUniversalTx === void 0 ? void 0 : pushChainUniversalTx.pcTx.at(-1);
const tx = yield this.pushClient.getTransaction(lastPcTransaction === null || lastPcTransaction === void 0 ? void 0 : lastPcTransaction.txHash);
const response = yield this.transformToUniversalTxResponse(tx);
// Funds Flow: Funds credited on Push Chain
this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_06_05, bridgeAmount, execute.funds.token.decimals, symbol);
this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_99_01, [response]);
return response;
}
}
else {
// Bridge funds + execute payload. Support:
// - EVM (Sepolia): ERC-20 approve path + native gas via msg.value
// - SVM (Solana Devnet): SPL or native SOL with gas_amount
const { chain, evmClient, gatewayAddress } = this.getOriginGatewayContext();
this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_01, chain, this.universalSigner.account.address);
// Default token to native ETH if none provided
if (!execute.funds.token) {
const available = tokens_1.MOVEABLE_TOKENS[chain] || [];
const vm = chain_1.CHAIN_INFO[chain].vm;
const preferredSymbol = vm === enums_1.VM.EVM ? 'ETH' : vm === enums_1.VM.SVM ? 'SOL' : undefined;
const nativeToken = preferredSymbol
? available.find((t) => t.symbol === preferredSymbol)
: undefined;
if (!nativeToken) {
throw new Error('Native token not configured for this chain');
}
execute.funds.token = nativeToken;
}
const mechanism = execute.funds.token.mechanism;
const { deployed, nonce } = yield this.getUeaStatusAndNonce();
const { payload: universalPayload, req } = yield this.buildGatewayPayloadAndGas(execute, nonce, 'sendTxWithFunds', execute.funds.amount);
this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_02_01);
// Compute required gas funding on Push Chain and current UEA balance
const gasEstimate = execute.gasLimit || BigInt(1e7);
const gasPrice = yield this.pushClient.getGasPrice();
const requiredGasFee = gasEstimate * gasPrice;
const payloadValue = (_a = execute.value) !== null && _a !== void 0 ? _a : BigInt(0);
const requiredFunds = requiredGasFee + payloadValue;
const ueaAddress = this.computeUEAOffchain();
const [ueaBalance] = yield Promise.all([
this.pushClient.getBalance(ueaAddress),
]);
// UEA resolved (address, deployment status, balance, nonce)
this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_03_02, ueaAddress, deployed);
// Determine USD to deposit via gateway (8 decimals) with caps: min=$1, max=$10
const oneUsd = push_chain_1.PushChain.utils.helpers.parseUnits('1', 8);
const tenUsd = push_chain_1.PushChain.utils.helpers.parseUnits('10', 8);
const deficit = requiredFunds > ueaBalance ? requiredFunds - ueaBalance : BigInt(0);
let depositUsd = deficit > BigInt(0) ? this.pushClient.pushToUSDC(deficit) : oneUsd;
if (depositUsd < oneUsd)
depositUsd = oneUsd;
if (depositUsd > tenUsd)
throw new Error('Deposit value exceeds max $10 worth of native token');
this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_02_02, depositUsd);
// If SVM, clamp depositUsd to on-chain Config caps
if (chain_1.CHAIN_INFO[chain].vm === enums_1.VM.SVM) {
const svmClient = new svm_client_1.SvmClient({
rpcUrls: this.rpcUrls[enums_1.CHAIN.SOLANA_DEVNET] ||
chain_1.CHAIN_INFO[enums_1.CHAIN.SOLANA_DEVNET].defaultRPC,
});
const programId = new web3_js_1.PublicKey(abi_1.SVM_GATEWAY_IDL.address);
const [configPda] = web3_js_1.PublicKey.findProgramAddressSync([(0, viem_1.stringToBytes)('config')], programId);
try {
const cfg = yield svmClient.readContract({
abi: abi_1.SVM_GATEWAY_IDL,
address: abi_1.SVM_GATEWAY_IDL.address,
functionName: 'config',
args: [configPda.toBase58()],
});
const minField = (_b = cfg.minCapUniversalTxUsd) !== null && _b !== void 0 ? _b : cfg.min_cap_universal_tx_usd;
const maxField = (_c = cfg.maxCapUniversalTxUsd) !== null && _c !== void 0 ? _c : cfg.max_cap_universal_tx_usd;
const minCapUsd = BigInt(minField.toString());
const maxCapUsd = BigInt(maxField.toString());
if (depositUsd < minCapUsd)
depositUsd = minCapUsd;
// Add 20% safety margin to avoid BelowMinCap due to price drift
const withMargin = (minCapUsd * BigInt(12)) / BigInt(10);
if (depositUsd < withMargin)
depositUsd = withMargin;
if (depositUsd > maxCapUsd)
depositUsd = maxCapUsd;
}
catch (_k) {
// best-effort; fallback to previous bounds if read fails
}
}
// Convert USD(8) -> native units using pricing path
const nativeTokenUsdPrice = yield new price_fetch_1.PriceFetch(this.rpcUrls).getPrice(chain); // 8 decimals
const nativeDecimals = chain_1.CHAIN_INFO[chain].vm === enums_1.VM.SVM ? 9 : 18;
const oneNativeUnit = push_chain_1.PushChain.utils.helpers.parseUnits('1', nativeDecimals);
// Ceil division to avoid rounding below min USD on-chain
let nativeAmount = (depositUsd * oneNativeUnit + (nativeTokenUsdPrice - BigInt(1))) /
nativeTokenUsdPrice;
// Add 1 unit safety to avoid BelowMinCap from rounding differences
nativeAmount = nativeAmount + BigInt(1);
const revertCFG = {
fundRecipient: this.universalSigner.account
.address,
revertMsg: '0x',
};
const bridgeAmount = execute.funds.amount;
const symbol = execute.funds.token.symbol;
// Funds Flow: Preparing funds transfer
this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_06_01, bridgeAmount, execute.funds.token.decimals, symbol);
if (chain_1.CHAIN_INFO[this.universalSigner.account.chain].vm === enums_1.VM.EVM) {
const tokenAddr = execute.funds.token.address;
if (mechanism !== 'approve') {
throw new Error('Only ERC-20 tokens are supported for funds+payload on EVM; native and permit2 are not supported yet');
}
const evmClientEvm = evmClient;
const gatewayAddressEvm = gatewayAddress;
yield this.ensureErc20Allowance(evmClientEvm, tokenAddr, gatewayAddressEvm, bridgeAmount);
}
let txHash;
try {
if (chain_1.CHAIN_INFO[this.universalSigner.account.chain].vm === enums_1.VM.EVM) {
const tokenAddr = execute.funds.token.address;
this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_03_01);
const ueaAddress = this.computeUEAOffchain();
this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_03_02, ueaAddress, deployed);
this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_04_01);
this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_04_02);
this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_04_03);
const evmClientEvm = evmClient;
const gatewayAddressEvm = gatewayAddress;
const payloadBytes = this.encodeUniversalPayload(universalPayload);
// New behavior: if user provided a gasTokenAddress, pay gas in that token via Uniswap quote
// Determine pay-with token address, min-out and slippage
const payWith = execute.payGasWith;
const gasTokenAddress = (_d = payWith === null || payWith === void 0 ? void 0 : payWith.token) === null || _d === void 0 ? void 0 : _d.address;
if (gasTokenAddress) {
if (chain !== enums_1.CHAIN.ETHEREUM_SEPOLIA) {
throw new Error(`Only ${push_chain_1.PushChain.utils.chains.getChainName(enums_1.CHAIN.ETHEREUM_SEPOLIA)} is supported for paying gas fees with ERC-20 tokens`);
}
let amountOutMinETH = (payWith === null || payWith === void 0 ? void 0 : payWith.minAmountOut) !== undefined
? BigInt(payWith.minAmountOut)
: nativeAmount;
const slippageBps = (_e = payWith === null || payWith === void 0 ? void 0 : payWith.slippageBps) !== null && _e !== void 0 ? _e : 100;
amountOutMinETH = BigInt(push_chain_1.PushChain.utils.conversion.slippageToMinAmount(amountOutMinETH.toString(), { slippageBps }));
const { gasAmount } = yield this.calculateGasAmountFromAmountOutMinETH(gasTokenAddress, amountOutMinETH);
const deadline = BigInt(0);
// Ensure caller has enough balance of the gas token to cover fees
const ownerAddress = this.universalSigner.account
.address;
const gasTokenBalance = yield evmClientEvm.getErc20Balance({
tokenAddress: gasTokenAddress,
ownerAddress,
});
if (gasTokenBalance < gasAmount) {
const sym = (_g = (_f = payWith === null || payWith === void 0 ? void 0 : payWith.token) === null || _f === void 0 ? void 0 : _f.symbol) !== null && _g !== void 0 ? _g : 'gas token';
const decimals = (_j = (_h = payWith === null || payWith === void 0 ? void 0 : payWith.token) === null || _h === void 0 ? void 0 : _h.decimals) !== null && _j !== void 0 ? _j : 18;
const needFmt = push_chain_1.PushChain.utils.helpers.formatUnits(gasAmount, decimals);
const haveFmt = push_chain_1.PushChain.utils.helpers.formatUnits(gasTokenBalance, decimals);
throw new Error(`Insufficient ${sym} balance to cover gas fees: need ${needFmt}, have ${haveFmt}`);
}
// Approve gas token to gateway
yield this.ensureErc20Allowance(evmClientEvm, gasTokenAddress, gatewayAddressEvm, gasAmount);
// Approve bridge token already done above; now call new gateway signature (nonpayable)
// const reqToken: UniversalTokenTxRequest = {
// recipient: zeroAddress,
// token: tokenAddr,
// amount: bridgeAmount,
// gasToken: gasTokenAddress,
// gasAmount,
// payload: payloadBytes,
// revertInstruction: revertCFG,
// signatureData: '0x',
// amountOutMinETH,
// deadline,
// } as unknown as never;
const reqToken = Object.assign(Object.assign({}, req), { gasToken: gasTokenAddress, gasAmount,
amountOutMinETH,
deadline });
txHash = yield evmClientEvm.writeContract({
abi: abi_1.UNIVERSAL_GATEWAY_V0,
address: gatewayAddressEvm,
functionName: 'sendUniversalTx',
args: [reqToken],
signer: this.universalSigner,
});
}
else {
// Existing native-ETH value path
// const req: UniversalTxRequest = {
// recipient: zeroAddress,
// token: tokenAddr,
// amount: bridgeAmount,
// payload: payloadBytes,
// revertInstruction: revertCFG,
// signatureData: '0x',
// };
// const req = this._buildUniversalTxRequest({
// recipient: zeroAddress,
// token: tokenAddr,
// amount: bridgeAmount,
// payload: this.encodeUniversalPayload(universalPayload),
// });
// VALUE + PAYLOAD + FUNDS && PAYLOAD + FUNDS
txHash = yield evmClientEvm.writeContract({
abi: abi_1.UNIVERSAL_GATEWAY_V0,
address: gatewayAddressEvm,
functionName: 'sendUniversalTx',
args: [req],
signer: this.universalSigner,
value: nativeAmount,
});
}
}
else {
txHash = yield this._sendSVMTxWithFunds({
execute,
mechanism,
universalPayload,
bridgeAmount,
nativeAmount,
req,
});
}
}
catch (err) {
this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_04_04);
throw err;
}
// Payload Flow: Verification Success
this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_04_03);
// Funds Flow: Funds lock submitted
this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_06_02, txHash, bridgeAmount, execute.funds.token.decimals, symbol);
// Awaiting confirmations
const signerChain = this.universalSigner.account.chain;
if (chain_1.CHAIN_INFO[signerChain].vm === enums_1.VM.EVM) {
const evmClientEvm = evmClient;
yield this.waitForEvmConfirmationsWithCountdown(evmClientEvm, txHash, chain_1.CHAIN_INFO[signerChain].confirmations, chain_1.CHAIN_INFO[signerChain].timeout);
}
else {
const svmClient = new svm_client_1.SvmClient({
rpcUrls: this.rpcUrls[enums_1.CHAIN.SOLANA_DEVNET] ||
chain_1.CHAIN_INFO[enums_1.CHAIN.SOLANA_DEVNET].defaultRPC,
});
yield this.waitForSvmConfirmationsWithCountdown(svmClient, txHash, chain_1.CHAIN_INFO[signerChain].confirmations, chain_1.CHAIN_INFO[signerChain].timeout);
}
// Funds Flow: Confirmed on origin
let feeLockTxHash = txHash;
if (chain_1.CHAIN_INFO[this.universalSigner.account.chain].vm === enums_1.VM.SVM) {
if (feeLockTxHash && !feeLockTxHash.startsWith('0x')) {
// decode svm base58
const decoded = anchor_1.utils.bytes.bs58.decode(feeLockTxHash);
feeLockTxHash = (0, viem_1.bytesToHex)(new Uint8Array(decoded));
}
}
// if (
// chain === CHAIN.SOLANA_DEVNET ||
// chain === CHAIN.SOLANA_TESTNET ||
// chain === CHAIN.SOLANA_MAINNET
// ) {
// await this.sendUniversalTx(deployed, feeLockTxHash);
// }
this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_06_04);
// After sending Cosmos tx to Push Chain, query UniversalTx status
let pushChainUniversalTx;
if (chain_1.CHAIN_INFO[this.universalSigner.account.chain].vm === enums_1.VM.EVM) {
const evmClientEvm = evmClient;
const gatewayAddressEvm = gatewayAddress;
pushChainUniversalTx =
yield this.queryUniversalTxStatusFromGatewayTx(evmClientEvm, gatewayAddressEvm, txHash, 'sendTxWithFunds');
}
else {
pushChainUniversalTx =
yield this.queryUniversalTxStatusFromGatewayTx(undefined, undefined, txHash, 'sendTxWithFunds');
}
const lastPcTransaction = pushChainUniversalTx === null || pushChainUniversalTx === void 0 ? void 0 : pushChainUniversalTx.pcTx.at(-1);
const tx = yield this.pushClient.getTransaction(lastPcTransaction === null || lastPcTransaction === void 0 ? void 0 : lastPcTransaction.txHash);
const response = yield this.transformToUniversalTxResponse(tx);
this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_99_01, [response]);
return response;
}
}
// Set default value for value if undefined
if (execute.value === undefined) {
execute.value = BigInt(0);
}
const chain = this.universalSigner.account.chain;
this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_01, chain, this.universalSigner.account.address);
this.validateMainnetConnection(chain);
/**
* Push to Push Tx
*/
if (this.isPushChain(chain)) {
this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_07);
const tx = yield this.sendPushTx(execute);
this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_99_01, [tx]);
return tx;
}
/**
* Fetch Gas details and estimate cost of execution
*/
this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_02_01);
const gasEstimate = execute.gasLimit || BigInt(1e7);
const gasPrice = yield this.pushClient.getGasPrice();
const requiredGasFee = gasEstimate * gasPrice;
const requiredFunds = requiredGasFee + execute.value;
this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_02_02, requiredFunds);
/**
* Fetch UEA Details
*/
this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_03_01);
const UEA = this.computeUEAOffchain();
const [code, funds] = yield Promise.all([
this.pushClient.publicClient.getCode({ address: UEA }),
this.pushClient.getBalance(UEA),
]);
const isUEADeployed = code !== undefined;
const nonce = isUEADeployed ? yield this.getUEANonce(UEA) : BigInt(0);
this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_03_02, UEA, isUEADeployed);
/**
* Compute Universal Payload Hash
*/
let feeLockTxHash = execute.feeLockTxHash;
if (feeLockTxHash && !feeLockTxHash.startsWith('0x')) {
// decode svm base58
const decoded = anchor_1.utils.bytes.bs58.decode(feeLockTxHash);
feeLockTxHash = (0, viem_1.bytesToHex)(new Uint8Array(decoded));
}
// Fee locking is required if UEA is not deployed OR insufficient funds
const feeLockingRequired = (!isUEADeployed || funds < requiredFunds) && !feeLockTxHash;
// const feeLockingRequired = true;
// Support multicall payload encoding when execute.data is an array
let payloadData;
let payloadTo;
let req;
// Here is only value and payload. No funds here
if (Array.isArray(execute.data)) {
// payloadData = this._buildMulticallPayloadData(execute.to, execute.data);
// Normal multicall. We replace the `to` to zeroAddress. Then console.warn to let user know that it should be
// passed as zeroAddress in the future.
// execute.to = zeroAddress;
payloadTo = viem_1.zeroAddress;
console.warn(`Multicalls should have execute.to as ${viem_1.zeroAddress}`);
payloadData = this._buildMulticallPayloadData(execute.to, (0, payload_builders_1.buildExecuteMulticall)({ execute, ueaAddress: UEA }));
req = this._buildUniversalTxRequest({
recipient: viem_1.zeroAddress,
token: viem_1.zeroAddress,
amount: BigInt(0),
payload: payloadData,
});
}
else {
if (execute.to.toLowerCase() !== UEA.toLowerCase()) {
// For Payload + Value we don't do multicall anymore.
// Multicall is only when Payload + Value;
// Payload + Value + Funds -> Multicall
// TODO: Check but I beleive this code se