UNPKG

@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
"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