@pushchain/core 4.0.14-alpha.0 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/package.json +1 -1
  3. package/src/lib/constants/abi/cea.evm.d.ts +23 -0
  4. package/src/lib/constants/abi/cea.evm.js +34 -0
  5. package/src/lib/constants/abi/cea.evm.js.map +1 -0
  6. package/src/lib/constants/abi/ceaFactory.evm.d.ts +65 -0
  7. package/src/lib/constants/abi/ceaFactory.evm.js +41 -0
  8. package/src/lib/constants/abi/ceaFactory.evm.js.map +1 -0
  9. package/src/lib/constants/abi/erc20.evm.d.ts +14 -0
  10. package/src/lib/constants/abi/erc20.evm.js +7 -0
  11. package/src/lib/constants/abi/erc20.evm.js.map +1 -1
  12. package/src/lib/constants/abi/index.d.ts +6 -0
  13. package/src/lib/constants/abi/index.js +15 -1
  14. package/src/lib/constants/abi/index.js.map +1 -1
  15. package/src/lib/constants/abi/prc20.evm.d.ts +188 -0
  16. package/src/lib/constants/abi/prc20.evm.js +130 -0
  17. package/src/lib/constants/abi/prc20.evm.js.map +1 -0
  18. package/src/lib/constants/abi/uea-factory.d.ts +30 -0
  19. package/src/lib/constants/abi/uea-factory.js +25 -0
  20. package/src/lib/constants/abi/uea-factory.js.map +1 -0
  21. package/src/lib/constants/abi/universalGateway.evm.d.ts +93 -0
  22. package/src/lib/constants/abi/universalGateway.evm.js +70 -0
  23. package/src/lib/constants/abi/universalGateway.evm.js.map +1 -0
  24. package/src/lib/constants/abi/universalGatewayPC.evm.d.ts +140 -0
  25. package/src/lib/constants/abi/universalGatewayPC.evm.js +70 -0
  26. package/src/lib/constants/abi/universalGatewayPC.evm.js.map +1 -0
  27. package/src/lib/constants/abi/universalGatewayV0.evm.js +57 -0
  28. package/src/lib/constants/abi/universalGatewayV0.evm.js.map +1 -1
  29. package/src/lib/constants/abi/universalGatewayV0.json +1162 -1647
  30. package/src/lib/constants/chain.d.ts +52 -0
  31. package/src/lib/constants/chain.js +161 -1
  32. package/src/lib/constants/chain.js.map +1 -1
  33. package/src/lib/constants/index.d.ts +9 -1
  34. package/src/lib/constants/index.js +20 -1
  35. package/src/lib/constants/index.js.map +1 -1
  36. package/src/lib/constants/selectors.d.ts +42 -0
  37. package/src/lib/constants/selectors.js +45 -0
  38. package/src/lib/constants/selectors.js.map +1 -0
  39. package/src/lib/constants/tokens.d.ts +41 -0
  40. package/src/lib/constants/tokens.js +62 -1
  41. package/src/lib/constants/tokens.js.map +1 -1
  42. package/src/lib/generated/uexecutor/v2/index.d.ts +2 -0
  43. package/src/lib/generated/uexecutor/v2/index.js +31 -0
  44. package/src/lib/generated/uexecutor/v2/index.js.map +1 -0
  45. package/src/lib/generated/uexecutor/v2/query.d.ts +23 -0
  46. package/src/lib/generated/uexecutor/v2/query.js +79 -0
  47. package/src/lib/generated/uexecutor/v2/query.js.map +1 -0
  48. package/src/lib/generated/uexecutor/v2/types.d.ts +101 -0
  49. package/src/lib/generated/uexecutor/v2/types.js +660 -0
  50. package/src/lib/generated/uexecutor/v2/types.js.map +1 -0
  51. package/src/lib/generated/v1/tx.d.ts +22 -0
  52. package/src/lib/generated/v1/tx.js +188 -1
  53. package/src/lib/generated/v1/tx.js.map +1 -1
  54. package/src/lib/index.d.ts +5 -0
  55. package/src/lib/index.js +25 -1
  56. package/src/lib/index.js.map +1 -1
  57. package/src/lib/orchestrator/cea-utils.d.ts +85 -0
  58. package/src/lib/orchestrator/cea-utils.js +186 -0
  59. package/src/lib/orchestrator/cea-utils.js.map +1 -0
  60. package/src/lib/orchestrator/orchestrator.d.ts +326 -3
  61. package/src/lib/orchestrator/orchestrator.js +3262 -135
  62. package/src/lib/orchestrator/orchestrator.js.map +1 -1
  63. package/src/lib/orchestrator/orchestrator.types.d.ts +487 -0
  64. package/src/lib/orchestrator/orchestrator.types.js +17 -0
  65. package/src/lib/orchestrator/orchestrator.types.js.map +1 -1
  66. package/src/lib/orchestrator/payload-builders.d.ts +210 -1
  67. package/src/lib/orchestrator/payload-builders.js +481 -0
  68. package/src/lib/orchestrator/payload-builders.js.map +1 -1
  69. package/src/lib/orchestrator/route-detector.d.ts +102 -0
  70. package/src/lib/orchestrator/route-detector.js +355 -0
  71. package/src/lib/orchestrator/route-detector.js.map +1 -0
  72. package/src/lib/price-fetch/price-fetch.js +5 -2
  73. package/src/lib/price-fetch/price-fetch.js.map +1 -1
  74. package/src/lib/progress-hook/progress-hook.js +43 -0
  75. package/src/lib/progress-hook/progress-hook.js.map +1 -1
  76. package/src/lib/progress-hook/progress-hook.types.d.ts +7 -1
  77. package/src/lib/progress-hook/progress-hook.types.js +7 -0
  78. package/src/lib/progress-hook/progress-hook.types.js.map +1 -1
  79. package/src/lib/push-chain/push-chain.d.ts +71 -1
  80. package/src/lib/push-chain/push-chain.js +70 -1
  81. package/src/lib/push-chain/push-chain.js.map +1 -1
  82. package/src/lib/push-client/push-client.d.ts +10 -1
  83. package/src/lib/push-client/push-client.js +35 -6
  84. package/src/lib/push-client/push-client.js.map +1 -1
  85. package/src/lib/vm-client/evm-client.d.ts +3 -1
  86. package/src/lib/vm-client/evm-client.js +23 -15
  87. package/src/lib/vm-client/evm-client.js.map +1 -1
@@ -8,8 +8,12 @@ const viem_1 = require("viem");
8
8
  const abi_1 = require("../constants/abi");
9
9
  const uea_evm_1 = require("../constants/abi/uea.evm");
10
10
  const chain_1 = require("../constants/chain");
11
+ const uea_factory_1 = require("../constants/abi/uea-factory");
11
12
  const enums_1 = require("../constants/enums");
12
13
  const tokens_1 = require("../constants/tokens");
14
+ const types_1 = require("../generated/uexecutor/v1/types");
15
+ const types_2 = require("../generated/uexecutor/v2/types");
16
+ const orchestrator_types_1 = require("./orchestrator.types");
13
17
  const tx_1 = require("../generated/v1/tx");
14
18
  const price_fetch_1 = require("../price-fetch/price-fetch");
15
19
  const progress_hook_1 = tslib_1.__importDefault(require("../progress-hook/progress-hook"));
@@ -20,6 +24,9 @@ const push_client_1 = require("../push-client/push-client");
20
24
  const evm_client_1 = require("../vm-client/evm-client");
21
25
  const svm_client_1 = require("../vm-client/svm-client");
22
26
  const payload_builders_1 = require("./payload-builders");
27
+ const route_detector_1 = require("./route-detector");
28
+ const cea_utils_1 = require("./cea-utils");
29
+ const selectors_1 = require("../constants/selectors");
23
30
  class Orchestrator {
24
31
  constructor(universalSigner, pushNetwork, rpcUrls = {}, printTraces = false, progressHook) {
25
32
  this.universalSigner = universalSigner;
@@ -27,6 +34,13 @@ class Orchestrator {
27
34
  this.rpcUrls = rpcUrls;
28
35
  this.printTraces = printTraces;
29
36
  this.progressHook = progressHook;
37
+ this.accountStatusCache = null;
38
+ /**
39
+ * Per-chain cache for detected gateway version (v0 or v1).
40
+ * Populated on first successful gateway tx per chain via try-V1-then-V0 fallback.
41
+ * TODO: Remove this cache + fallback logic once all chains are upgraded to V1.
42
+ */
43
+ this.gatewayVersionCache = new Map();
30
44
  let pushChain;
31
45
  if (pushNetwork === enums_1.PUSH_NETWORK.MAINNET) {
32
46
  pushChain = enums_1.CHAIN.PUSH_MAINNET;
@@ -43,6 +57,9 @@ class Orchestrator {
43
57
  rpcUrls: pushChainRPCs,
44
58
  network: pushNetwork,
45
59
  });
60
+ // BNB is already upgraded to V1 — pre-seed the cache so it never falls back to V0.
61
+ // TODO: Remove these entries once all chains are upgraded to V1.
62
+ this.gatewayVersionCache.set(enums_1.CHAIN.BNB_TESTNET, 'v1');
46
63
  }
47
64
  /**
48
65
  * Read-only accessors for current Orchestrator configuration
@@ -59,12 +76,242 @@ class Orchestrator {
59
76
  getProgressHook() {
60
77
  return this.progressHook;
61
78
  }
79
+ // =========================================================================
80
+ // Account Status & UEA Upgrade
81
+ // =========================================================================
62
82
  /**
63
- * Executes an interaction on Push Chain
83
+ * Fetches account status including UEA deployment state and version info.
84
+ * Results are cached — pass forceRefresh to bypass cache.
64
85
  */
65
- execute(execute) {
86
+ getAccountStatus(options) {
66
87
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
67
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
88
+ if (this.accountStatusCache && !(options === null || options === void 0 ? void 0 : options.forceRefresh)) {
89
+ return this.accountStatusCache;
90
+ }
91
+ const chain = this.universalSigner.account.chain;
92
+ const { vm } = chain_1.CHAIN_INFO[chain];
93
+ const isReadOnly = false; // This is called from Orchestrator which always has a signer
94
+ const { deployed } = yield this.getUeaStatusAndNonce();
95
+ if (!deployed) {
96
+ const status = {
97
+ mode: 'signer',
98
+ uea: {
99
+ loaded: true,
100
+ deployed: false,
101
+ version: '',
102
+ minRequiredVersion: '',
103
+ requiresUpgrade: false,
104
+ },
105
+ };
106
+ this.accountStatusCache = status;
107
+ return status;
108
+ }
109
+ // Fetch current version and latest version from factory in parallel
110
+ const [currentVersion, minRequiredVersion] = yield Promise.all([
111
+ this.fetchUEAVersion(),
112
+ this.fetchLatestUEAVersion(vm),
113
+ ]);
114
+ const requiresUpgrade = (0, orchestrator_types_1.parseUEAVersion)(currentVersion) < (0, orchestrator_types_1.parseUEAVersion)(minRequiredVersion);
115
+ const status = {
116
+ mode: 'signer',
117
+ uea: {
118
+ loaded: true,
119
+ deployed: true,
120
+ version: currentVersion,
121
+ minRequiredVersion,
122
+ requiresUpgrade,
123
+ },
124
+ };
125
+ this.accountStatusCache = status;
126
+ return status;
127
+ });
128
+ }
129
+ /**
130
+ * Upgrades the UEA to the latest implementation version.
131
+ * Sends MsgMigrateUEA Cosmos message with EIP-712 signed MigrationPayload.
132
+ * The UEA contract delegates to the UEAMigration contract to update implementation.
133
+ */
134
+ upgradeAccount(options) {
135
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
136
+ const hook = (options === null || options === void 0 ? void 0 : options.progressHook) || this.progressHook;
137
+ const fireHook = (hookId, ...args) => {
138
+ const hookEntry = progress_hook_1.default[hookId];
139
+ if (hookEntry && hook) {
140
+ hook(hookEntry(...args));
141
+ }
142
+ };
143
+ // Step 1: Check status
144
+ fireHook(progress_hook_types_1.PROGRESS_HOOK.UEA_MIG_01);
145
+ const status = yield this.getAccountStatus({ forceRefresh: true });
146
+ if (!status.uea.requiresUpgrade) {
147
+ fireHook(progress_hook_types_1.PROGRESS_HOOK.UEA_MIG_9903);
148
+ return;
149
+ }
150
+ // Step 2: Awaiting signature
151
+ fireHook(progress_hook_types_1.PROGRESS_HOOK.UEA_MIG_02);
152
+ try {
153
+ const { chain, address } = this.universalSigner.account;
154
+ const { vm, chainId } = chain_1.CHAIN_INFO[chain];
155
+ const ueaAddress = this.computeUEAOffchain();
156
+ const migrationContractAddress = chain_1.UEA_MIGRATION[this.pushNetwork];
157
+ if (!migrationContractAddress || migrationContractAddress === '0xTBD') {
158
+ throw new Error('UEA migration contract address not configured');
159
+ }
160
+ // Read UEA nonce
161
+ const { nonce } = yield this.getUeaStatusAndNonce();
162
+ const deadline = BigInt(9999999999);
163
+ const ueaVersion = status.uea.version || '0.1.0';
164
+ // Sign MigrationPayload via EIP-712
165
+ if (!this.universalSigner.signTypedData) {
166
+ throw new Error('signTypedData not available on signer');
167
+ }
168
+ const signatureBytes = yield this.universalSigner.signTypedData({
169
+ domain: {
170
+ version: ueaVersion,
171
+ chainId: Number(chainId),
172
+ verifyingContract: ueaAddress,
173
+ },
174
+ types: {
175
+ MigrationPayload: [
176
+ { name: 'migration', type: 'address' },
177
+ { name: 'nonce', type: 'uint256' },
178
+ { name: 'deadline', type: 'uint256' },
179
+ ],
180
+ },
181
+ primaryType: 'MigrationPayload',
182
+ message: {
183
+ migration: migrationContractAddress,
184
+ nonce,
185
+ deadline,
186
+ },
187
+ });
188
+ const signature = (0, viem_1.bytesToHex)(signatureBytes);
189
+ // Build Cosmos message
190
+ fireHook(progress_hook_types_1.PROGRESS_HOOK.UEA_MIG_03);
191
+ const universalAccountId = {
192
+ chainNamespace: chain_1.VM_NAMESPACE[vm],
193
+ chainId,
194
+ owner: vm === enums_1.VM.EVM
195
+ ? address
196
+ : vm === enums_1.VM.SVM
197
+ ? (0, viem_1.bytesToHex)(new Uint8Array(anchor_1.utils.bytes.bs58.decode(address)))
198
+ : address,
199
+ };
200
+ const { cosmosAddress: signer } = this.pushClient.getSignerAddress();
201
+ const msg = this.pushClient.createMsgMigrateUEA({
202
+ signer,
203
+ universalAccountId,
204
+ migrationPayload: {
205
+ migration: migrationContractAddress,
206
+ nonce: nonce.toString(),
207
+ deadline: deadline.toString(),
208
+ },
209
+ signature,
210
+ });
211
+ const txBody = yield this.pushClient.createCosmosTxBody([msg]);
212
+ const txRaw = yield this.pushClient.signCosmosTx(txBody);
213
+ const tx = yield this.pushClient.broadcastCosmosTx(txRaw);
214
+ if (tx.code !== 0) {
215
+ throw new Error(tx.rawLog || 'UEA migration transaction failed');
216
+ }
217
+ // Clear version cache so next read picks up new version
218
+ this.ueaVersionCache = undefined;
219
+ // Refresh account status
220
+ const updated = yield this.getAccountStatus({ forceRefresh: true });
221
+ fireHook(progress_hook_types_1.PROGRESS_HOOK.UEA_MIG_9901, updated.uea.version);
222
+ }
223
+ catch (err) {
224
+ fireHook(progress_hook_types_1.PROGRESS_HOOK.UEA_MIG_9902);
225
+ throw err;
226
+ }
227
+ });
228
+ }
229
+ /**
230
+ * Fetches the latest UEA version from UEAFactory contract.
231
+ * This is the version of the newest registered implementation — used as minRequiredVersion.
232
+ * Returns empty string if factory is unavailable.
233
+ */
234
+ fetchLatestUEAVersion(vm) {
235
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
236
+ const factoryAddress = chain_1.UEA_FACTORY[this.pushNetwork];
237
+ if (!factoryAddress || factoryAddress === '0xTBD') {
238
+ return '';
239
+ }
240
+ try {
241
+ const vmHash = vm === enums_1.VM.EVM
242
+ ? (0, viem_1.keccak256)((0, viem_1.encodeAbiParameters)([{ type: 'string' }], ['EVM']))
243
+ : (0, viem_1.keccak256)((0, viem_1.encodeAbiParameters)([{ type: 'string' }], ['SVM']));
244
+ return yield this.pushClient.readContract({
245
+ address: factoryAddress,
246
+ abi: uea_factory_1.UEA_FACTORY_ABI,
247
+ functionName: 'UEA_VERSION',
248
+ args: [vmHash],
249
+ });
250
+ }
251
+ catch (_a) {
252
+ return '';
253
+ }
254
+ });
255
+ }
256
+ /**
257
+ * Migrate the CEA contract on an external chain to the latest version.
258
+ * Sends a MIGRATION_SELECTOR payload via Route 2 to trigger CEA upgrade.
259
+ *
260
+ * @param chain - The external chain where the CEA should be migrated
261
+ * @returns Transaction response
262
+ */
263
+ migrateCEA(chain) {
264
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
265
+ var _a;
266
+ if (this.isPushChain(chain)) {
267
+ throw new Error('Cannot migrate CEA on Push Chain');
268
+ }
269
+ if (!(0, cea_utils_1.chainSupportsCEA)(chain)) {
270
+ throw new Error(`Chain ${chain} does not support CEA`);
271
+ }
272
+ const ueaAddress = this.computeUEAOffchain();
273
+ const { cea, isDeployed } = yield (0, cea_utils_1.getCEAAddress)(ueaAddress, chain, (_a = this.rpcUrls[chain]) === null || _a === void 0 ? void 0 : _a[0]);
274
+ if (!isDeployed) {
275
+ throw new Error(`CEA not deployed on chain ${chain}. Deploy CEA first.`);
276
+ }
277
+ return this.execute({
278
+ to: { address: cea, chain },
279
+ migration: true,
280
+ });
281
+ });
282
+ }
283
+ /**
284
+ * Executes a transaction with automatic route detection.
285
+ *
286
+ * Supports both simple Push Chain transactions and multi-chain routing:
287
+ * - Route 1 (UOA_TO_PUSH): `to` is a simple address string
288
+ * - Route 2 (UOA_TO_CEA): `to` is `{ address, chain }` targeting external chain
289
+ * - Route 3 (CEA_TO_PUSH): `from.chain` specified, targeting Push Chain
290
+ * - Route 4 (CEA_TO_CEA): `from.chain` specified, targeting external chain
291
+ *
292
+ * @param params - ExecuteParams or UniversalExecuteParams
293
+ * @returns Transaction response
294
+ */
295
+ execute(params) {
296
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
297
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s;
298
+ // Lazy UEA upgrade check
299
+ if (!this.accountStatusCache || !this.accountStatusCache.uea.loaded) {
300
+ yield this.getAccountStatus();
301
+ }
302
+ if (((_a = this.accountStatusCache) === null || _a === void 0 ? void 0 : _a.uea.deployed) &&
303
+ ((_b = this.accountStatusCache) === null || _b === void 0 ? void 0 : _b.uea.requiresUpgrade)) {
304
+ yield this.upgradeAccount({ progressHook: this.progressHook });
305
+ }
306
+ // Check if this is a multi-chain request (has ChainTarget or from.chain)
307
+ const isMultiChain = (0, route_detector_1.isChainTarget)(params.to) || ('from' in params && ((_c = params.from) === null || _c === void 0 ? void 0 : _c.chain));
308
+ if (isMultiChain) {
309
+ // sendTransaction delegates to prepareTransaction().send() for multi-chain routes
310
+ const prepared = yield this.prepareTransaction(params);
311
+ return prepared.send();
312
+ }
313
+ // Standard Push Chain execution (Route 1)
314
+ const execute = params;
68
315
  // Create buffer to collect events during execution for tx.progressHook() replay
69
316
  const eventBuffer = [];
70
317
  // Store original progressHook and wrap to collect events
@@ -186,14 +433,7 @@ class Orchestrator {
186
433
  isNative, bridgeAmount: bridgeAmount.toString(),
187
434
  nativeAmount: nativeAmount.toString(), bridgeToken,
188
435
  }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2));
189
- txHash = yield evmClient.writeContract({
190
- abi: abi_1.UNIVERSAL_GATEWAY_V0,
191
- address: gatewayAddress,
192
- functionName: 'sendUniversalTx',
193
- args: [req],
194
- signer: this.universalSigner,
195
- value: isNative ? nativeAmount + bridgeAmount : nativeAmount,
196
- });
436
+ txHash = yield this.sendGatewayTxWithFallback(evmClient, gatewayAddress, req, this.universalSigner, isNative ? nativeAmount + bridgeAmount : nativeAmount);
197
437
  }
198
438
  else {
199
439
  // FUNDS ONLY OTHER
@@ -214,14 +454,7 @@ class Orchestrator {
214
454
  isNative, bridgeAmount: bridgeAmount.toString(),
215
455
  nativeAmount: nativeAmount.toString(), bridgeToken,
216
456
  }, (_, v) => typeof v === 'bigint' ? v.toString() : v, 2));
217
- txHash = yield evmClient.writeContract({
218
- abi: abi_1.UNIVERSAL_GATEWAY_V0,
219
- address: gatewayAddress,
220
- functionName: 'sendUniversalTx',
221
- args: [req],
222
- signer: this.universalSigner,
223
- value: isNative ? nativeAmount + bridgeAmount : nativeAmount,
224
- });
457
+ txHash = yield this.sendGatewayTxWithFallback(evmClient, gatewayAddress, req, this.universalSigner, isNative ? nativeAmount + bridgeAmount : nativeAmount);
225
458
  }
226
459
  }
227
460
  catch (err) {
@@ -240,14 +473,14 @@ class Orchestrator {
240
473
  evmGatewayMethod: execute.to === ueaAddress ? 'sendFunds' : 'sendTxWithFunds',
241
474
  }));
242
475
  const pushChainUniversalTx = yield this.queryUniversalTxStatusFromGatewayTx(evmClient, gatewayAddress, txHash, execute.to === ueaAddress ? 'sendFunds' : 'sendTxWithFunds');
243
- if (!((_a = pushChainUniversalTx === null || pushChainUniversalTx === void 0 ? void 0 : pushChainUniversalTx.pcTx) === null || _a === void 0 ? void 0 : _a.length)) {
476
+ if (!((_d = pushChainUniversalTx === null || pushChainUniversalTx === void 0 ? void 0 : pushChainUniversalTx.pcTx) === null || _d === void 0 ? void 0 : _d.length)) {
244
477
  throw new Error(`Failed to retrieve Push Chain transaction status for gateway tx: ${txHash}. ` +
245
478
  `The transaction may have failed on Push Chain or not been indexed yet.`);
246
479
  }
247
480
  // For sendFunds operations, MintPC (first) succeeds and executePayload (second) may fail
248
481
  // Always use the last pcTx entry as it represents the final execution result
249
482
  const lastPcTransaction = pushChainUniversalTx.pcTx.at(-1);
250
- this.printLog('sendFunds — pushChainUniversalTx pcTx: ' + JSON.stringify((_b = pushChainUniversalTx === null || pushChainUniversalTx === void 0 ? void 0 : pushChainUniversalTx.pcTx) === null || _b === void 0 ? void 0 : _b.map((p) => ({ txHash: p.txHash, status: p.status, errorMsg: p.errorMsg })), null, 2));
483
+ this.printLog('sendFunds — pushChainUniversalTx pcTx: ' + JSON.stringify((_e = pushChainUniversalTx === null || pushChainUniversalTx === void 0 ? void 0 : pushChainUniversalTx.pcTx) === null || _e === void 0 ? void 0 : _e.map((p) => ({ txHash: p.txHash, status: p.status, errorMsg: p.errorMsg })), null, 2));
251
484
  this.printLog('sendFunds — using lastPcTransaction: ' + JSON.stringify(lastPcTransaction, null, 2));
252
485
  if (!(lastPcTransaction === null || lastPcTransaction === void 0 ? void 0 : lastPcTransaction.txHash)) {
253
486
  // Check for error messages in failed entries
@@ -270,7 +503,7 @@ class Orchestrator {
270
503
  const programId = new web3_js_1.PublicKey(abi_1.SVM_GATEWAY_IDL.address);
271
504
  const [configPda] = web3_js_1.PublicKey.findProgramAddressSync([(0, viem_1.stringToBytes)('config')], programId);
272
505
  const [vaultPda] = web3_js_1.PublicKey.findProgramAddressSync([(0, viem_1.stringToBytes)('vault')], programId);
273
- const [whitelistPda] = web3_js_1.PublicKey.findProgramAddressSync([(0, viem_1.stringToBytes)('whitelist')], programId);
506
+ const { feeVaultPda, protocolFeeLamports } = yield this._getSvmProtocolFee(svmClient, programId);
274
507
  const [rateLimitConfigPda] = web3_js_1.PublicKey.findProgramAddressSync([(0, viem_1.stringToBytes)('rate_limit_config')], programId);
275
508
  const userPk = new web3_js_1.PublicKey(this.universalSigner.account.address);
276
509
  const priceUpdatePk = new web3_js_1.PublicKey('7UVimffxr9ow1uXYxsr4LHAcV58mLzhmwaeKvJ1pjLiE');
@@ -279,11 +512,6 @@ class Orchestrator {
279
512
  throw new Error('Pay-with token is not supported on Solana');
280
513
  }
281
514
  let txSignature;
282
- // SVM-specific RevertSettings: bytes must be a Buffer
283
- const revertSvm = {
284
- fundRecipient: userPk,
285
- revertMsg: Buffer.from([]),
286
- };
287
515
  // New gateway expects EVM recipient as [u8; 20]
288
516
  const recipientEvm20 = Array.from(Buffer.from(execute.to.slice(2).padStart(40, '0'), 'hex').subarray(0, 20));
289
517
  this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_03_01);
@@ -298,18 +526,19 @@ class Orchestrator {
298
526
  token: web3_js_1.PublicKey.default,
299
527
  amount: bridgeAmount,
300
528
  payload: '0x',
301
- fundRecipient: userPk,
529
+ revertRecipient: userPk,
302
530
  signatureData: '0x',
303
531
  });
304
532
  txSignature = yield svmClient.writeContract({
305
533
  abi: abi_1.SVM_GATEWAY_IDL,
306
534
  address: programId.toBase58(),
307
535
  functionName: 'sendUniversalTx',
308
- args: [reqNative, bridgeAmount],
536
+ args: [reqNative, bridgeAmount + protocolFeeLamports],
309
537
  signer: this.universalSigner,
310
538
  accounts: {
311
539
  config: configPda,
312
540
  vault: vaultPda,
541
+ feeVault: feeVaultPda,
313
542
  userTokenAccount: vaultPda,
314
543
  gatewayTokenAccount: vaultPda,
315
544
  user: userPk,
@@ -344,18 +573,19 @@ class Orchestrator {
344
573
  token: mintPk,
345
574
  amount: bridgeAmount,
346
575
  payload: '0x',
347
- fundRecipient: userPk,
576
+ revertRecipient: userPk,
348
577
  signatureData: '0x',
349
578
  });
350
579
  txSignature = yield svmClient.writeContract({
351
580
  abi: abi_1.SVM_GATEWAY_IDL,
352
581
  address: programId.toBase58(),
353
582
  functionName: 'sendUniversalTx',
354
- args: [reqSpl, BigInt(0)],
583
+ args: [reqSpl, protocolFeeLamports],
355
584
  signer: this.universalSigner,
356
585
  accounts: {
357
586
  config: configPda,
358
587
  vault: vaultPda,
588
+ feeVault: feeVaultPda,
359
589
  userTokenAccount: userAta,
360
590
  gatewayTokenAccount: vaultAta,
361
591
  user: userPk,
@@ -375,18 +605,19 @@ class Orchestrator {
375
605
  token: mintPk,
376
606
  amount: bridgeAmount,
377
607
  payload: '0x',
378
- fundRecipient: userPk,
608
+ revertRecipient: userPk,
379
609
  signatureData: '0x',
380
610
  });
381
611
  txSignature = yield svmClient.writeContract({
382
612
  abi: abi_1.SVM_GATEWAY_IDL,
383
613
  address: programId.toBase58(),
384
614
  functionName: 'sendUniversalTx',
385
- args: [reqSpl, BigInt(0)],
615
+ args: [reqSpl, protocolFeeLamports],
386
616
  signer: this.universalSigner,
387
617
  accounts: {
388
618
  config: configPda,
389
619
  vault: vaultPda,
620
+ feeVault: feeVaultPda,
390
621
  userTokenAccount: userAta,
391
622
  gatewayTokenAccount: vaultAta,
392
623
  user: userPk,
@@ -411,7 +642,7 @@ class Orchestrator {
411
642
  this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_06_05);
412
643
  // After origin confirmations, query Push Chain for UniversalTx status (SVM)
413
644
  const pushChainUniversalTx = yield this.queryUniversalTxStatusFromGatewayTx(undefined, undefined, txSignature, 'sendFunds');
414
- if (!((_c = pushChainUniversalTx === null || pushChainUniversalTx === void 0 ? void 0 : pushChainUniversalTx.pcTx) === null || _c === void 0 ? void 0 : _c.length)) {
645
+ if (!((_f = pushChainUniversalTx === null || pushChainUniversalTx === void 0 ? void 0 : pushChainUniversalTx.pcTx) === null || _f === void 0 ? void 0 : _f.length)) {
415
646
  throw new Error(`Failed to retrieve Push Chain transaction status for gateway tx: ${txSignature}. ` +
416
647
  `The transaction may have failed on Push Chain or not been indexed yet.`);
417
648
  }
@@ -459,7 +690,7 @@ class Orchestrator {
459
690
  const gasEstimate = execute.gasLimit || BigInt(1e7);
460
691
  const gasPrice = yield this.pushClient.getGasPrice();
461
692
  const requiredGasFee = gasEstimate * gasPrice;
462
- const payloadValue = (_d = execute.value) !== null && _d !== void 0 ? _d : BigInt(0);
693
+ const payloadValue = (_g = execute.value) !== null && _g !== void 0 ? _g : BigInt(0);
463
694
  const requiredFunds = requiredGasFee + payloadValue;
464
695
  const ueaAddress = this.computeUEAOffchain();
465
696
  const [ueaBalance] = yield Promise.all([
@@ -492,8 +723,8 @@ class Orchestrator {
492
723
  functionName: 'config',
493
724
  args: [configPda.toBase58()],
494
725
  });
495
- const minField = (_e = cfg.minCapUniversalTxUsd) !== null && _e !== void 0 ? _e : cfg.min_cap_universal_tx_usd;
496
- const maxField = (_f = cfg.maxCapUniversalTxUsd) !== null && _f !== void 0 ? _f : cfg.max_cap_universal_tx_usd;
726
+ const minField = (_h = cfg.minCapUniversalTxUsd) !== null && _h !== void 0 ? _h : cfg.min_cap_universal_tx_usd;
727
+ const maxField = (_j = cfg.maxCapUniversalTxUsd) !== null && _j !== void 0 ? _j : cfg.max_cap_universal_tx_usd;
497
728
  const minCapUsd = BigInt(minField.toString());
498
729
  const maxCapUsd = BigInt(maxField.toString());
499
730
  if (depositUsd < minCapUsd)
@@ -505,7 +736,7 @@ class Orchestrator {
505
736
  if (depositUsd > maxCapUsd)
506
737
  depositUsd = maxCapUsd;
507
738
  }
508
- catch (_q) {
739
+ catch (_t) {
509
740
  // best-effort; fallback to previous bounds if read fails
510
741
  }
511
742
  }
@@ -556,7 +787,7 @@ class Orchestrator {
556
787
  // New behavior: if user provided a gasTokenAddress, pay gas in that token via Uniswap quote
557
788
  // Determine pay-with token address, min-out and slippage
558
789
  const payWith = execute.payGasWith;
559
- const gasTokenAddress = (_g = payWith === null || payWith === void 0 ? void 0 : payWith.token) === null || _g === void 0 ? void 0 : _g.address;
790
+ const gasTokenAddress = (_k = payWith === null || payWith === void 0 ? void 0 : payWith.token) === null || _k === void 0 ? void 0 : _k.address;
560
791
  if (gasTokenAddress) {
561
792
  if (chain !== enums_1.CHAIN.ETHEREUM_SEPOLIA) {
562
793
  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`);
@@ -564,7 +795,7 @@ class Orchestrator {
564
795
  let amountOutMinETH = (payWith === null || payWith === void 0 ? void 0 : payWith.minAmountOut) !== undefined
565
796
  ? BigInt(payWith.minAmountOut)
566
797
  : nativeAmount;
567
- const slippageBps = (_h = payWith === null || payWith === void 0 ? void 0 : payWith.slippageBps) !== null && _h !== void 0 ? _h : 100;
798
+ const slippageBps = (_l = payWith === null || payWith === void 0 ? void 0 : payWith.slippageBps) !== null && _l !== void 0 ? _l : 100;
568
799
  amountOutMinETH = BigInt(push_chain_1.PushChain.utils.conversion.slippageToMinAmount(amountOutMinETH.toString(), { slippageBps }));
569
800
  const { gasAmount } = yield this.calculateGasAmountFromAmountOutMinETH(gasTokenAddress, amountOutMinETH);
570
801
  const deadline = BigInt(0);
@@ -576,8 +807,8 @@ class Orchestrator {
576
807
  ownerAddress,
577
808
  });
578
809
  if (gasTokenBalance < gasAmount) {
579
- const sym = (_k = (_j = payWith === null || payWith === void 0 ? void 0 : payWith.token) === null || _j === void 0 ? void 0 : _j.symbol) !== null && _k !== void 0 ? _k : 'gas token';
580
- const decimals = (_m = (_l = payWith === null || payWith === void 0 ? void 0 : payWith.token) === null || _l === void 0 ? void 0 : _l.decimals) !== null && _m !== void 0 ? _m : 18;
810
+ const sym = (_o = (_m = payWith === null || payWith === void 0 ? void 0 : payWith.token) === null || _m === void 0 ? void 0 : _m.symbol) !== null && _o !== void 0 ? _o : 'gas token';
811
+ const decimals = (_q = (_p = payWith === null || payWith === void 0 ? void 0 : payWith.token) === null || _p === void 0 ? void 0 : _p.decimals) !== null && _q !== void 0 ? _q : 18;
581
812
  const needFmt = utils_1.Utils.helpers.formatUnits(gasAmount, decimals);
582
813
  const haveFmt = utils_1.Utils.helpers.formatUnits(gasTokenBalance, decimals);
583
814
  throw new Error(`Insufficient ${sym} balance to cover gas fees: need ${needFmt}, have ${haveFmt}`);
@@ -600,13 +831,7 @@ class Orchestrator {
600
831
  const reqToken = Object.assign(Object.assign({}, req), { gasToken: gasTokenAddress, gasAmount,
601
832
  amountOutMinETH,
602
833
  deadline });
603
- txHash = yield evmClientEvm.writeContract({
604
- abi: abi_1.UNIVERSAL_GATEWAY_V0,
605
- address: gatewayAddressEvm,
606
- functionName: 'sendUniversalTx',
607
- args: [reqToken],
608
- signer: this.universalSigner,
609
- });
834
+ txHash = yield this.sendGatewayTokenTxWithFallback(evmClientEvm, gatewayAddressEvm, reqToken, this.universalSigner);
610
835
  }
611
836
  else {
612
837
  // Existing native-ETH value path
@@ -631,14 +856,7 @@ class Orchestrator {
631
856
  const totalValue = isNativeToken
632
857
  ? nativeAmount + bridgeAmount
633
858
  : nativeAmount;
634
- txHash = yield evmClientEvm.writeContract({
635
- abi: abi_1.UNIVERSAL_GATEWAY_V0,
636
- address: gatewayAddressEvm,
637
- functionName: 'sendUniversalTx',
638
- args: [req],
639
- signer: this.universalSigner,
640
- value: totalValue,
641
- });
859
+ txHash = yield this.sendGatewayTxWithFallback(evmClientEvm, gatewayAddressEvm, req, this.universalSigner, totalValue);
642
860
  }
643
861
  }
644
862
  else {
@@ -705,7 +923,7 @@ class Orchestrator {
705
923
  pushChainUniversalTx =
706
924
  yield this.queryUniversalTxStatusFromGatewayTx(undefined, undefined, txHash, 'sendTxWithFunds');
707
925
  }
708
- if (!((_o = pushChainUniversalTx === null || pushChainUniversalTx === void 0 ? void 0 : pushChainUniversalTx.pcTx) === null || _o === void 0 ? void 0 : _o.length)) {
926
+ if (!((_r = pushChainUniversalTx === null || pushChainUniversalTx === void 0 ? void 0 : pushChainUniversalTx.pcTx) === null || _r === void 0 ? void 0 : _r.length)) {
709
927
  throw new Error(`Failed to retrieve Push Chain transaction status for gateway tx: ${txHash}. ` +
710
928
  `The transaction may have failed on Push Chain or not been indexed yet.`);
711
929
  }
@@ -752,17 +970,32 @@ class Orchestrator {
752
970
  const requiredFunds = requiredGasFee + execute.value;
753
971
  this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_02_02, requiredFunds);
754
972
  /**
755
- * Fetch UEA Details
973
+ * Fetch UEA Details (or use pre-fetched status if available)
756
974
  */
757
- this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_03_01);
758
975
  const UEA = this.computeUEAOffchain();
759
- const [code, funds] = yield Promise.all([
760
- this.pushClient.publicClient.getCode({ address: UEA }),
761
- this.pushClient.getBalance(UEA),
762
- ]);
763
- const isUEADeployed = code !== undefined;
764
- const nonce = isUEADeployed ? yield this.getUEANonce(UEA) : BigInt(0);
765
- this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_03_02, UEA, isUEADeployed);
976
+ let isUEADeployed;
977
+ let nonce;
978
+ let funds;
979
+ if (execute._ueaStatus) {
980
+ // Use pre-fetched UEA status (from executeUoaToCea)
981
+ isUEADeployed = execute._ueaStatus.isDeployed;
982
+ nonce = execute._ueaStatus.nonce;
983
+ funds = execute._ueaStatus.balance;
984
+ this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_03_01);
985
+ this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_03_02, UEA, isUEADeployed);
986
+ }
987
+ else {
988
+ // Fetch UEA status from Push Chain RPC
989
+ this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_03_01);
990
+ const [code, balance] = yield Promise.all([
991
+ this.pushClient.publicClient.getCode({ address: UEA }),
992
+ this.pushClient.getBalance(UEA),
993
+ ]);
994
+ isUEADeployed = code !== undefined;
995
+ nonce = isUEADeployed ? yield this.getUEANonce(UEA) : BigInt(0);
996
+ funds = balance;
997
+ this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_03_02, UEA, isUEADeployed);
998
+ }
766
999
  /**
767
1000
  * Compute Universal Payload Hash
768
1001
  */
@@ -772,9 +1005,11 @@ class Orchestrator {
772
1005
  const decoded = anchor_1.utils.bytes.bs58.decode(feeLockTxHash);
773
1006
  feeLockTxHash = (0, viem_1.bytesToHex)(new Uint8Array(decoded));
774
1007
  }
775
- // Fee locking is required if UEA is not deployed OR insufficient funds
776
- const feeLockingRequired = (!isUEADeployed || funds < requiredFunds) && !feeLockTxHash;
777
- // const feeLockingRequired = true;
1008
+ // Fee locking is required if UEA is not deployed OR insufficient funds.
1009
+ // Skip for outbound flows (UEA→CEA) those execute on Push Chain and
1010
+ // don't need external-chain fee locking.
1011
+ const feeLockingRequired = !execute._skipFeeLocking &&
1012
+ (!isUEADeployed || funds < requiredFunds) && !feeLockTxHash;
778
1013
  // Support multicall payload encoding when execute.data is an array
779
1014
  let payloadData;
780
1015
  let payloadTo;
@@ -931,7 +1166,7 @@ class Orchestrator {
931
1166
  * Note: queryTx may be undefined since validators don't recognize new UniversalTx event yet
932
1167
  */
933
1168
  // Transform to UniversalTxResponse (follow sendFunds pattern)
934
- if (!((_p = pushChainUniversalTx === null || pushChainUniversalTx === void 0 ? void 0 : pushChainUniversalTx.pcTx) === null || _p === void 0 ? void 0 : _p.length)) {
1169
+ if (!((_s = pushChainUniversalTx === null || pushChainUniversalTx === void 0 ? void 0 : pushChainUniversalTx.pcTx) === null || _s === void 0 ? void 0 : _s.length)) {
935
1170
  throw new Error(`Failed to retrieve Push Chain transaction status for gateway tx: ${feeLockTxHash}. ` +
936
1171
  `The transaction may have failed on Push Chain or not been indexed yet.`);
937
1172
  }
@@ -967,19 +1202,2179 @@ class Orchestrator {
967
1202
  try {
968
1203
  return JSON.stringify(err);
969
1204
  }
970
- catch (_a) {
971
- return 'Unknown error';
1205
+ catch (_a) {
1206
+ return 'Unknown error';
1207
+ }
1208
+ })();
1209
+ this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_99_02, errMessage);
1210
+ throw err;
1211
+ }
1212
+ finally {
1213
+ // Restore original progressHook
1214
+ this.progressHook = originalHook;
1215
+ }
1216
+ });
1217
+ }
1218
+ // ============================================================================
1219
+ // Multi-Chain Universal Transaction Methods
1220
+ // ============================================================================
1221
+ /**
1222
+ * Executes a universal transaction with multi-chain routing support.
1223
+ * Supports 4 routes:
1224
+ * - Route 1: UOA → Push Chain (existing behavior)
1225
+ * - Route 2: UOA → CEA (outbound to external chain)
1226
+ * - Route 3: CEA → Push Chain (inbound from external chain)
1227
+ * - Route 4: CEA → CEA (external chain to external chain via Push)
1228
+ *
1229
+ * @param params - Universal execution parameters with optional chain targets
1230
+ * @returns UniversalTxResponse with chain information
1231
+ */
1232
+ /**
1233
+ * Internal method for multi-chain transaction routing.
1234
+ * Called by execute() when ChainTarget or from.chain is detected.
1235
+ */
1236
+ executeMultiChain(params) {
1237
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
1238
+ // Validate route parameters
1239
+ (0, route_detector_1.validateRouteParams)(params, {
1240
+ clientChain: this.universalSigner.account.chain,
1241
+ });
1242
+ // Detect the transaction route
1243
+ const route = (0, route_detector_1.detectRoute)(params);
1244
+ this.printLog(`executeMultiChain — detected route: ${route}, params: ${JSON.stringify({
1245
+ to: typeof params.to === 'string'
1246
+ ? params.to
1247
+ : { address: params.to.address, chain: params.to.chain },
1248
+ from: params.from,
1249
+ hasValue: params.value !== undefined,
1250
+ hasData: params.data !== undefined,
1251
+ hasFunds: params.funds !== undefined,
1252
+ }, null, 2)}`);
1253
+ let response;
1254
+ switch (route) {
1255
+ case route_detector_1.TransactionRoute.UOA_TO_PUSH:
1256
+ // Route 1: Standard Push Chain execution
1257
+ response = yield this.execute(this.toExecuteParams(params));
1258
+ break;
1259
+ case route_detector_1.TransactionRoute.UOA_TO_CEA:
1260
+ // Route 2: Outbound to external chain via CEA
1261
+ response = yield this.executeUoaToCea(params);
1262
+ break;
1263
+ case route_detector_1.TransactionRoute.CEA_TO_PUSH:
1264
+ // Route 3: Inbound from CEA to Push Chain
1265
+ response = yield this.executeCeaToPush(params);
1266
+ break;
1267
+ case route_detector_1.TransactionRoute.CEA_TO_CEA:
1268
+ // Route 4: CEA to CEA via Push Chain
1269
+ response = yield this.executeCeaToCea(params);
1270
+ break;
1271
+ default:
1272
+ throw new Error(`Unknown transaction route: ${route}`);
1273
+ }
1274
+ // Set the route on the response for .wait() to use
1275
+ response.route = route;
1276
+ return response;
1277
+ });
1278
+ }
1279
+ /**
1280
+ * Prepare a universal transaction without executing it.
1281
+ * Returns a PreparedUniversalTx that can be chained with thenOn() or sent with send().
1282
+ *
1283
+ * @param params - Universal execution parameters
1284
+ * @returns PreparedUniversalTx with chaining capabilities
1285
+ */
1286
+ prepareTransaction(params) {
1287
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
1288
+ (0, route_detector_1.validateRouteParams)(params, {
1289
+ clientChain: this.universalSigner.account.chain,
1290
+ });
1291
+ const route = (0, route_detector_1.detectRoute)(params);
1292
+ const { nonce, deployed } = yield this.getUeaStatusAndNonce();
1293
+ const ueaAddress = this.computeUEAOffchain();
1294
+ // Build the payload based on route
1295
+ const { payload, gatewayRequest } = yield this.buildPayloadForRoute(params, route, nonce);
1296
+ const gasEstimate = params.gasLimit || selectors_1.DEFAULT_OUTBOUND_GAS_LIMIT;
1297
+ const deadline = params.deadline || BigInt(Math.floor(Date.now() / 1000) + 3600);
1298
+ // Build the HopDescriptor with all metadata needed for cascade nesting
1299
+ const hop = yield this.buildHopDescriptor(params, route, ueaAddress);
1300
+ const prepared = {
1301
+ route,
1302
+ payload,
1303
+ gatewayRequest,
1304
+ estimatedGas: gasEstimate,
1305
+ nonce,
1306
+ deadline,
1307
+ _hop: hop,
1308
+ thenOn: (nextTx) => this.createCascadedBuilder([prepared, nextTx]),
1309
+ send: () => this.executeMultiChain(params),
1310
+ };
1311
+ return prepared;
1312
+ });
1313
+ }
1314
+ /**
1315
+ * Build a HopDescriptor for a prepared transaction.
1316
+ * Resolves CEA addresses, queries gas fees, and builds multicall arrays.
1317
+ *
1318
+ * @param params - Original user params
1319
+ * @param route - Detected route
1320
+ * @param ueaAddress - UEA address
1321
+ * @returns HopDescriptor with all metadata
1322
+ */
1323
+ buildHopDescriptor(params, route, ueaAddress) {
1324
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
1325
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
1326
+ // Pass 0 when user omits gasLimit → contract uses per-chain baseGasLimitByChainNamespace
1327
+ const gasLimit = (_a = params.gasLimit) !== null && _a !== void 0 ? _a : BigInt(0);
1328
+ const routeStr = route;
1329
+ const baseDescriptor = {
1330
+ params,
1331
+ route: routeStr,
1332
+ gasLimit,
1333
+ ueaAddress,
1334
+ revertRecipient: ueaAddress,
1335
+ };
1336
+ switch (route) {
1337
+ case route_detector_1.TransactionRoute.UOA_TO_PUSH: {
1338
+ // Route 1: Build Push Chain multicalls
1339
+ const executeParams = this.toExecuteParams(params);
1340
+ const pushMulticalls = (0, payload_builders_1.buildExecuteMulticall)({
1341
+ execute: executeParams,
1342
+ ueaAddress,
1343
+ });
1344
+ return Object.assign(Object.assign({}, baseDescriptor), { pushMulticalls });
1345
+ }
1346
+ case route_detector_1.TransactionRoute.UOA_TO_CEA: {
1347
+ // Route 2: Build outbound metadata
1348
+ const target = params.to;
1349
+ const targetChain = target.chain;
1350
+ // Branch: SVM vs EVM
1351
+ if ((0, payload_builders_1.isSvmChain)(targetChain)) {
1352
+ // SVM path: no CEA lookup, build SVM payload
1353
+ const hasSvmExecute = !!params.svmExecute;
1354
+ let svmPayload = '0x';
1355
+ if (hasSvmExecute) {
1356
+ const exec = params.svmExecute;
1357
+ svmPayload = (0, payload_builders_1.encodeSvmExecutePayload)({
1358
+ targetProgram: exec.targetProgram,
1359
+ accounts: exec.accounts,
1360
+ ixData: exec.ixData,
1361
+ instructionId: 2,
1362
+ });
1363
+ }
1364
+ let prc20Token = selectors_1.ZERO_ADDRESS;
1365
+ let burnAmount = BigInt(0);
1366
+ if ((_b = params.funds) === null || _b === void 0 ? void 0 : _b.amount) {
1367
+ const token = params.funds.token;
1368
+ if (token) {
1369
+ prc20Token = push_chain_1.PushChain.utils.tokens.getPRC20Address(token);
1370
+ burnAmount = params.funds.amount;
1371
+ }
1372
+ }
1373
+ else if (params.value && params.value > BigInt(0)) {
1374
+ prc20Token = this.getNativePRC20ForChain(targetChain);
1375
+ burnAmount = params.value;
1376
+ }
1377
+ else if (hasSvmExecute) {
1378
+ prc20Token = this.getNativePRC20ForChain(targetChain);
1379
+ burnAmount = BigInt(1);
1380
+ }
1381
+ let gasToken = selectors_1.ZERO_ADDRESS;
1382
+ let gasFee = BigInt(0);
1383
+ if (prc20Token !== selectors_1.ZERO_ADDRESS) {
1384
+ const result = yield this.queryOutboundGasFee(prc20Token, gasLimit);
1385
+ gasToken = result.gasToken;
1386
+ gasFee = result.gasFee;
1387
+ }
1388
+ return Object.assign(Object.assign({}, baseDescriptor), { targetChain, isSvmTarget: true, svmPayload,
1389
+ prc20Token,
1390
+ burnAmount,
1391
+ gasToken,
1392
+ gasFee });
1393
+ }
1394
+ // EVM path: Resolve CEA address + build CEA multicalls
1395
+ const { cea: ceaAddress } = yield (0, cea_utils_1.getCEAAddress)(ueaAddress, targetChain, (_c = this.rpcUrls[targetChain]) === null || _c === void 0 ? void 0 : _c[0]);
1396
+ // Migration path: raw MIGRATION_SELECTOR payload, no multicall wrapping
1397
+ if (params.migration) {
1398
+ const prc20Token = this.getNativePRC20ForChain(targetChain);
1399
+ const burnAmount = BigInt(0); // Migration is logic-only — no funds. CEA rejects msg.value != 0.
1400
+ let gasToken = selectors_1.ZERO_ADDRESS;
1401
+ let gasFee = BigInt(0);
1402
+ if (prc20Token !== selectors_1.ZERO_ADDRESS) {
1403
+ const result = yield this.queryOutboundGasFee(prc20Token, gasLimit);
1404
+ gasToken = result.gasToken;
1405
+ gasFee = result.gasFee;
1406
+ }
1407
+ return Object.assign(Object.assign({}, baseDescriptor), { targetChain,
1408
+ ceaAddress, ceaMulticalls: [], prc20Token,
1409
+ burnAmount,
1410
+ gasToken,
1411
+ gasFee, isMigration: true });
1412
+ }
1413
+ // Build CEA multicalls
1414
+ const ceaMulticalls = [];
1415
+ if (params.data) {
1416
+ if (Array.isArray(params.data)) {
1417
+ ceaMulticalls.push(...params.data);
1418
+ }
1419
+ else {
1420
+ // When ERC-20 funds are provided with a single payload, auto-prepend a
1421
+ // transfer() call so the tokens minted to the CEA are forwarded to the
1422
+ // target address. This mirrors the Route 1 behavior in buildExecuteMulticall.
1423
+ if ((_d = params.funds) === null || _d === void 0 ? void 0 : _d.amount) {
1424
+ const token = params.funds.token;
1425
+ if (token && token.mechanism !== 'native') {
1426
+ const erc20Transfer = (0, viem_1.encodeFunctionData)({
1427
+ abi: abi_1.ERC20_EVM,
1428
+ functionName: 'transfer',
1429
+ args: [target.address, params.funds.amount],
1430
+ });
1431
+ ceaMulticalls.push({
1432
+ to: token.address,
1433
+ value: BigInt(0),
1434
+ data: erc20Transfer,
1435
+ });
1436
+ }
1437
+ }
1438
+ // Single call with data. Forward native value (if any) so the target
1439
+ // contract receives it alongside the payload call. The vault deposits
1440
+ // native value to the CEA, and the multicall forwards it to the target.
1441
+ ceaMulticalls.push({
1442
+ to: target.address,
1443
+ value: (_e = params.value) !== null && _e !== void 0 ? _e : BigInt(0),
1444
+ data: params.data,
1445
+ });
1446
+ }
1447
+ }
1448
+ else if (params.value) {
1449
+ // Skip multicall when sending native value to own CEA — gateway deposits directly.
1450
+ // Self-call with value would revert (CEA._handleMulticall rejects it).
1451
+ if (target.address.toLowerCase() !== ceaAddress.toLowerCase()) {
1452
+ ceaMulticalls.push({
1453
+ to: target.address,
1454
+ value: params.value,
1455
+ data: '0x',
1456
+ });
1457
+ }
1458
+ }
1459
+ // Determine PRC-20 token and burn amount
1460
+ let prc20Token = selectors_1.ZERO_ADDRESS;
1461
+ let burnAmount = BigInt(0);
1462
+ if ((_f = params.funds) === null || _f === void 0 ? void 0 : _f.amount) {
1463
+ const token = params.funds.token;
1464
+ if (token) {
1465
+ prc20Token = push_chain_1.PushChain.utils.tokens.getPRC20Address(token);
1466
+ burnAmount = params.funds.amount;
1467
+ }
1468
+ }
1469
+ else if (params.value && params.value > BigInt(0)) {
1470
+ prc20Token = this.getNativePRC20ForChain(targetChain);
1471
+ burnAmount = params.value;
1472
+ }
1473
+ else if (params.data) {
1474
+ prc20Token = this.getNativePRC20ForChain(targetChain);
1475
+ burnAmount = BigInt(1); // Minimum for precompile
1476
+ }
1477
+ // Query gas fee
1478
+ let gasToken = selectors_1.ZERO_ADDRESS;
1479
+ let gasFee = BigInt(0);
1480
+ if (prc20Token !== selectors_1.ZERO_ADDRESS) {
1481
+ const result = yield this.queryOutboundGasFee(prc20Token, gasLimit);
1482
+ gasToken = result.gasToken;
1483
+ gasFee = result.gasFee;
1484
+ }
1485
+ return Object.assign(Object.assign({}, baseDescriptor), { targetChain,
1486
+ ceaAddress,
1487
+ ceaMulticalls,
1488
+ prc20Token,
1489
+ burnAmount,
1490
+ gasToken,
1491
+ gasFee });
1492
+ }
1493
+ case route_detector_1.TransactionRoute.CEA_TO_PUSH: {
1494
+ // Route 3: Build CEA multicalls for sendUniversalTxFromCEA
1495
+ const sourceChain = params.from.chain;
1496
+ // SVM chains use PDA-based CEA, not factory-deployed CEA
1497
+ if ((0, payload_builders_1.isSvmChain)(sourceChain)) {
1498
+ const lockerContract = chain_1.CHAIN_INFO[sourceChain].lockerContract;
1499
+ if (!lockerContract) {
1500
+ throw new Error(`No SVM gateway program configured for chain ${sourceChain}`);
1501
+ }
1502
+ const programPk = new web3_js_1.PublicKey(lockerContract);
1503
+ const gatewayProgramHex = ('0x' + Buffer.from(programPk.toBytes()).toString('hex'));
1504
+ // Derive CEA PDA
1505
+ const ueaBytes = Buffer.from(ueaAddress.slice(2), 'hex');
1506
+ const [ceaPda] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from('push_identity'), ueaBytes], programPk);
1507
+ const ceaPdaHex = ('0x' + Buffer.from(ceaPda.toBytes()).toString('hex'));
1508
+ let amount = BigInt(0);
1509
+ let prc20Token;
1510
+ if (((_g = params.funds) === null || _g === void 0 ? void 0 : _g.amount) && params.funds.amount > BigInt(0)) {
1511
+ amount = params.funds.amount;
1512
+ const token = params.funds.token;
1513
+ if (token && token.address) {
1514
+ prc20Token = push_chain_1.PushChain.utils.tokens.getPRC20Address(token);
1515
+ }
1516
+ else {
1517
+ prc20Token = this.getNativePRC20ForChain(sourceChain);
1518
+ }
1519
+ }
1520
+ else if (params.value && params.value > BigInt(0)) {
1521
+ amount = params.value;
1522
+ prc20Token = this.getNativePRC20ForChain(sourceChain);
1523
+ }
1524
+ else {
1525
+ // Payload-only Route 3 SVM
1526
+ prc20Token = this.getNativePRC20ForChain(sourceChain);
1527
+ }
1528
+ let gasToken = selectors_1.ZERO_ADDRESS;
1529
+ let gasFee = BigInt(0);
1530
+ if (prc20Token !== selectors_1.ZERO_ADDRESS) {
1531
+ const result = yield this.queryOutboundGasFee(prc20Token, gasLimit);
1532
+ gasToken = result.gasToken;
1533
+ gasFee = result.gasFee;
1534
+ }
1535
+ return Object.assign(Object.assign({}, baseDescriptor), { sourceChain, ceaAddress: ceaPdaHex, isSvmTarget: true, prc20Token, burnAmount: amount > BigInt(0) ? amount : BigInt(1), gasToken,
1536
+ gasFee });
1537
+ }
1538
+ const { cea: ceaAddress, isDeployed } = yield (0, cea_utils_1.getCEAAddress)(ueaAddress, sourceChain, (_h = this.rpcUrls[sourceChain]) === null || _h === void 0 ? void 0 : _h[0]);
1539
+ // Determine token/amount for the inbound
1540
+ let tokenAddress = selectors_1.ZERO_ADDRESS;
1541
+ let amount = BigInt(0);
1542
+ let nativeValue = BigInt(0);
1543
+ if ((_j = params.funds) === null || _j === void 0 ? void 0 : _j.amount) {
1544
+ const token = params.funds.token;
1545
+ if (token) {
1546
+ if (token.mechanism === 'native') {
1547
+ amount = params.funds.amount;
1548
+ nativeValue = params.funds.amount;
1549
+ }
1550
+ else {
1551
+ tokenAddress = token.address;
1552
+ amount = params.funds.amount;
1553
+ }
1554
+ }
1555
+ }
1556
+ else if (params.value && params.value > BigInt(0)) {
1557
+ amount = params.value;
1558
+ nativeValue = params.value;
1559
+ }
1560
+ // The PRC-20 for the outbound wrapper (Route 2 to source chain)
1561
+ const prc20Token = this.getNativePRC20ForChain(sourceChain);
1562
+ let gasToken = selectors_1.ZERO_ADDRESS;
1563
+ let gasFee = BigInt(0);
1564
+ if (prc20Token !== selectors_1.ZERO_ADDRESS) {
1565
+ const result = yield this.queryOutboundGasFee(prc20Token, gasLimit);
1566
+ gasToken = result.gasToken;
1567
+ gasFee = result.gasFee;
1568
+ }
1569
+ return Object.assign(Object.assign({}, baseDescriptor), { sourceChain,
1570
+ ceaAddress,
1571
+ prc20Token, burnAmount: amount > BigInt(0) ? amount : BigInt(1), gasToken,
1572
+ gasFee });
1573
+ }
1574
+ default:
1575
+ return baseDescriptor;
1576
+ }
1577
+ });
1578
+ }
1579
+ /**
1580
+ * Query outbound gas fee from UniversalCore contract.
1581
+ * Extracted from executeUoaToCea for reuse.
1582
+ *
1583
+ * @param prc20Token - PRC-20 token address
1584
+ * @param gasLimit - Gas limit for the outbound
1585
+ * @returns gasToken address and gasFee amount
1586
+ */
1587
+ queryOutboundGasFee(prc20Token, gasLimit) {
1588
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
1589
+ var _a, _b;
1590
+ const gatewayPcAddress = this.getUniversalGatewayPCAddress();
1591
+ const pushChain = this.getPushChainForNetwork();
1592
+ const rpcUrl = ((_b = (_a = chain_1.CHAIN_INFO[pushChain]) === null || _a === void 0 ? void 0 : _a.defaultRPC) === null || _b === void 0 ? void 0 : _b[0]) || 'unknown';
1593
+ this.printLog(`queryOutboundGasFee — [step 1] inputs: gateway=${gatewayPcAddress}, prc20Token=${prc20Token}, gasLimit=${gasLimit}, pushNetwork=${this.pushNetwork}, rpcUrl=${rpcUrl}`);
1594
+ // Step 2: Get UNIVERSAL_CORE address from gateway
1595
+ let universalCoreAddress;
1596
+ try {
1597
+ const gatewayCallData = (0, viem_1.encodeFunctionData)({
1598
+ abi: abi_1.UNIVERSAL_GATEWAY_PC,
1599
+ functionName: 'UNIVERSAL_CORE',
1600
+ args: [],
1601
+ });
1602
+ this.printLog(`queryOutboundGasFee — [step 2] reading UNIVERSAL_CORE from ${gatewayPcAddress}, callData=${gatewayCallData}`);
1603
+ universalCoreAddress = yield this.pushClient.readContract({
1604
+ address: gatewayPcAddress,
1605
+ abi: abi_1.UNIVERSAL_GATEWAY_PC,
1606
+ functionName: 'UNIVERSAL_CORE',
1607
+ args: [],
1608
+ });
1609
+ this.printLog(`queryOutboundGasFee — [step 2] UNIVERSAL_CORE resolved to: ${universalCoreAddress}`);
1610
+ }
1611
+ catch (err) {
1612
+ this.printLog(`queryOutboundGasFee — [step 2] FAILED to read UNIVERSAL_CORE: ${err instanceof Error ? err.message : String(err)}`);
1613
+ throw err;
1614
+ }
1615
+ // Step 3: Call getOutboundTxGasAndFees on UNIVERSAL_CORE
1616
+ const callData = (0, viem_1.encodeFunctionData)({
1617
+ abi: abi_1.UNIVERSAL_CORE_EVM,
1618
+ functionName: 'getOutboundTxGasAndFees',
1619
+ args: [prc20Token, gasLimit],
1620
+ });
1621
+ this.printLog(`queryOutboundGasFee — [step 3] calling getOutboundTxGasAndFees on ${universalCoreAddress}`);
1622
+ this.printLog(`queryOutboundGasFee — [step 3] eth_call: {"method":"eth_call","params":[{"to":"${universalCoreAddress}","data":"${callData}"},"latest"]}`);
1623
+ let gasToken;
1624
+ let gasFee;
1625
+ let protocolFee;
1626
+ let gasPrice = BigInt(0);
1627
+ try {
1628
+ const result = yield this.pushClient.readContract({
1629
+ address: universalCoreAddress,
1630
+ abi: abi_1.UNIVERSAL_CORE_EVM,
1631
+ functionName: 'getOutboundTxGasAndFees',
1632
+ args: [prc20Token, gasLimit],
1633
+ });
1634
+ gasToken = result[0];
1635
+ gasFee = result[1];
1636
+ protocolFee = result[2];
1637
+ gasPrice = result[3];
1638
+ this.printLog(`queryOutboundGasFee — [step 4] success: gasToken=${gasToken}, gasFee=${gasFee}, protocolFee=${protocolFee}, gasPrice=${gasPrice}`);
1639
+ }
1640
+ catch (err) {
1641
+ this.printLog(`queryOutboundGasFee — [step 3] FAILED: ${err instanceof Error ? err.message : String(err)}`);
1642
+ throw err;
1643
+ }
1644
+ // gasFee is in gas token units — exchange rate to PC is unknown without quoter.
1645
+ // Use 1000000x buffer; excess is refunded by swapAndBurnGas to the UEA.
1646
+ const nativeValueForGas = protocolFee + (gasFee * BigInt(1000000));
1647
+ this.printLog(`queryOutboundGasFee — [step 5] using 1000000x buffer: nativeValueForGas=${nativeValueForGas}`);
1648
+ return { gasToken, gasFee, protocolFee, nativeValueForGas, gasPrice };
1649
+ });
1650
+ }
1651
+ // ============================================================================
1652
+ // Cascade Composition (Advance Hopping)
1653
+ // ============================================================================
1654
+ /**
1655
+ * Classify hops into segments for cascade composition.
1656
+ * Consecutive same-type/same-chain hops are merged.
1657
+ *
1658
+ * @param hops - Array of HopDescriptors from prepared transactions
1659
+ * @returns Array of CascadeSegments
1660
+ */
1661
+ classifyIntoSegments(hops) {
1662
+ if (hops.length === 0)
1663
+ return [];
1664
+ const segments = [];
1665
+ let currentSegment = null;
1666
+ for (const hop of hops) {
1667
+ const segType = this.getSegmentType(hop.route);
1668
+ const chain = hop.targetChain || hop.sourceChain;
1669
+ const canMerge = currentSegment &&
1670
+ currentSegment.type === segType &&
1671
+ // Same-chain merging for OUTBOUND_TO_CEA (EVM only — SVM hops are atomic)
1672
+ (segType === 'OUTBOUND_TO_CEA'
1673
+ ? currentSegment.targetChain === hop.targetChain &&
1674
+ !hop.isSvmTarget
1675
+ : segType === 'PUSH_EXECUTION');
1676
+ if (canMerge && currentSegment) {
1677
+ // Merge into current segment
1678
+ currentSegment.hops.push(hop);
1679
+ if (segType === 'OUTBOUND_TO_CEA') {
1680
+ currentSegment.mergedCeaMulticalls = [
1681
+ ...(currentSegment.mergedCeaMulticalls || []),
1682
+ ...(hop.ceaMulticalls || []),
1683
+ ];
1684
+ currentSegment.totalBurnAmount =
1685
+ (currentSegment.totalBurnAmount || BigInt(0)) +
1686
+ (hop.burnAmount || BigInt(0));
1687
+ // Gas fee: take the max gasLimit across merged hops
1688
+ if (hop.gasLimit > (currentSegment.gasLimit || BigInt(0))) {
1689
+ currentSegment.gasLimit = hop.gasLimit;
1690
+ }
1691
+ // Accumulate gas fees
1692
+ currentSegment.gasFee =
1693
+ (currentSegment.gasFee || BigInt(0)) +
1694
+ (hop.gasFee || BigInt(0));
1695
+ }
1696
+ else if (segType === 'PUSH_EXECUTION') {
1697
+ currentSegment.mergedPushMulticalls = [
1698
+ ...(currentSegment.mergedPushMulticalls || []),
1699
+ ...(hop.pushMulticalls || []),
1700
+ ];
1701
+ }
1702
+ }
1703
+ else {
1704
+ // Start a new segment
1705
+ currentSegment = {
1706
+ type: segType,
1707
+ hops: [hop],
1708
+ targetChain: hop.targetChain,
1709
+ sourceChain: hop.sourceChain,
1710
+ mergedCeaMulticalls: segType === 'OUTBOUND_TO_CEA' ? [...(hop.ceaMulticalls || [])] : undefined,
1711
+ mergedPushMulticalls: segType === 'PUSH_EXECUTION' ? [...(hop.pushMulticalls || [])] : undefined,
1712
+ totalBurnAmount: hop.burnAmount,
1713
+ prc20Token: hop.prc20Token,
1714
+ gasToken: hop.gasToken,
1715
+ gasFee: hop.gasFee,
1716
+ gasLimit: hop.gasLimit,
1717
+ };
1718
+ segments.push(currentSegment);
1719
+ }
1720
+ }
1721
+ return segments;
1722
+ }
1723
+ /**
1724
+ * Map route to segment type
1725
+ */
1726
+ getSegmentType(route) {
1727
+ switch (route) {
1728
+ case 'UOA_TO_PUSH':
1729
+ return 'PUSH_EXECUTION';
1730
+ case 'UOA_TO_CEA':
1731
+ return 'OUTBOUND_TO_CEA';
1732
+ case 'CEA_TO_PUSH':
1733
+ return 'INBOUND_FROM_CEA';
1734
+ default:
1735
+ return 'PUSH_EXECUTION';
1736
+ }
1737
+ }
1738
+ /**
1739
+ * Compose cascade from segments using bottom-to-top nesting.
1740
+ * Processes segments in reverse order, building nested payloads.
1741
+ *
1742
+ * @param segments - Classified segments from classifyIntoSegments()
1743
+ * @param ueaAddress - UEA address
1744
+ * @returns Final MultiCall[] to execute as the initial UEA multicall
1745
+ */
1746
+ composeCascade(segments, ueaAddress, ueaBalance, ueaNonce) {
1747
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
1748
+ let accumulatedPushMulticalls = [];
1749
+ const gatewayPcAddress = this.getUniversalGatewayPCAddress();
1750
+ // Compute per-outbound nativeValueForGas from UEA balance
1751
+ // Each outbound segment needs native value for the gas swap on the destination chain.
1752
+ // The contract refunds excess, so over-allocating is safe.
1753
+ const numOutbounds = segments.filter(s => s.type !== 'PUSH_EXECUTION').length;
1754
+ const CASCADE_GAS_RESERVE = BigInt(3e18); // 3 PC reserve for gas costs
1755
+ let perOutboundNativeValue;
1756
+ if (ueaBalance && numOutbounds > 0 && ueaBalance > CASCADE_GAS_RESERVE) {
1757
+ perOutboundNativeValue = (ueaBalance - CASCADE_GAS_RESERVE) / BigInt(numOutbounds);
1758
+ }
1759
+ for (let i = segments.length - 1; i >= 0; i--) {
1760
+ const segment = segments[i];
1761
+ switch (segment.type) {
1762
+ case 'PUSH_EXECUTION': {
1763
+ // Prepend Push Chain multicalls to accumulated
1764
+ accumulatedPushMulticalls = [
1765
+ ...(segment.mergedPushMulticalls || []),
1766
+ ...accumulatedPushMulticalls,
1767
+ ];
1768
+ break;
1769
+ }
1770
+ case 'OUTBOUND_TO_CEA': {
1771
+ const firstHop = segment.hops[0];
1772
+ const isSvmSegment = (firstHop === null || firstHop === void 0 ? void 0 : firstHop.isSvmTarget) === true;
1773
+ let outboundPayload;
1774
+ let targetForOutbound;
1775
+ const isMigration = (firstHop === null || firstHop === void 0 ? void 0 : firstHop.isMigration) === true;
1776
+ if (isSvmSegment) {
1777
+ // SVM: use the pre-built SVM payload from the hop descriptor
1778
+ outboundPayload = (_a = firstHop.svmPayload) !== null && _a !== void 0 ? _a : '0x';
1779
+ // For SVM, target is the recipient/program from the hop params
1780
+ const svmTarget = firstHop.params.to;
1781
+ targetForOutbound = (_c = (_b = firstHop.params.svmExecute) === null || _b === void 0 ? void 0 : _b.targetProgram) !== null && _c !== void 0 ? _c : svmTarget.address;
1782
+ }
1783
+ else if (isMigration) {
1784
+ // Migration: use raw 4-byte MIGRATION_SELECTOR, no multicall wrapping
1785
+ outboundPayload = (0, payload_builders_1.buildMigrationPayload)();
1786
+ targetForOutbound = (firstHop === null || firstHop === void 0 ? void 0 : firstHop.ceaAddress) || ueaAddress;
1787
+ }
1788
+ else {
1789
+ // EVM: build CEA payload from merged multicalls
1790
+ outboundPayload = (0, payload_builders_1.buildCeaMulticallPayload)(segment.mergedCeaMulticalls || []);
1791
+ // Get CEA address from the first hop
1792
+ targetForOutbound = (firstHop === null || firstHop === void 0 ? void 0 : firstHop.ceaAddress) || ueaAddress;
1793
+ }
1794
+ // Build outbound request
1795
+ const outboundReq = (0, payload_builders_1.buildOutboundRequest)(targetForOutbound, segment.prc20Token || selectors_1.ZERO_ADDRESS, segment.totalBurnAmount || BigInt(0), (_d = segment.gasLimit) !== null && _d !== void 0 ? _d : BigInt(0), outboundPayload, ueaAddress);
1796
+ // Build approval + outbound multicalls
1797
+ const segGasFee = segment.gasFee || BigInt(0);
1798
+ const outboundMulticalls = (0, payload_builders_1.buildOutboundApprovalAndCall)({
1799
+ prc20Token: segment.prc20Token || selectors_1.ZERO_ADDRESS,
1800
+ gasToken: segment.gasToken || selectors_1.ZERO_ADDRESS,
1801
+ burnAmount: segment.totalBurnAmount || BigInt(0),
1802
+ gasFee: segGasFee,
1803
+ nativeValueForGas: perOutboundNativeValue !== null && perOutboundNativeValue !== void 0 ? perOutboundNativeValue : segGasFee * BigInt(1000),
1804
+ gatewayPcAddress,
1805
+ outboundRequest: outboundReq,
1806
+ });
1807
+ // Prepend to accumulated
1808
+ accumulatedPushMulticalls = [
1809
+ ...outboundMulticalls,
1810
+ ...accumulatedPushMulticalls,
1811
+ ];
1812
+ break;
1813
+ }
1814
+ case 'INBOUND_FROM_CEA': {
1815
+ // The accumulated multicalls = what runs on Push Chain AFTER inbound arrives.
1816
+ // Wrap in UniversalPayload struct with correct UEA nonce for the relay.
1817
+ // Build push multicalls from this hop's own data (e.g., counter.increment())
1818
+ // This is the Route 3 hop's payload that executes on Push Chain after inbound.
1819
+ const hop0 = segment.hops[0];
1820
+ if ((_e = hop0 === null || hop0 === void 0 ? void 0 : hop0.params) === null || _e === void 0 ? void 0 : _e.data) {
1821
+ const hopPushMulticalls = (0, payload_builders_1.buildExecuteMulticall)({
1822
+ execute: {
1823
+ to: hop0.params.to,
1824
+ value: hop0.params.value,
1825
+ data: hop0.params.data,
1826
+ },
1827
+ ueaAddress,
1828
+ });
1829
+ // Prepend the Route 3's own push calls before subsequent hops
1830
+ accumulatedPushMulticalls = [
1831
+ ...hopPushMulticalls,
1832
+ ...accumulatedPushMulticalls,
1833
+ ];
1834
+ }
1835
+ let intermediatePayload = '0x';
1836
+ if (accumulatedPushMulticalls.length > 0) {
1837
+ const multicallPayload = this._buildMulticallPayloadData(ueaAddress, accumulatedPushMulticalls);
1838
+ // +1: the outbound tx consumes one nonce via execute()
1839
+ intermediatePayload = (0, payload_builders_1.buildInboundUniversalPayload)(multicallPayload, { nonce: (ueaNonce !== null && ueaNonce !== void 0 ? ueaNonce : BigInt(0)) + BigInt(1) });
1840
+ }
1841
+ const hop = segment.hops[0];
1842
+ const sourceChain = hop.sourceChain;
1843
+ const ceaAddress = hop.ceaAddress || ueaAddress;
1844
+ // SVM chains: build SVM CPI payload instead of EVM CEA multicall
1845
+ if ((0, payload_builders_1.isSvmChain)(sourceChain)) {
1846
+ const lockerContract = chain_1.CHAIN_INFO[sourceChain].lockerContract;
1847
+ if (!lockerContract) {
1848
+ throw new Error(`No SVM gateway program configured for chain ${sourceChain}`);
1849
+ }
1850
+ const programPk = new web3_js_1.PublicKey(lockerContract);
1851
+ const gatewayProgramHex = ('0x' + Buffer.from(programPk.toBytes()).toString('hex'));
1852
+ let drainAmount = BigInt(0);
1853
+ let tokenMintHex;
1854
+ const params = hop.params;
1855
+ if (((_f = params.funds) === null || _f === void 0 ? void 0 : _f.amount) && params.funds.amount > BigInt(0)) {
1856
+ drainAmount = params.funds.amount;
1857
+ const token = params.funds.token;
1858
+ if (token && token.address) {
1859
+ const mintPk = new web3_js_1.PublicKey(token.address);
1860
+ tokenMintHex = ('0x' + Buffer.from(mintPk.toBytes()).toString('hex'));
1861
+ }
1862
+ }
1863
+ else if (params.value && params.value > BigInt(0)) {
1864
+ drainAmount = params.value;
1865
+ }
1866
+ // Derive CEA PDA as revert recipient
1867
+ const ueaBytes = Buffer.from(ueaAddress.slice(2), 'hex');
1868
+ const [ceaPda] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from('push_identity'), ueaBytes], programPk);
1869
+ const ceaPdaHex = ('0x' + Buffer.from(ceaPda.toBytes()).toString('hex'));
1870
+ // Build SVM payload with intermediate Push Chain payload embedded
1871
+ const svmPayload = (0, payload_builders_1.encodeSvmCeaToUeaPayload)({
1872
+ gatewayProgramHex,
1873
+ drainAmount,
1874
+ tokenMintHex,
1875
+ revertRecipientHex: ceaPdaHex,
1876
+ extraPayload: intermediatePayload !== '0x'
1877
+ ? new Uint8Array(Buffer.from(intermediatePayload.slice(2), 'hex'))
1878
+ : undefined,
1879
+ });
1880
+ const burnAmount = BigInt(1);
1881
+ const outboundReq = (0, payload_builders_1.buildOutboundRequest)(gatewayProgramHex, segment.prc20Token || this.getNativePRC20ForChain(sourceChain), burnAmount, (_g = segment.gasLimit) !== null && _g !== void 0 ? _g : BigInt(0), svmPayload, ueaAddress);
1882
+ const inboundGasFee = segment.gasFee || BigInt(0);
1883
+ const outboundMulticalls = (0, payload_builders_1.buildOutboundApprovalAndCall)({
1884
+ prc20Token: segment.prc20Token || this.getNativePRC20ForChain(sourceChain),
1885
+ gasToken: segment.gasToken || selectors_1.ZERO_ADDRESS,
1886
+ burnAmount,
1887
+ gasFee: inboundGasFee,
1888
+ nativeValueForGas: perOutboundNativeValue !== null && perOutboundNativeValue !== void 0 ? perOutboundNativeValue : inboundGasFee * BigInt(1000),
1889
+ gatewayPcAddress,
1890
+ outboundRequest: outboundReq,
1891
+ });
1892
+ accumulatedPushMulticalls = [...outboundMulticalls];
1893
+ break;
1894
+ }
1895
+ // EVM path: Build CEA multicall: [approve?, sendUniversalTxFromCEA(payload)]
1896
+ const ceaMulticalls = [];
1897
+ // Add hop's own CEA operations if any
1898
+ // (e.g., approve + swap before bridging back)
1899
+ if (hop.ceaMulticalls && hop.ceaMulticalls.length > 0) {
1900
+ ceaMulticalls.push(...hop.ceaMulticalls);
1901
+ }
1902
+ // Determine token/amount for inbound
1903
+ let tokenAddress = selectors_1.ZERO_ADDRESS;
1904
+ let amount = BigInt(0);
1905
+ const params = hop.params;
1906
+ if ((_h = params.funds) === null || _h === void 0 ? void 0 : _h.amount) {
1907
+ const token = params.funds.token;
1908
+ if (token) {
1909
+ if (token.mechanism === 'native') {
1910
+ amount = params.funds.amount;
1911
+ }
1912
+ else {
1913
+ tokenAddress = token.address;
1914
+ amount = params.funds.amount;
1915
+ // Add approve for ERC20 (CEA approves gateway)
1916
+ const gatewayAddr = chain_1.UNIVERSAL_GATEWAY_ADDRESSES[sourceChain];
1917
+ if (!gatewayAddr) {
1918
+ throw new Error(`No UniversalGateway address configured for chain ${sourceChain}`);
1919
+ }
1920
+ const approveData = (0, viem_1.encodeFunctionData)({
1921
+ abi: abi_1.ERC20_EVM,
1922
+ functionName: 'approve',
1923
+ args: [gatewayAddr, amount],
1924
+ });
1925
+ ceaMulticalls.push({
1926
+ to: tokenAddress,
1927
+ value: BigInt(0),
1928
+ data: approveData,
1929
+ });
1930
+ }
1931
+ }
1932
+ }
1933
+ else if (params.value && params.value > BigInt(0)) {
1934
+ amount = params.value;
1935
+ }
1936
+ // Build sendUniversalTxToUEA self-call on CEA (to=CEA, value=0)
1937
+ const sendCall = (0, payload_builders_1.buildSendUniversalTxToUEA)(ceaAddress, tokenAddress, amount, intermediatePayload, ceaAddress);
1938
+ ceaMulticalls.push(sendCall);
1939
+ // Wrap CEA multicall in outbound from Push Chain
1940
+ const ceaPayload = (0, payload_builders_1.buildCeaMulticallPayload)(ceaMulticalls);
1941
+ const outboundReq = (0, payload_builders_1.buildOutboundRequest)(ceaAddress, segment.prc20Token || this.getNativePRC20ForChain(sourceChain), segment.totalBurnAmount || BigInt(1), (_j = segment.gasLimit) !== null && _j !== void 0 ? _j : BigInt(0), ceaPayload, ueaAddress);
1942
+ const inboundGasFee = segment.gasFee || BigInt(0);
1943
+ const outboundMulticalls = (0, payload_builders_1.buildOutboundApprovalAndCall)({
1944
+ prc20Token: segment.prc20Token || this.getNativePRC20ForChain(sourceChain),
1945
+ gasToken: segment.gasToken || selectors_1.ZERO_ADDRESS,
1946
+ burnAmount: segment.totalBurnAmount || BigInt(1),
1947
+ gasFee: inboundGasFee,
1948
+ nativeValueForGas: perOutboundNativeValue !== null && perOutboundNativeValue !== void 0 ? perOutboundNativeValue : inboundGasFee * BigInt(1000),
1949
+ gatewayPcAddress,
1950
+ outboundRequest: outboundReq,
1951
+ });
1952
+ // Reset accumulated -- everything is now inside this outbound
1953
+ accumulatedPushMulticalls = [...outboundMulticalls];
1954
+ break;
1955
+ }
1956
+ }
1957
+ }
1958
+ return accumulatedPushMulticalls;
1959
+ }
1960
+ /**
1961
+ * Creates a cascaded transaction builder for nested multi-chain execution.
1962
+ * The cascade composes all hops bottom-to-top into a single Push Chain tx.
1963
+ *
1964
+ * @param preparedTxs - Array of prepared transactions
1965
+ * @returns CascadedTransactionBuilder
1966
+ */
1967
+ createCascadedBuilder(preparedTxs) {
1968
+ return {
1969
+ thenOn: (nextTx) => this.createCascadedBuilder([...preparedTxs, nextTx]),
1970
+ send: () => tslib_1.__awaiter(this, void 0, void 0, function* () {
1971
+ const ueaAddress = this.computeUEAOffchain();
1972
+ // Extract HopDescriptors
1973
+ const hops = preparedTxs.map((tx) => tx._hop);
1974
+ // Classify into segments
1975
+ const segments = this.classifyIntoSegments(hops);
1976
+ // Check if this is a single-hop Route 1 (no composition needed)
1977
+ if (preparedTxs.length === 1 &&
1978
+ preparedTxs[0].route === 'UOA_TO_PUSH') {
1979
+ const response = yield this.executeMultiChain(hops[0].params);
1980
+ const singleRoute1Result = {
1981
+ initialTxHash: response.hash,
1982
+ initialTxResponse: response,
1983
+ hops: [
1984
+ {
1985
+ hopIndex: 0,
1986
+ route: hops[0].route,
1987
+ executionChain: enums_1.CHAIN.PUSH_TESTNET_DONUT,
1988
+ status: 'confirmed',
1989
+ txHash: response.hash,
1990
+ },
1991
+ ],
1992
+ hopCount: 1,
1993
+ waitForAll: () => tslib_1.__awaiter(this, void 0, void 0, function* () {
1994
+ return ({
1995
+ success: true,
1996
+ hops: [
1997
+ {
1998
+ hopIndex: 0,
1999
+ route: hops[0].route,
2000
+ executionChain: enums_1.CHAIN.PUSH_TESTNET_DONUT,
2001
+ status: 'confirmed',
2002
+ txHash: response.hash,
2003
+ },
2004
+ ],
2005
+ });
2006
+ }),
2007
+ wait: (opts) => tslib_1.__awaiter(this, void 0, void 0, function* () { return singleRoute1Result.waitForAll(opts); }),
2008
+ };
2009
+ return singleRoute1Result;
2010
+ }
2011
+ // Check if single-hop Route 2 (just execute directly)
2012
+ if (preparedTxs.length === 1 &&
2013
+ preparedTxs[0].route === 'UOA_TO_CEA') {
2014
+ const response = yield this.executeMultiChain(hops[0].params);
2015
+ const targetChain = hops[0].targetChain || enums_1.CHAIN.PUSH_TESTNET_DONUT;
2016
+ const singleRoute2Result = {
2017
+ initialTxHash: response.hash,
2018
+ initialTxResponse: response,
2019
+ hops: [
2020
+ {
2021
+ hopIndex: 0,
2022
+ route: hops[0].route,
2023
+ executionChain: targetChain,
2024
+ status: 'confirmed',
2025
+ txHash: response.hash,
2026
+ },
2027
+ ],
2028
+ hopCount: 1,
2029
+ waitForAll: () => tslib_1.__awaiter(this, void 0, void 0, function* () {
2030
+ return ({
2031
+ success: true,
2032
+ hops: [
2033
+ {
2034
+ hopIndex: 0,
2035
+ route: hops[0].route,
2036
+ executionChain: targetChain,
2037
+ status: 'confirmed',
2038
+ txHash: response.hash,
2039
+ },
2040
+ ],
2041
+ });
2042
+ }),
2043
+ wait: (opts) => tslib_1.__awaiter(this, void 0, void 0, function* () { return singleRoute2Result.waitForAll(opts); }),
2044
+ };
2045
+ return singleRoute2Result;
2046
+ }
2047
+ // Multi-hop: compose cascade bottom-to-top
2048
+ // Fetch UEA balance + nonce so composeCascade can allocate native value and build inbound payloads
2049
+ const ueaBalance = yield this.pushClient.getBalance(ueaAddress);
2050
+ const ueaCodeCascade = yield this.pushClient.publicClient.getCode({ address: ueaAddress });
2051
+ const ueaNonceCascade = ueaCodeCascade !== undefined ? yield this.getUEANonce(ueaAddress) : BigInt(0);
2052
+ const composedMulticalls = this.composeCascade(segments, ueaAddress, ueaBalance, ueaNonceCascade);
2053
+ // Execute the composed multicall as a single Push Chain tx
2054
+ const executeParams = {
2055
+ to: ueaAddress,
2056
+ data: composedMulticalls,
2057
+ };
2058
+ const response = yield this.execute(executeParams);
2059
+ // Build hop info for tracking
2060
+ const hopInfos = hops.map((hop, index) => ({
2061
+ hopIndex: index,
2062
+ route: hop.route,
2063
+ executionChain: hop.targetChain || hop.sourceChain || enums_1.CHAIN.PUSH_TESTNET_DONUT,
2064
+ status: 'pending',
2065
+ }));
2066
+ // Mark first hop as submitted
2067
+ if (hopInfos.length > 0) {
2068
+ hopInfos[0].status = 'submitted';
2069
+ hopInfos[0].txHash = response.hash;
2070
+ }
2071
+ const cascadeResponse = {
2072
+ initialTxHash: response.hash,
2073
+ initialTxResponse: response,
2074
+ hops: hopInfos,
2075
+ hopCount: hops.length,
2076
+ waitForAll: (opts) => tslib_1.__awaiter(this, void 0, void 0, function* () {
2077
+ var _a;
2078
+ const { pollingIntervalMs = 10000, timeout = 300000, progressHook: cascadeProgressHook, } = opts || {};
2079
+ const startTime = Date.now();
2080
+ try {
2081
+ // 1. Wait for initial Push Chain tx confirmation
2082
+ cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
2083
+ hopIndex: 0,
2084
+ route: ((_a = hopInfos[0]) === null || _a === void 0 ? void 0 : _a.route) || 'UOA_TO_PUSH',
2085
+ chain: enums_1.CHAIN.PUSH_TESTNET_DONUT,
2086
+ status: 'waiting',
2087
+ elapsed: Date.now() - startTime,
2088
+ });
2089
+ yield response.wait();
2090
+ // Mark all Push Chain (Route 1) hops as confirmed
2091
+ for (const hop of hopInfos) {
2092
+ if (hop.route === 'UOA_TO_PUSH') {
2093
+ hop.status = 'confirmed';
2094
+ hop.txHash = response.hash;
2095
+ cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
2096
+ hopIndex: hop.hopIndex,
2097
+ route: hop.route,
2098
+ chain: enums_1.CHAIN.PUSH_TESTNET_DONUT,
2099
+ status: 'confirmed',
2100
+ txHash: response.hash,
2101
+ elapsed: Date.now() - startTime,
2102
+ });
2103
+ }
2104
+ }
2105
+ // 2. Track outbound hops (Route 2: UOA_TO_CEA)
2106
+ // Hops after a CEA_TO_PUSH are "child outbounds" — they execute inside
2107
+ // the inbound payload on Push Chain and live under a DIFFERENT utx_id
2108
+ // (the inbound UTX, not the parent). We can't track them via the parent
2109
+ // utx_id polling, so we auto-confirm them.
2110
+ const ceaToPushIndex = hopInfos.findIndex((h) => h.route === 'CEA_TO_PUSH');
2111
+ const outboundHops = hopInfos.filter((h, i) => {
2112
+ if (h.route !== 'UOA_TO_CEA')
2113
+ return false;
2114
+ // Child outbounds (after CEA_TO_PUSH) live under the inbound UTX,
2115
+ // not the parent — auto-confirm since we can't poll them here.
2116
+ if (ceaToPushIndex >= 0 && i > ceaToPushIndex) {
2117
+ h.status = 'confirmed';
2118
+ cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
2119
+ hopIndex: h.hopIndex,
2120
+ route: h.route,
2121
+ chain: h.executionChain,
2122
+ status: 'confirmed',
2123
+ elapsed: Date.now() - startTime,
2124
+ });
2125
+ return false;
2126
+ }
2127
+ return true;
2128
+ });
2129
+ if (outboundHops.length > 0) {
2130
+ if (outboundHops.length === 1) {
2131
+ // Single direct outbound hop: use the existing V1-based tracking
2132
+ const hop = outboundHops[0];
2133
+ const remainingTimeout = timeout - (Date.now() - startTime);
2134
+ if (remainingTimeout <= 0) {
2135
+ hop.status = 'failed';
2136
+ cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
2137
+ hopIndex: hop.hopIndex,
2138
+ route: hop.route,
2139
+ chain: hop.executionChain,
2140
+ status: 'timeout',
2141
+ elapsed: Date.now() - startTime,
2142
+ });
2143
+ return { success: false, hops: hopInfos, failedAt: hop.hopIndex };
2144
+ }
2145
+ cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
2146
+ hopIndex: hop.hopIndex,
2147
+ route: hop.route,
2148
+ chain: hop.executionChain,
2149
+ status: 'polling',
2150
+ elapsed: Date.now() - startTime,
2151
+ });
2152
+ try {
2153
+ const outboundDetails = yield this.waitForOutboundTx(response.hash, {
2154
+ initialWaitMs: Math.min(60000, remainingTimeout),
2155
+ pollingIntervalMs,
2156
+ timeout: remainingTimeout,
2157
+ progressHook: (event) => {
2158
+ cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
2159
+ hopIndex: hop.hopIndex,
2160
+ route: hop.route,
2161
+ chain: hop.executionChain,
2162
+ status: event.status,
2163
+ elapsed: Date.now() - startTime,
2164
+ });
2165
+ },
2166
+ });
2167
+ hop.status = 'confirmed';
2168
+ hop.txHash = outboundDetails.externalTxHash;
2169
+ hop.outboundDetails = outboundDetails;
2170
+ cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
2171
+ hopIndex: hop.hopIndex,
2172
+ route: hop.route,
2173
+ chain: hop.executionChain,
2174
+ status: 'confirmed',
2175
+ txHash: outboundDetails.externalTxHash,
2176
+ elapsed: Date.now() - startTime,
2177
+ });
2178
+ }
2179
+ catch (err) {
2180
+ hop.status = 'failed';
2181
+ cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
2182
+ hopIndex: hop.hopIndex,
2183
+ route: hop.route,
2184
+ chain: hop.executionChain,
2185
+ status: 'failed',
2186
+ elapsed: Date.now() - startTime,
2187
+ });
2188
+ return { success: false, hops: hopInfos, failedAt: hop.hopIndex };
2189
+ }
2190
+ }
2191
+ else {
2192
+ // Multiple outbound hops: use V2 API which returns outboundTx[]
2193
+ const allOutboundDetails = yield this.waitForAllOutboundTxsV2(response.hash, outboundHops, {
2194
+ initialWaitMs: Math.min(60000, timeout - (Date.now() - startTime)),
2195
+ pollingIntervalMs,
2196
+ timeout: timeout - (Date.now() - startTime),
2197
+ progressHook: (event) => {
2198
+ cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
2199
+ hopIndex: event.hopIndex,
2200
+ route: event.route,
2201
+ chain: event.chain,
2202
+ status: event.status,
2203
+ txHash: event.txHash,
2204
+ elapsed: Date.now() - startTime,
2205
+ });
2206
+ },
2207
+ });
2208
+ if (!allOutboundDetails.success) {
2209
+ return {
2210
+ success: false,
2211
+ hops: hopInfos,
2212
+ failedAt: allOutboundDetails.failedAt,
2213
+ };
2214
+ }
2215
+ }
2216
+ }
2217
+ // 3. Route 3 (CEA_TO_PUSH) tracking - mark as submitted
2218
+ const inboundHops = hopInfos.filter((h) => h.route === 'CEA_TO_PUSH');
2219
+ for (const inboundHop of inboundHops) {
2220
+ inboundHop.status = 'submitted';
2221
+ cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
2222
+ hopIndex: inboundHop.hopIndex,
2223
+ route: inboundHop.route,
2224
+ chain: inboundHop.executionChain,
2225
+ status: 'waiting',
2226
+ elapsed: Date.now() - startTime,
2227
+ });
2228
+ }
2229
+ return { success: true, hops: hopInfos };
2230
+ }
2231
+ catch (err) {
2232
+ const failedIdx = hopInfos.findIndex((h) => h.status !== 'confirmed');
2233
+ return {
2234
+ success: false,
2235
+ hops: hopInfos,
2236
+ failedAt: failedIdx >= 0 ? failedIdx : 0,
2237
+ };
2238
+ }
2239
+ }),
2240
+ wait: (opts) => tslib_1.__awaiter(this, void 0, void 0, function* () { return cascadeResponse.waitForAll(opts); }),
2241
+ };
2242
+ return cascadeResponse;
2243
+ }),
2244
+ };
2245
+ }
2246
+ /**
2247
+ * @deprecated Use createCascadedBuilder instead.
2248
+ * Creates a chained transaction builder for sequential multi-chain execution.
2249
+ *
2250
+ * @param transactions - Array of transactions to execute in sequence
2251
+ * @returns ChainedTransactionBuilder
2252
+ */
2253
+ createChainedBuilder(transactions) {
2254
+ return {
2255
+ thenOn: (nextTx) => this.createChainedBuilder([...transactions, nextTx]),
2256
+ send: () => tslib_1.__awaiter(this, void 0, void 0, function* () {
2257
+ const responses = [];
2258
+ for (let i = 0; i < transactions.length; i++) {
2259
+ const response = yield this.execute(transactions[i]);
2260
+ response.hopIndex = i;
2261
+ if (i > 0 && responses[i - 1]) {
2262
+ response.parentTxHash = responses[i - 1].hash;
2263
+ responses[i - 1].childTxHash = response.hash;
2264
+ }
2265
+ responses.push(response);
2266
+ }
2267
+ return {
2268
+ transactions: responses,
2269
+ chains: responses.map((r) => ({
2270
+ chain: r.chain || enums_1.CHAIN.PUSH_TESTNET_DONUT,
2271
+ hash: r.hash,
2272
+ blockNumber: r.blockNumber,
2273
+ status: 'confirmed',
2274
+ })),
2275
+ };
2276
+ }),
2277
+ };
2278
+ }
2279
+ /**
2280
+ * Route 2: Execute outbound transaction from Push Chain to external CEA
2281
+ *
2282
+ * This method builds a multicall that executes on Push Chain (from UEA context):
2283
+ * 1. Approves the gateway to spend PRC-20 tokens (if needed)
2284
+ * 2. Calls sendUniversalTxOutbound on UniversalGatewayPC precompile
2285
+ *
2286
+ * The multicall is executed through the normal execute() flow which handles
2287
+ * fee-locking on the origin chain and signature verification.
2288
+ *
2289
+ * @param params - Universal execution parameters with ChainTarget
2290
+ * @returns UniversalTxResponse
2291
+ */
2292
+ executeUoaToCea(params) {
2293
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
2294
+ var _a, _b, _c, _d, _e;
2295
+ const target = params.to;
2296
+ const targetChain = target.chain;
2297
+ const targetAddress = target.address;
2298
+ const isSvm = (0, payload_builders_1.isSvmChain)(targetChain);
2299
+ // Validate target address based on VM type
2300
+ if (isSvm) {
2301
+ // SVM: 32-byte hex address
2302
+ if (!(0, payload_builders_1.isValidSolanaHexAddress)(targetAddress)) {
2303
+ throw new Error(`Invalid Solana address: ${targetAddress}. ` +
2304
+ `Expected 0x + 64 hex chars (32 bytes).`);
2305
+ }
2306
+ const ZERO_32 = ('0x' + '0'.repeat(64));
2307
+ if (targetAddress.toLowerCase() === ZERO_32.toLowerCase()) {
2308
+ throw new Error(`Cannot send to zero address on Solana. ` +
2309
+ `This would result in permanent loss of funds.`);
2310
+ }
2311
+ }
2312
+ else {
2313
+ // EVM: 20-byte hex address
2314
+ // Zero address is allowed for multicall (data is array) — the actual targets are in the data entries.
2315
+ const isMulticall = Array.isArray(params.data);
2316
+ if (targetAddress.toLowerCase() === selectors_1.ZERO_ADDRESS.toLowerCase() && !isMulticall) {
2317
+ throw new Error(`Cannot send to zero address (0x0000...0000). ` +
2318
+ `This would result in permanent loss of funds.`);
2319
+ }
2320
+ }
2321
+ // Validate chain supports outbound operations
2322
+ if (!(0, cea_utils_1.chainSupportsOutbound)(targetChain)) {
2323
+ throw new Error(`Chain ${targetChain} does not support outbound operations. ` +
2324
+ `Supported chains: BNB_TESTNET, ETHEREUM_SEPOLIA, SOLANA_DEVNET, etc.`);
2325
+ }
2326
+ // Branch based on VM type
2327
+ if (isSvm) {
2328
+ return this.executeUoaToCeaSvm(params, target);
2329
+ }
2330
+ // ===== EVM path (existing logic) =====
2331
+ // Get UEA address
2332
+ const ueaAddress = this.computeUEAOffchain();
2333
+ this.printLog(`executeUoaToCea — target chain: ${targetChain}, target address: ${targetAddress}, UEA: ${ueaAddress}`);
2334
+ // Get CEA address for this UEA on target chain
2335
+ const { cea: ceaAddress, isDeployed: ceaDeployed } = yield (0, cea_utils_1.getCEAAddress)(ueaAddress, targetChain, (_a = this.rpcUrls[targetChain]) === null || _a === void 0 ? void 0 : _a[0]);
2336
+ this.printLog(`executeUoaToCea — CEA address: ${ceaAddress}, deployed: ${ceaDeployed}`);
2337
+ // Migration path: raw MIGRATION_SELECTOR payload, no multicall wrapping
2338
+ let ceaPayload;
2339
+ let prc20Token = selectors_1.ZERO_ADDRESS;
2340
+ let burnAmount = BigInt(0);
2341
+ if (params.migration) {
2342
+ ceaPayload = (0, payload_builders_1.buildMigrationPayload)();
2343
+ prc20Token = this.getNativePRC20ForChain(targetChain);
2344
+ burnAmount = BigInt(0); // Migration is logic-only — no funds. CEA rejects msg.value != 0.
2345
+ this.printLog(`executeUoaToCea — MIGRATION: using raw MIGRATION_SELECTOR payload (${ceaPayload}), native PRC-20 ${prc20Token}`);
2346
+ }
2347
+ else {
2348
+ // Build multicall for CEA execution on target chain
2349
+ const ceaMulticalls = [];
2350
+ // If there's data to execute on target
2351
+ if (params.data) {
2352
+ if (Array.isArray(params.data)) {
2353
+ // User provided explicit multicall array
2354
+ ceaMulticalls.push(...params.data);
2355
+ }
2356
+ else {
2357
+ // When ERC-20 funds are provided with a single payload, auto-prepend a
2358
+ // transfer() call so the tokens minted to the CEA are forwarded to the
2359
+ // target address. This mirrors the Route 1 behavior in buildExecuteMulticall.
2360
+ if ((_b = params.funds) === null || _b === void 0 ? void 0 : _b.amount) {
2361
+ const token = params.funds.token;
2362
+ if (token && token.mechanism !== 'native') {
2363
+ const erc20Transfer = (0, viem_1.encodeFunctionData)({
2364
+ abi: abi_1.ERC20_EVM,
2365
+ functionName: 'transfer',
2366
+ args: [targetAddress, params.funds.amount],
2367
+ });
2368
+ ceaMulticalls.push({
2369
+ to: token.address,
2370
+ value: BigInt(0),
2371
+ data: erc20Transfer,
2372
+ });
2373
+ }
2374
+ }
2375
+ // Single call with data. Forward native value (if any) so the target
2376
+ // contract receives it alongside the payload call. The vault deposits
2377
+ // native value to the CEA, and the multicall forwards it to the target.
2378
+ ceaMulticalls.push({
2379
+ to: targetAddress,
2380
+ value: (_c = params.value) !== null && _c !== void 0 ? _c : BigInt(0),
2381
+ data: params.data,
2382
+ });
2383
+ }
2384
+ }
2385
+ else if (params.value) {
2386
+ // Native value transfer only.
2387
+ // If sending to the CEA itself, skip the multicall — the gateway deposits native
2388
+ // value directly to CEA. A self-call with value would revert (CEA._handleMulticall
2389
+ // rejects value-bearing self-calls).
2390
+ if (targetAddress.toLowerCase() !== ceaAddress.toLowerCase()) {
2391
+ ceaMulticalls.push({
2392
+ to: targetAddress,
2393
+ value: params.value,
2394
+ data: '0x',
2395
+ });
2396
+ }
2397
+ }
2398
+ // Build CEA multicall payload (this is what gets executed on the external chain)
2399
+ ceaPayload = (0, payload_builders_1.buildCeaMulticallPayload)(ceaMulticalls);
2400
+ // Determine token to burn on Push Chain
2401
+ // NOTE: Even for PAYLOAD-only (no value), we need a valid PRC-20 token to:
2402
+ // 1. Look up the target chain namespace in the gateway
2403
+ // 2. Query and pay gas fees for the relay
2404
+ if ((_d = params.funds) === null || _d === void 0 ? void 0 : _d.amount) {
2405
+ // User explicitly specified funds with token
2406
+ const token = params.funds.token;
2407
+ if (token) {
2408
+ prc20Token = push_chain_1.PushChain.utils.tokens.getPRC20Address(token);
2409
+ burnAmount = params.funds.amount;
2410
+ }
2411
+ }
2412
+ else if (params.value && params.value > BigInt(0)) {
2413
+ // Native value transfer: auto-select the PRC-20 token for target chain
2414
+ prc20Token = this.getNativePRC20ForChain(targetChain);
2415
+ burnAmount = params.value;
2416
+ this.printLog(`executeUoaToCea — auto-selected native PRC-20 ${prc20Token} for chain ${targetChain}, amount: ${burnAmount.toString()}`);
2417
+ }
2418
+ else if (params.data) {
2419
+ // PAYLOAD-only (no value transfer): still need native token for chain namespace + gas fees
2420
+ prc20Token = this.getNativePRC20ForChain(targetChain);
2421
+ burnAmount = BigInt(0);
2422
+ this.printLog(`executeUoaToCea — PAYLOAD-only: using native PRC-20 ${prc20Token} for chain ${targetChain} with zero burn amount`);
2423
+ }
2424
+ }
2425
+ // Build outbound request struct for the gateway
2426
+ // NOTE: `target` is a LEGACY/DUMMY parameter for contract compatibility.
2427
+ // The deployed UniversalGatewayPC still expects this field, but the relay does NOT use it
2428
+ // to determine the actual destination. The relay determines destination from the PRC-20 token's
2429
+ // SOURCE_CHAIN_NAMESPACE. We pass the CEA address as a non-zero placeholder.
2430
+ // This field will be removed in future contract upgrades.
2431
+ const targetBytes = ceaAddress; // Dummy value - any non-zero address works
2432
+ const outboundReq = (0, payload_builders_1.buildOutboundRequest)(targetBytes, prc20Token, burnAmount, (_e = params.gasLimit) !== null && _e !== void 0 ? _e : BigInt(0), ceaPayload, ueaAddress // revert recipient is the UEA
2433
+ );
2434
+ this.printLog(`executeUoaToCea — outbound request: ${JSON.stringify({
2435
+ target: outboundReq.target,
2436
+ token: outboundReq.token,
2437
+ amount: outboundReq.amount.toString(),
2438
+ gasLimit: outboundReq.gasLimit.toString(),
2439
+ payloadLength: outboundReq.payload.length,
2440
+ revertRecipient: outboundReq.revertRecipient,
2441
+ }, null, 2)}`);
2442
+ // Get UniversalGatewayPC address
2443
+ const gatewayPcAddress = this.getUniversalGatewayPCAddress();
2444
+ // Pre-fetch UEA status early — balance is needed for gas value calculation
2445
+ const [ueaCode, ueaBalance] = yield Promise.all([
2446
+ this.pushClient.publicClient.getCode({ address: ueaAddress }),
2447
+ this.pushClient.getBalance(ueaAddress),
2448
+ ]);
2449
+ const isUEADeployed = ueaCode !== undefined;
2450
+ const ueaNonce = isUEADeployed ? yield this.getUEANonce(ueaAddress) : BigInt(0);
2451
+ // Build the multicall that will execute ON Push Chain from UEA context
2452
+ // This includes: 1) approve PRC-20 (if needed), 2) call sendUniversalTxOutbound
2453
+ const pushChainMulticalls = [];
2454
+ // Query gas fee from UniversalCore contract (needed for approval amount)
2455
+ let gasFee = BigInt(0);
2456
+ let nativeValueForGas = BigInt(0);
2457
+ let gasToken = selectors_1.ZERO_ADDRESS;
2458
+ if (prc20Token !== selectors_1.ZERO_ADDRESS) {
2459
+ try {
2460
+ const result = yield this.queryOutboundGasFee(prc20Token, outboundReq.gasLimit);
2461
+ gasFee = result.gasFee;
2462
+ gasToken = result.gasToken;
2463
+ nativeValueForGas = result.nativeValueForGas;
2464
+ this.printLog(`executeUoaToCea — queried gas fee: ${gasFee.toString()}, gasToken: ${gasToken}, nativeValueForGas: ${nativeValueForGas.toString()}`);
2465
+ }
2466
+ catch (err) {
2467
+ throw new Error(`Failed to query outbound gas fee: ${err}`);
2468
+ }
2469
+ }
2470
+ // Adjust nativeValueForGas: the 1Mx multiplier from queryOutboundGasFee produces
2471
+ // a value far too low for the actual WPC/gasToken swap price. Set it to 200 UPC
2472
+ // (capped by balance) — enough for the swap, but avoids draining thin pools.
2473
+ // The contract's swapAndBurnGas does exactOutputSingle and refunds excess PC.
2474
+ const EVM_NATIVE_VALUE_TARGET = BigInt(200e18); // 200 UPC
2475
+ const EVM_GAS_RESERVE = BigInt(3e18); // 3 UPC for tx overhead
2476
+ const currentBalance = yield this.pushClient.getBalance(ueaAddress);
2477
+ let adjustedValue;
2478
+ if (currentBalance > EVM_NATIVE_VALUE_TARGET + EVM_GAS_RESERVE) {
2479
+ // Enough balance: use 200 UPC target
2480
+ adjustedValue = EVM_NATIVE_VALUE_TARGET;
2481
+ }
2482
+ else if (currentBalance > EVM_GAS_RESERVE) {
2483
+ // Low balance: use what's available minus reserve
2484
+ adjustedValue = currentBalance - EVM_GAS_RESERVE;
2485
+ }
2486
+ else {
2487
+ // Very low balance: use original query value as-is
2488
+ adjustedValue = nativeValueForGas;
2489
+ }
2490
+ if (adjustedValue !== nativeValueForGas) {
2491
+ this.printLog(`executeUoaToCea — adjusting nativeValueForGas from ${nativeValueForGas.toString()} to ${adjustedValue.toString()} (UEA balance: ${currentBalance.toString()})`);
2492
+ nativeValueForGas = adjustedValue;
2493
+ }
2494
+ // Build outbound multicalls (approve burn + sendUniversalTxOutbound with native value)
2495
+ const outboundMulticalls = (0, payload_builders_1.buildOutboundApprovalAndCall)({
2496
+ prc20Token,
2497
+ gasToken,
2498
+ burnAmount,
2499
+ gasFee,
2500
+ nativeValueForGas,
2501
+ gatewayPcAddress,
2502
+ outboundRequest: outboundReq,
2503
+ });
2504
+ pushChainMulticalls.push(...outboundMulticalls);
2505
+ this.printLog(`executeUoaToCea — Push Chain multicall has ${pushChainMulticalls.length} operations`);
2506
+ // Execute through the normal execute() flow
2507
+ // This handles fee-locking on origin chain and executes the multicall from UEA context
2508
+ // Sum native values from multicall entries for proper fee calculation
2509
+ const multicallNativeValue = pushChainMulticalls.reduce((sum, mc) => { var _a; return sum + ((_a = mc.value) !== null && _a !== void 0 ? _a : BigInt(0)); }, BigInt(0));
2510
+ const executeParams = {
2511
+ to: ueaAddress, // multicall executes from UEA
2512
+ value: multicallNativeValue, // ensures correct requiredFunds calculation
2513
+ data: pushChainMulticalls, // array triggers multicall mode
2514
+ _ueaStatus: {
2515
+ isDeployed: isUEADeployed,
2516
+ nonce: ueaNonce,
2517
+ balance: ueaBalance,
2518
+ },
2519
+ _skipFeeLocking: true, // outbound executes on Push Chain, no external fee locking
2520
+ };
2521
+ const response = yield this.execute(executeParams);
2522
+ // Add chain info to response
2523
+ response.chain = targetChain;
2524
+ response.chainNamespace = this.getChainNamespace(targetChain);
2525
+ return response;
2526
+ });
2527
+ }
2528
+ /**
2529
+ * Route 2 for SVM targets: Outbound from Push Chain to Solana.
2530
+ *
2531
+ * Three cases:
2532
+ * 1. Withdraw SOL: Burn pSOL on Push Chain, recipient gets native SOL
2533
+ * 2. Withdraw SPL: Burn PRC-20 on Push Chain, recipient gets SPL token
2534
+ * 3. Execute (CPI): Burn pSOL + execute CPI on target Solana program
2535
+ *
2536
+ * @param params - Universal execution parameters
2537
+ * @param target - ChainTarget with Solana chain and hex address
2538
+ * @returns UniversalTxResponse
2539
+ */
2540
+ executeUoaToCeaSvm(params, target) {
2541
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
2542
+ var _a, _b, _c, _d;
2543
+ const targetChain = target.chain;
2544
+ const targetAddress = target.address; // 0x-prefixed, 32 bytes
2545
+ const ueaAddress = this.computeUEAOffchain();
2546
+ const hasSvmExecute = !!params.svmExecute;
2547
+ this.printLog(`executeUoaToCeaSvm — target: ${targetAddress}, chain: ${targetChain}, ` +
2548
+ `hasSvmExecute: ${hasSvmExecute}, value: ${(_b = (_a = params.value) === null || _a === void 0 ? void 0 : _a.toString()) !== null && _b !== void 0 ? _b : '0'}`);
2549
+ // --- Build SVM payload ---
2550
+ let svmPayload = '0x'; // empty for withdraw (SOL/SPL)
2551
+ if (hasSvmExecute) {
2552
+ // Execute case: encode the CPI payload
2553
+ const exec = params.svmExecute;
2554
+ svmPayload = (0, payload_builders_1.encodeSvmExecutePayload)({
2555
+ targetProgram: exec.targetProgram,
2556
+ accounts: exec.accounts,
2557
+ ixData: exec.ixData,
2558
+ instructionId: 2,
2559
+ });
2560
+ this.printLog(`executeUoaToCeaSvm — encoded execute payload: ${(svmPayload.length - 2) / 2} bytes, ` +
2561
+ `${exec.accounts.length} accounts`);
2562
+ }
2563
+ // --- Determine PRC-20 token and burn amount ---
2564
+ let prc20Token = selectors_1.ZERO_ADDRESS;
2565
+ let burnAmount = BigInt(0);
2566
+ if ((_c = params.funds) === null || _c === void 0 ? void 0 : _c.amount) {
2567
+ // User explicitly specified funds with token
2568
+ const token = params.funds.token;
2569
+ if (token) {
2570
+ prc20Token = push_chain_1.PushChain.utils.tokens.getPRC20Address(token);
2571
+ burnAmount = params.funds.amount;
2572
+ }
2573
+ }
2574
+ else if (params.value && params.value > BigInt(0)) {
2575
+ // Native value transfer: auto-select pSOL for Solana chains
2576
+ prc20Token = this.getNativePRC20ForChain(targetChain);
2577
+ burnAmount = params.value;
2578
+ this.printLog(`executeUoaToCeaSvm — auto-selected native PRC-20 ${prc20Token} for chain ${targetChain}, amount: ${burnAmount.toString()}`);
2579
+ }
2580
+ else if (hasSvmExecute) {
2581
+ // Execute-only (no value): check if user specified an SPL token context
2582
+ const token = params.funds && params.funds.token;
2583
+ if (token) {
2584
+ prc20Token = push_chain_1.PushChain.utils.tokens.getPRC20Address(token);
2585
+ }
2586
+ else {
2587
+ prc20Token = this.getNativePRC20ForChain(targetChain);
2588
+ }
2589
+ burnAmount = BigInt(0);
2590
+ this.printLog(`executeUoaToCeaSvm — EXECUTE-only: using PRC-20 ${prc20Token} with zero burn amount`);
2591
+ }
2592
+ // --- Determine target bytes ---
2593
+ // For withdraw: target is the Solana recipient pubkey
2594
+ // For execute: target is the target Solana program
2595
+ const targetBytes = hasSvmExecute
2596
+ ? params.svmExecute.targetProgram
2597
+ : targetAddress;
2598
+ // --- Build outbound request ---
2599
+ const outboundReq = (0, payload_builders_1.buildOutboundRequest)(targetBytes, prc20Token, burnAmount, (_d = params.gasLimit) !== null && _d !== void 0 ? _d : BigInt(0), svmPayload, ueaAddress // revert recipient is the UEA
2600
+ );
2601
+ this.printLog(`executeUoaToCeaSvm — outbound request: ${JSON.stringify({
2602
+ target: outboundReq.target,
2603
+ token: outboundReq.token,
2604
+ amount: outboundReq.amount.toString(),
2605
+ gasLimit: outboundReq.gasLimit.toString(),
2606
+ payloadLength: (outboundReq.payload.length - 2) / 2,
2607
+ revertRecipient: outboundReq.revertRecipient,
2608
+ }, null, 2)}`);
2609
+ // --- Pre-fetch UEA status early — balance is needed for gas value calculation ---
2610
+ const gatewayPcAddress = this.getUniversalGatewayPCAddress();
2611
+ const [ueaCode, ueaBalance] = yield Promise.all([
2612
+ this.pushClient.publicClient.getCode({ address: ueaAddress }),
2613
+ this.pushClient.getBalance(ueaAddress),
2614
+ ]);
2615
+ const isUEADeployed = ueaCode !== undefined;
2616
+ const ueaNonce = isUEADeployed
2617
+ ? yield this.getUEANonce(ueaAddress)
2618
+ : BigInt(0);
2619
+ // --- Query gas fee (identical to EVM path) ---
2620
+ let gasFee = BigInt(0);
2621
+ let gasToken = selectors_1.ZERO_ADDRESS;
2622
+ let nativeValueForGas = BigInt(0);
2623
+ if (prc20Token !== selectors_1.ZERO_ADDRESS) {
2624
+ const gasLimit = outboundReq.gasLimit;
2625
+ try {
2626
+ const result = yield this.queryOutboundGasFee(prc20Token, gasLimit);
2627
+ gasFee = result.gasFee;
2628
+ gasToken = result.gasToken;
2629
+ nativeValueForGas = result.nativeValueForGas;
2630
+ // When user omits gasLimit (sent as 0), the contract computes fees using its internal
2631
+ // baseGasLimitByChainNamespace. But the relay reads the stored gasLimit=0 from the
2632
+ // on-chain outbound record and uses it as the Solana compute budget — 0 CU means the
2633
+ // relay cannot execute the tx. Derive the effective limit from gasFee/gasPrice so
2634
+ // the stored record has a non-zero compute budget the relay can use.
2635
+ if (!params.gasLimit && result.gasPrice > BigInt(0)) {
2636
+ outboundReq.gasLimit = result.gasFee / result.gasPrice;
2637
+ this.printLog(`executeUoaToCeaSvm — derived effectiveGasLimit: ${outboundReq.gasLimit} (gasFee=${result.gasFee} / gasPrice=${result.gasPrice})`);
2638
+ }
2639
+ this.printLog(`executeUoaToCeaSvm — queried gas fee: ${gasFee.toString()}, gasToken: ${gasToken}, nativeValueForGas: ${nativeValueForGas.toString()}`);
2640
+ }
2641
+ catch (err) {
2642
+ throw new Error(`Failed to query outbound gas fee: ${err}`);
2643
+ }
2644
+ }
2645
+ // Adjust nativeValueForGas: the 1Mx multiplier from queryOutboundGasFee produces
2646
+ // a value far too low for the actual WPC/gasToken swap price. Set it to 200 UPC
2647
+ // (capped by balance) — enough for the swap, but avoids draining thin pools.
2648
+ // The contract's swapAndBurnGas does exactOutputSingle and refunds excess PC.
2649
+ const SVM_NATIVE_VALUE_TARGET = BigInt(200e18); // 200 UPC
2650
+ const SVM_GAS_RESERVE = BigInt(3e18); // 3 UPC for tx overhead
2651
+ const currentBalance = yield this.pushClient.getBalance(ueaAddress);
2652
+ let adjustedValue;
2653
+ if (currentBalance > SVM_NATIVE_VALUE_TARGET + SVM_GAS_RESERVE) {
2654
+ // Enough balance: use 200 UPC target
2655
+ adjustedValue = SVM_NATIVE_VALUE_TARGET;
2656
+ }
2657
+ else if (currentBalance > SVM_GAS_RESERVE) {
2658
+ // Low balance: use what's available minus reserve
2659
+ adjustedValue = currentBalance - SVM_GAS_RESERVE;
2660
+ }
2661
+ else {
2662
+ // Very low balance: use original query value as-is
2663
+ adjustedValue = nativeValueForGas;
2664
+ }
2665
+ if (adjustedValue !== nativeValueForGas) {
2666
+ this.printLog(`executeUoaToCeaSvm — adjusting nativeValueForGas from ${nativeValueForGas.toString()} to ${adjustedValue.toString()} (UEA balance: ${currentBalance.toString()})`);
2667
+ nativeValueForGas = adjustedValue;
2668
+ }
2669
+ // --- Build Push Chain multicalls (approve + sendUniversalTxOutbound) ---
2670
+ // Reuse the same builder as EVM — this part is identical
2671
+ const pushChainMulticalls = (0, payload_builders_1.buildOutboundApprovalAndCall)({
2672
+ prc20Token,
2673
+ gasToken,
2674
+ burnAmount,
2675
+ gasFee,
2676
+ nativeValueForGas,
2677
+ gatewayPcAddress,
2678
+ outboundRequest: outboundReq,
2679
+ });
2680
+ this.printLog(`executeUoaToCeaSvm — Push Chain multicall has ${pushChainMulticalls.length} operations`);
2681
+ // Sum native values from multicall entries for proper fee calculation
2682
+ const multicallNativeValue = pushChainMulticalls.reduce((sum, mc) => { var _a; return sum + ((_a = mc.value) !== null && _a !== void 0 ? _a : BigInt(0)); }, BigInt(0));
2683
+ const executeParams = {
2684
+ to: ueaAddress,
2685
+ value: multicallNativeValue, // ensures correct requiredFunds calculation
2686
+ data: pushChainMulticalls,
2687
+ _ueaStatus: {
2688
+ isDeployed: isUEADeployed,
2689
+ nonce: ueaNonce,
2690
+ balance: ueaBalance,
2691
+ },
2692
+ _skipFeeLocking: true, // outbound executes on Push Chain, no external fee locking
2693
+ };
2694
+ const response = yield this.execute(executeParams);
2695
+ // Add chain info to response
2696
+ response.chain = targetChain;
2697
+ response.chainNamespace = this.getChainNamespace(targetChain);
2698
+ return response;
2699
+ });
2700
+ }
2701
+ /**
2702
+ * Route 3: Execute inbound transaction from CEA to Push Chain
2703
+ *
2704
+ * This route instructs CEA on an external chain to call sendUniversalTxFromCEA,
2705
+ * bridging funds/payloads back to Push Chain.
2706
+ *
2707
+ * Flow:
2708
+ * 1. Build multicall for CEA: [approve Gateway (if ERC20), sendUniversalTxFromCEA]
2709
+ * 2. Execute via Route 2 (UOA → CEA) with PAYLOAD-only (CEA uses its own funds)
2710
+ * 3. CEA executes multicall, Gateway locks funds, relayer mints PRC-20 on Push Chain
2711
+ *
2712
+ * @param params - Universal execution parameters with from.chain specified
2713
+ * @returns UniversalTxResponse
2714
+ */
2715
+ executeCeaToPush(params) {
2716
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
2717
+ var _a, _b, _c, _d, _e;
2718
+ // 1. Validate and extract source chain
2719
+ if (!((_a = params.from) === null || _a === void 0 ? void 0 : _a.chain)) {
2720
+ throw new Error('Route 3 (CEA → Push) requires from.chain to specify the source CEA chain');
2721
+ }
2722
+ const sourceChain = params.from.chain;
2723
+ // SVM chains use a fundamentally different flow (gateway self-call, not CEA multicall)
2724
+ if ((0, payload_builders_1.isSvmChain)(sourceChain)) {
2725
+ return this.executeCeaToPushSvm(params, sourceChain);
2726
+ }
2727
+ // 2. Extract destination on Push Chain
2728
+ // For Route 3, 'to' is a Push Chain address (string), not a ChainTarget
2729
+ const pushDestination = params.to;
2730
+ if (typeof params.to !== 'string') {
2731
+ throw new Error('Route 3 (CEA → Push): to must be a Push Chain address (string), not a ChainTarget');
2732
+ }
2733
+ // 3. Get UEA address (will be recipient on Push Chain from CEA's perspective)
2734
+ const ueaAddress = this.computeUEAOffchain();
2735
+ // 4. Get CEA address on source chain
2736
+ const { cea: ceaAddress, isDeployed: ceaDeployed } = yield (0, cea_utils_1.getCEAAddress)(ueaAddress, sourceChain, (_b = this.rpcUrls[sourceChain]) === null || _b === void 0 ? void 0 : _b[0]);
2737
+ this.printLog(`executeCeaToPush — sourceChain: ${sourceChain}, CEA: ${ceaAddress}, deployed: ${ceaDeployed}`);
2738
+ if (!ceaDeployed) {
2739
+ throw new Error(`CEA not deployed on ${sourceChain}. ` +
2740
+ `Deploy CEA first using Route 2 (UOA → CEA) before using Route 3.`);
2741
+ }
2742
+ // 5. Get UniversalGateway address on source chain
2743
+ const gatewayAddress = chain_1.UNIVERSAL_GATEWAY_ADDRESSES[sourceChain];
2744
+ if (!gatewayAddress) {
2745
+ throw new Error(`No UniversalGateway address configured for chain ${sourceChain}`);
2746
+ }
2747
+ // 6. Build multicall for CEA to execute on source chain (self-calls via sendUniversalTxToUEA)
2748
+ const ceaMulticalls = [];
2749
+ // Determine token and amount for sendUniversalTxToUEA
2750
+ let tokenAddress = selectors_1.ZERO_ADDRESS;
2751
+ let amount = BigInt(0);
2752
+ if ((_c = params.funds) === null || _c === void 0 ? void 0 : _c.amount) {
2753
+ // ERC20 token transfer from CEA
2754
+ const token = params.funds.token;
2755
+ if (token) {
2756
+ if (token.mechanism === 'native') {
2757
+ // Native token (e.g., BNB on BSC)
2758
+ tokenAddress = selectors_1.ZERO_ADDRESS;
2759
+ amount = params.funds.amount;
2760
+ }
2761
+ else {
2762
+ // ERC20 token - need approval for gateway before sendUniversalTxToUEA
2763
+ tokenAddress = token.address;
2764
+ amount = params.funds.amount;
2765
+ }
2766
+ }
2767
+ }
2768
+ else if (params.value && params.value > BigInt(0)) {
2769
+ // Native value transfer (e.g., BNB, ETH)
2770
+ tokenAddress = selectors_1.ZERO_ADDRESS;
2771
+ amount = params.value;
2772
+ }
2773
+ // bridgeAmount = only the burn amount (what the Vault will actually deposit to CEA).
2774
+ // Previously this included ceaExistingBalance (CEA's pre-existing balance on the
2775
+ // external chain), but that approach is racy: the balance can change between the SDK
2776
+ // query and relay execution, causing sendUniversalTxToUEA to revert with
2777
+ // InsufficientBalance. Pre-existing CEA funds remain parked and can be swept separately.
2778
+ let bridgeAmount = amount;
2779
+ // Note: CEA contract may reject amount=0 in sendUniversalTxToUEA.
2780
+ // Keeping bridgeAmount as-is (0) for payload-only to test precompile behavior.
2781
+ // For ERC20 tokens, add approve call for the bridge amount
2782
+ // (CEA approves gateway to spend the Vault-deposited amount)
2783
+ if (tokenAddress !== selectors_1.ZERO_ADDRESS && bridgeAmount > BigInt(0)) {
2784
+ const approveData = (0, viem_1.encodeFunctionData)({
2785
+ abi: abi_1.ERC20_EVM,
2786
+ functionName: 'approve',
2787
+ args: [gatewayAddress, bridgeAmount],
2788
+ });
2789
+ ceaMulticalls.push({
2790
+ to: tokenAddress,
2791
+ value: BigInt(0),
2792
+ data: approveData,
2793
+ });
2794
+ }
2795
+ // Pre-fetch UEA nonce — needed for the inbound UniversalPayload struct
2796
+ const ueaCode = yield this.pushClient.publicClient.getCode({ address: ueaAddress });
2797
+ const isUEADeployed = ueaCode !== undefined;
2798
+ const ueaNonce = isUEADeployed ? yield this.getUEANonce(ueaAddress) : BigInt(0);
2799
+ // Build payload for Push Chain execution (if any)
2800
+ // This is what happens AFTER funds arrive on Push Chain.
2801
+ // The relay expects a full UniversalPayload struct (to, value, data, gasLimit, ...),
2802
+ // where `data` contains the multicall payload (with UEA_MULTICALL_SELECTOR prefix).
2803
+ let pushPayload = '0x';
2804
+ if (params.data) {
2805
+ const multicallData = (0, payload_builders_1.buildExecuteMulticall)({
2806
+ execute: {
2807
+ to: pushDestination,
2808
+ value: params.value,
2809
+ data: params.data,
2810
+ },
2811
+ ueaAddress,
2812
+ });
2813
+ const multicallPayload = this._buildMulticallPayloadData(pushDestination, multicallData);
2814
+ // Use ueaNonce + 1: the outbound tx itself consumes one nonce via execute(),
2815
+ // so the inbound will arrive when the UEA nonce is already incremented.
2816
+ pushPayload = (0, payload_builders_1.buildInboundUniversalPayload)(multicallPayload, { nonce: ueaNonce + BigInt(1) });
2817
+ }
2818
+ // Build sendUniversalTxToUEA self-call on CEA
2819
+ // CEA multicall: to=CEA (self-call), value=0
2820
+ // CEA internally calls gateway.sendUniversalTxFromCEA(...)
2821
+ // Uses bridgeAmount (= burn amount deposited by Vault)
2822
+ const sendUniversalTxCall = (0, payload_builders_1.buildSendUniversalTxToUEA)(ceaAddress, // to: CEA address (self-call)
2823
+ tokenAddress, // token: address(0) for native, ERC20 address otherwise
2824
+ bridgeAmount, // amount: burn amount only (Vault-deposited)
2825
+ pushPayload, // payload: Push Chain execution payload
2826
+ ueaAddress // revertRecipient: UEA on Push Chain (receives refund if inbound fails)
2827
+ );
2828
+ ceaMulticalls.push(sendUniversalTxCall);
2829
+ // 7. Encode CEA multicalls into outbound payload
2830
+ // CEA will self-execute this multicall (to=CEA, value=0)
2831
+ const ceaPayload = (0, payload_builders_1.buildCeaMulticallPayload)(ceaMulticalls);
2832
+ this.printLog(`executeCeaToPush — CEA payload (first 100 chars): ${ceaPayload.slice(0, 100)}...`);
2833
+ // 8. Determine PRC-20 token for the outbound burn on Push Chain.
2834
+ // For ERC20 flows (params.funds with token), use the token's PRC-20 (e.g. pUSDT)
2835
+ // so the Vault deposits ERC20 to CEA. For native flows, use the chain's native PRC-20 (e.g. pBNB).
2836
+ let prc20Token;
2837
+ if (((_d = params.funds) === null || _d === void 0 ? void 0 : _d.amount) && params.funds.token) {
2838
+ const token = params.funds.token;
2839
+ prc20Token = push_chain_1.PushChain.utils.tokens.getPRC20Address(token);
2840
+ }
2841
+ else {
2842
+ prc20Token = this.getNativePRC20ForChain(sourceChain);
2843
+ }
2844
+ // burnAmount = PRC20 to burn on Push Chain (NOT the bridge amount).
2845
+ // Vault deposits burnAmount to CEA. CEA uses burnAmount + pre-existing balance for the bridge.
2846
+ const burnAmount = amount;
2847
+ this.printLog(`executeCeaToPush — prc20Token: ${prc20Token}, burnAmount: ${burnAmount.toString()}`);
2848
+ // 9. Build outbound request (same structure as Route 2)
2849
+ // target = CEA address (for self-execution), value = 0 in payload
2850
+ const outboundReq = (0, payload_builders_1.buildOutboundRequest)(ceaAddress, // target: CEA address (to=CEA for self-execution)
2851
+ prc20Token, // token: native PRC-20 for source chain (for namespace lookup)
2852
+ burnAmount, // amount: 1 wei (precompile workaround)
2853
+ (_e = params.gasLimit) !== null && _e !== void 0 ? _e : BigInt(0), ceaPayload, // payload: ABI-encoded CEA multicall
2854
+ ueaAddress // revertRecipient: UEA
2855
+ );
2856
+ this.printLog(`executeCeaToPush — outbound request: ${JSON.stringify({
2857
+ target: outboundReq.target,
2858
+ token: outboundReq.token,
2859
+ amount: outboundReq.amount.toString(),
2860
+ gasLimit: outboundReq.gasLimit.toString(),
2861
+ payloadLength: outboundReq.payload.length,
2862
+ revertRecipient: outboundReq.revertRecipient,
2863
+ }, null, 2)}`);
2864
+ // 10. Fetch UEA balance — needed for gas value calculation
2865
+ // (UEA code + nonce already fetched above for the inbound UniversalPayload)
2866
+ const gatewayPcAddress = this.getUniversalGatewayPCAddress();
2867
+ const ueaBalance = yield this.pushClient.getBalance(ueaAddress);
2868
+ // 11. Query gas fees from UniversalCore
2869
+ let gasFee = BigInt(0);
2870
+ let nativeValueForGas = BigInt(0);
2871
+ let gasToken = selectors_1.ZERO_ADDRESS;
2872
+ if (prc20Token !== selectors_1.ZERO_ADDRESS) {
2873
+ const gasLimit = outboundReq.gasLimit;
2874
+ try {
2875
+ const result = yield this.queryOutboundGasFee(prc20Token, gasLimit);
2876
+ gasToken = result.gasToken;
2877
+ gasFee = result.gasFee;
2878
+ nativeValueForGas = result.nativeValueForGas;
2879
+ this.printLog(`executeCeaToPush — queried gas fee: ${gasFee.toString()}, gasToken: ${gasToken}, nativeValueForGas: ${nativeValueForGas.toString()}`);
2880
+ }
2881
+ catch (err) {
2882
+ throw new Error(`Failed to query outbound gas fee for Route 3: ${err}`);
2883
+ }
2884
+ }
2885
+ // Adjust nativeValueForGas using UEA balance (contract refunds excess)
2886
+ // Re-fetch balance to minimize staleness from gas fee query RPC roundtrips
2887
+ const currentBalance = yield this.pushClient.getBalance(ueaAddress);
2888
+ // Cosmos-EVM tx overhead costs ~1 PC per operation; 3 PC covers approve(s) + buffer.
2889
+ const OUTBOUND_GAS_RESERVE_R3 = BigInt(3e18);
2890
+ if (currentBalance > OUTBOUND_GAS_RESERVE_R3 && currentBalance - OUTBOUND_GAS_RESERVE_R3 > nativeValueForGas) {
2891
+ const adjustedValue = currentBalance - OUTBOUND_GAS_RESERVE_R3;
2892
+ this.printLog(`executeCeaToPush — adjusting nativeValueForGas from ${nativeValueForGas.toString()} to ${adjustedValue.toString()} (UEA balance: ${currentBalance.toString()})`);
2893
+ nativeValueForGas = adjustedValue;
2894
+ }
2895
+ // 12. Build Push Chain multicalls (approvals + sendUniversalTxOutbound)
2896
+ const pushChainMulticalls = (0, payload_builders_1.buildOutboundApprovalAndCall)({
2897
+ prc20Token,
2898
+ gasToken,
2899
+ burnAmount,
2900
+ gasFee,
2901
+ nativeValueForGas,
2902
+ gatewayPcAddress,
2903
+ outboundRequest: outboundReq,
2904
+ });
2905
+ this.printLog(`executeCeaToPush — Push Chain multicall has ${pushChainMulticalls.length} operations`);
2906
+ // Sum native values from multicall entries for proper fee calculation
2907
+ const multicallNativeValue = pushChainMulticalls.reduce((sum, mc) => { var _a; return sum + ((_a = mc.value) !== null && _a !== void 0 ? _a : BigInt(0)); }, BigInt(0));
2908
+ // 13. Execute through the normal execute() flow
2909
+ const executeParams = {
2910
+ to: ueaAddress,
2911
+ value: multicallNativeValue, // ensures correct requiredFunds calculation
2912
+ data: pushChainMulticalls,
2913
+ _ueaStatus: {
2914
+ isDeployed: isUEADeployed,
2915
+ nonce: ueaNonce,
2916
+ balance: ueaBalance,
2917
+ },
2918
+ _skipFeeLocking: true, // outbound executes on Push Chain, no external fee locking
2919
+ };
2920
+ const response = yield this.execute(executeParams);
2921
+ // Add Route 3 context to response
2922
+ response.chain = sourceChain;
2923
+ const chainInfo = chain_1.CHAIN_INFO[sourceChain];
2924
+ response.chainNamespace = `${chain_1.VM_NAMESPACE[chainInfo.vm]}:${chainInfo.chainId}`;
2925
+ return response;
2926
+ });
2927
+ }
2928
+ /**
2929
+ * Route 3 SVM: Execute CEA-to-Push for Solana chains.
2930
+ *
2931
+ * Unlike EVM Route 3 which builds CEA multicalls, SVM Route 3 encodes a
2932
+ * `send_universal_tx_to_uea` instruction as an execute payload targeting
2933
+ * the SVM gateway program (self-call). The drain amount is embedded in
2934
+ * the instruction data, not in the outbound request amount.
2935
+ */
2936
+ executeCeaToPushSvm(params, sourceChain) {
2937
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
2938
+ var _a, _b;
2939
+ if (typeof params.to !== 'string') {
2940
+ throw new Error('Route 3 SVM (CEA → Push): to must be a Push Chain address (string), not a ChainTarget');
2941
+ }
2942
+ const ueaAddress = this.computeUEAOffchain();
2943
+ // Get gateway program ID from chain config and convert to 0x-hex 32 bytes
2944
+ const lockerContract = chain_1.CHAIN_INFO[sourceChain].lockerContract;
2945
+ if (!lockerContract) {
2946
+ throw new Error(`No SVM gateway program configured for chain ${sourceChain}`);
2947
+ }
2948
+ const programPk = new web3_js_1.PublicKey(lockerContract);
2949
+ const gatewayProgramHex = ('0x' + Buffer.from(programPk.toBytes()).toString('hex'));
2950
+ this.printLog(`executeCeaToPushSvm — sourceChain: ${sourceChain}, gateway: ${lockerContract}`);
2951
+ // Determine drain amount and token
2952
+ let drainAmount = BigInt(0);
2953
+ let tokenMintHex;
2954
+ let prc20Token;
2955
+ if (((_a = params.funds) === null || _a === void 0 ? void 0 : _a.amount) && params.funds.amount > BigInt(0)) {
2956
+ // SPL token drain
2957
+ drainAmount = params.funds.amount;
2958
+ const token = params.funds.token;
2959
+ if (token && token.address) {
2960
+ // Convert SPL mint address to 32-byte hex
2961
+ const mintPk = new web3_js_1.PublicKey(token.address);
2962
+ tokenMintHex = ('0x' + Buffer.from(mintPk.toBytes()).toString('hex'));
2963
+ prc20Token = push_chain_1.PushChain.utils.tokens.getPRC20Address(token);
2964
+ }
2965
+ else {
2966
+ prc20Token = this.getNativePRC20ForChain(sourceChain);
2967
+ }
2968
+ }
2969
+ else if (params.value && params.value > BigInt(0)) {
2970
+ // Native SOL drain
2971
+ drainAmount = params.value;
2972
+ prc20Token = this.getNativePRC20ForChain(sourceChain);
2973
+ }
2974
+ else {
2975
+ // Payload-only Route 3 SVM: no funds to drain, just execute data on Push Chain
2976
+ drainAmount = BigInt(0);
2977
+ prc20Token = this.getNativePRC20ForChain(sourceChain);
2978
+ }
2979
+ // Build the SVM CPI payload (send_universal_tx_to_uea wrapped in execute)
2980
+ // If params.data is provided, pass it as extraPayload for Push Chain execution
2981
+ let extraPayload;
2982
+ if (params.data && typeof params.data === 'string') {
2983
+ extraPayload = (0, viem_1.hexToBytes)(params.data);
2984
+ }
2985
+ // Derive CEA PDA as revert recipient: ["push_identity", ueaAddress_20_bytes]
2986
+ const ueaBytes = Buffer.from(ueaAddress.slice(2), 'hex'); // 20 bytes
2987
+ const [ceaPda] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from('push_identity'), ueaBytes], programPk);
2988
+ const ceaPdaHex = ('0x' + Buffer.from(ceaPda.toBytes()).toString('hex'));
2989
+ const svmPayload = (0, payload_builders_1.encodeSvmCeaToUeaPayload)({
2990
+ gatewayProgramHex,
2991
+ drainAmount,
2992
+ tokenMintHex,
2993
+ extraPayload,
2994
+ revertRecipientHex: ceaPdaHex,
2995
+ });
2996
+ this.printLog(`executeCeaToPushSvm — drainAmount: ${drainAmount.toString()}, payload length: ${(svmPayload.length - 2) / 2} bytes`);
2997
+ // burnAmount = 1 (minimum for precompile; drain amount lives inside the ixData)
2998
+ // The precompile rejects amount=0, so we use BigInt(1) as a workaround.
2999
+ const burnAmount = BigInt(1);
3000
+ // Build outbound request: target = gateway program (self-call)
3001
+ const outboundReq = (0, payload_builders_1.buildOutboundRequest)(gatewayProgramHex, prc20Token, burnAmount, (_b = params.gasLimit) !== null && _b !== void 0 ? _b : BigInt(0), svmPayload, ueaAddress);
3002
+ this.printLog(`executeCeaToPushSvm — outbound request: target=${outboundReq.target.slice(0, 20)}..., token=${outboundReq.token}`);
3003
+ // Pre-fetch UEA status early — balance is needed for gas value calculation
3004
+ const gatewayPcAddress = this.getUniversalGatewayPCAddress();
3005
+ const [ueaCode, ueaBalance] = yield Promise.all([
3006
+ this.pushClient.publicClient.getCode({ address: ueaAddress }),
3007
+ this.pushClient.getBalance(ueaAddress),
3008
+ ]);
3009
+ const isUEADeployed = ueaCode !== undefined;
3010
+ const ueaNonce = isUEADeployed ? yield this.getUEANonce(ueaAddress) : BigInt(0);
3011
+ // Query gas fees
3012
+ let gasFee = BigInt(0);
3013
+ let nativeValueForGas = BigInt(0);
3014
+ let gasToken = selectors_1.ZERO_ADDRESS;
3015
+ if (prc20Token !== selectors_1.ZERO_ADDRESS) {
3016
+ try {
3017
+ const result = yield this.queryOutboundGasFee(prc20Token, outboundReq.gasLimit);
3018
+ gasToken = result.gasToken;
3019
+ gasFee = result.gasFee;
3020
+ nativeValueForGas = result.nativeValueForGas;
3021
+ this.printLog(`executeCeaToPushSvm — gasFee: ${gasFee.toString()}, gasToken: ${gasToken}, nativeValueForGas: ${nativeValueForGas.toString()}`);
3022
+ }
3023
+ catch (err) {
3024
+ throw new Error(`Failed to query outbound gas fee for SVM Route 3: ${err}`);
3025
+ }
3026
+ }
3027
+ // Adjust nativeValueForGas using UEA balance (contract refunds excess)
3028
+ // Re-fetch balance to minimize staleness from gas fee query RPC roundtrips
3029
+ const currentBalance = yield this.pushClient.getBalance(ueaAddress);
3030
+ // Cosmos-EVM tx overhead costs ~1 PC per operation; 3 PC covers approve(s) + buffer.
3031
+ const OUTBOUND_GAS_RESERVE_R3_SVM = BigInt(3e18);
3032
+ if (currentBalance > OUTBOUND_GAS_RESERVE_R3_SVM && currentBalance - OUTBOUND_GAS_RESERVE_R3_SVM > nativeValueForGas) {
3033
+ const adjustedValue = currentBalance - OUTBOUND_GAS_RESERVE_R3_SVM;
3034
+ this.printLog(`executeCeaToPushSvm — adjusting nativeValueForGas from ${nativeValueForGas.toString()} to ${adjustedValue.toString()} (UEA balance: ${currentBalance.toString()})`);
3035
+ nativeValueForGas = adjustedValue;
3036
+ }
3037
+ // Build Push Chain multicalls (approvals + sendUniversalTxOutbound)
3038
+ const pushChainMulticalls = (0, payload_builders_1.buildOutboundApprovalAndCall)({
3039
+ prc20Token,
3040
+ gasToken,
3041
+ burnAmount,
3042
+ gasFee,
3043
+ nativeValueForGas,
3044
+ gatewayPcAddress,
3045
+ outboundRequest: outboundReq,
3046
+ });
3047
+ // Sum native values from multicall entries for proper fee calculation
3048
+ const multicallNativeValue = pushChainMulticalls.reduce((sum, mc) => { var _a; return sum + ((_a = mc.value) !== null && _a !== void 0 ? _a : BigInt(0)); }, BigInt(0));
3049
+ // Execute through the normal execute() flow
3050
+ const executeParams = {
3051
+ to: ueaAddress,
3052
+ value: multicallNativeValue, // ensures correct requiredFunds calculation
3053
+ data: pushChainMulticalls,
3054
+ _ueaStatus: {
3055
+ isDeployed: isUEADeployed,
3056
+ nonce: ueaNonce,
3057
+ balance: ueaBalance,
3058
+ },
3059
+ _skipFeeLocking: true, // outbound executes on Push Chain, no external fee locking
3060
+ };
3061
+ const response = yield this.execute(executeParams);
3062
+ // Add Route 3 SVM context to response
3063
+ response.chain = sourceChain;
3064
+ const chainInfo = chain_1.CHAIN_INFO[sourceChain];
3065
+ response.chainNamespace = `${chain_1.VM_NAMESPACE[chainInfo.vm]}:${chainInfo.chainId}`;
3066
+ return response;
3067
+ });
3068
+ }
3069
+ /**
3070
+ * Route 4: Execute CEA to CEA transaction via Push Chain
3071
+ *
3072
+ * @param params - Universal execution parameters with from.chain and to.chain
3073
+ * @returns UniversalTxResponse
3074
+ */
3075
+ executeCeaToCea(params) {
3076
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
3077
+ // CEA → CEA requires chaining Route 3 (CEA → Push) and Route 2 (Push → CEA)
3078
+ // This is a complex flow that requires coordination
3079
+ throw new Error('CEA → CEA transactions are not yet fully implemented. ' +
3080
+ 'Use prepareTransaction().thenOn() to chain Route 3 → Route 2 manually.');
3081
+ });
3082
+ }
3083
+ /**
3084
+ * Build payload for a specific route
3085
+ */
3086
+ buildPayloadForRoute(params, route, nonce) {
3087
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
3088
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
3089
+ const ueaAddress = this.computeUEAOffchain();
3090
+ switch (route) {
3091
+ case route_detector_1.TransactionRoute.UOA_TO_PUSH: {
3092
+ // Build standard Push Chain payload
3093
+ const executeParams = this.toExecuteParams(params);
3094
+ const multicallData = (0, payload_builders_1.buildExecuteMulticall)({
3095
+ execute: executeParams,
3096
+ ueaAddress,
3097
+ });
3098
+ const payload = this._buildMulticallPayloadData(executeParams.to, multicallData);
3099
+ const req = this._buildUniversalTxRequest({
3100
+ recipient: viem_1.zeroAddress,
3101
+ token: viem_1.zeroAddress,
3102
+ amount: BigInt(0),
3103
+ payload,
3104
+ });
3105
+ return { payload, gatewayRequest: req };
3106
+ }
3107
+ case route_detector_1.TransactionRoute.UOA_TO_CEA: {
3108
+ const target = params.to;
3109
+ // Branch: SVM vs EVM
3110
+ if ((0, payload_builders_1.isSvmChain)(target.chain)) {
3111
+ // SVM: build SVM payload (binary or empty for withdraw)
3112
+ let payload = '0x';
3113
+ if (params.svmExecute) {
3114
+ payload = (0, payload_builders_1.encodeSvmExecutePayload)({
3115
+ targetProgram: params.svmExecute.targetProgram,
3116
+ accounts: params.svmExecute.accounts,
3117
+ ixData: params.svmExecute.ixData,
3118
+ instructionId: 2,
3119
+ });
3120
+ }
3121
+ const targetBytes = (_b = (_a = params.svmExecute) === null || _a === void 0 ? void 0 : _a.targetProgram) !== null && _b !== void 0 ? _b : target.address;
3122
+ let prc20Token = selectors_1.ZERO_ADDRESS;
3123
+ let burnAmount = BigInt(0);
3124
+ if ((_c = params.funds) === null || _c === void 0 ? void 0 : _c.amount) {
3125
+ const token = params.funds.token;
3126
+ if (token) {
3127
+ prc20Token = push_chain_1.PushChain.utils.tokens.getPRC20Address(token);
3128
+ burnAmount = params.funds.amount;
3129
+ }
3130
+ }
3131
+ else if (params.value && params.value > BigInt(0)) {
3132
+ prc20Token = this.getNativePRC20ForChain(target.chain);
3133
+ burnAmount = params.value;
3134
+ }
3135
+ else if (params.svmExecute) {
3136
+ prc20Token = this.getNativePRC20ForChain(target.chain);
3137
+ burnAmount = BigInt(1);
3138
+ }
3139
+ const outboundReq = (0, payload_builders_1.buildOutboundRequest)(targetBytes, prc20Token, burnAmount, (_d = params.gasLimit) !== null && _d !== void 0 ? _d : BigInt(0), payload, ueaAddress);
3140
+ return { payload, gatewayRequest: outboundReq };
3141
+ }
3142
+ // EVM path: Resolve CEA address first (needed for self-transfer check)
3143
+ const { cea: ceaAddress } = yield (0, cea_utils_1.getCEAAddress)(ueaAddress, target.chain, (_e = this.rpcUrls[target.chain]) === null || _e === void 0 ? void 0 : _e[0]);
3144
+ // Build CEA outbound payload
3145
+ const multicalls = [];
3146
+ if (params.data) {
3147
+ if (Array.isArray(params.data)) {
3148
+ multicalls.push(...params.data);
3149
+ }
3150
+ else {
3151
+ // Single call with data. Native value (if any) is already delivered to
3152
+ // CEA by the Vault via executeUniversalTx{value: amount}(). Attaching
3153
+ // value to the call would revert if the target function is not payable.
3154
+ // To call a payable function with value, use explicit multicalls.
3155
+ multicalls.push({
3156
+ to: target.address,
3157
+ value: BigInt(0),
3158
+ data: params.data,
3159
+ });
3160
+ }
3161
+ }
3162
+ else if (params.value) {
3163
+ // Skip multicall when sending native value to own CEA — gateway deposits directly.
3164
+ // Self-call with value would revert (CEA._handleMulticall rejects it).
3165
+ if (target.address.toLowerCase() !== ceaAddress.toLowerCase()) {
3166
+ multicalls.push({
3167
+ to: target.address,
3168
+ value: params.value,
3169
+ data: '0x',
3170
+ });
3171
+ }
3172
+ }
3173
+ const payload = (0, payload_builders_1.buildCeaMulticallPayload)(multicalls);
3174
+ let prc20Token = selectors_1.ZERO_ADDRESS;
3175
+ let burnAmount = BigInt(0);
3176
+ if ((_f = params.funds) === null || _f === void 0 ? void 0 : _f.amount) {
3177
+ const token = params.funds.token;
3178
+ if (token) {
3179
+ prc20Token = push_chain_1.PushChain.utils.tokens.getPRC20Address(token);
3180
+ burnAmount = params.funds.amount;
3181
+ }
3182
+ }
3183
+ const targetBytes = ceaAddress;
3184
+ const outboundReq = (0, payload_builders_1.buildOutboundRequest)(targetBytes, prc20Token, burnAmount, (_g = params.gasLimit) !== null && _g !== void 0 ? _g : BigInt(0), payload, ueaAddress);
3185
+ return { payload, gatewayRequest: outboundReq };
3186
+ }
3187
+ case route_detector_1.TransactionRoute.CEA_TO_PUSH: {
3188
+ // Route 3: CEA → Push Chain
3189
+ // Build CEA multicall (approve + sendUniversalTxFromCEA) and wrap in outbound
3190
+ if (!((_h = params.from) === null || _h === void 0 ? void 0 : _h.chain)) {
3191
+ throw new Error('Route 3 (CEA → Push) requires from.chain');
3192
+ }
3193
+ const sourceChain = params.from.chain;
3194
+ const pushDestination = params.to;
3195
+ // SVM chains use gateway self-call, not CEA multicall
3196
+ if ((0, payload_builders_1.isSvmChain)(sourceChain)) {
3197
+ const lockerContract = chain_1.CHAIN_INFO[sourceChain].lockerContract;
3198
+ if (!lockerContract) {
3199
+ throw new Error(`No SVM gateway program configured for chain ${sourceChain}`);
3200
+ }
3201
+ const programPk = new web3_js_1.PublicKey(lockerContract);
3202
+ const gatewayProgramHex = ('0x' + Buffer.from(programPk.toBytes()).toString('hex'));
3203
+ let drainAmount = BigInt(0);
3204
+ let tokenMintHex;
3205
+ let prc20Token;
3206
+ if (((_j = params.funds) === null || _j === void 0 ? void 0 : _j.amount) && params.funds.amount > BigInt(0)) {
3207
+ drainAmount = params.funds.amount;
3208
+ const token = params.funds.token;
3209
+ if (token && token.address) {
3210
+ const mintPk = new web3_js_1.PublicKey(token.address);
3211
+ tokenMintHex = ('0x' + Buffer.from(mintPk.toBytes()).toString('hex'));
3212
+ prc20Token = push_chain_1.PushChain.utils.tokens.getPRC20Address(token);
3213
+ }
3214
+ else {
3215
+ prc20Token = this.getNativePRC20ForChain(sourceChain);
3216
+ }
3217
+ }
3218
+ else if (params.value && params.value > BigInt(0)) {
3219
+ drainAmount = params.value;
3220
+ prc20Token = this.getNativePRC20ForChain(sourceChain);
3221
+ }
3222
+ else {
3223
+ // Payload-only Route 3 SVM: no funds to drain, just execute data on Push Chain
3224
+ drainAmount = BigInt(0);
3225
+ prc20Token = this.getNativePRC20ForChain(sourceChain);
3226
+ }
3227
+ // Derive CEA PDA as revert recipient
3228
+ const ueaBytes2 = Buffer.from(ueaAddress.slice(2), 'hex');
3229
+ const [ceaPda2] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from('push_identity'), ueaBytes2], programPk);
3230
+ const ceaPdaHex2 = ('0x' + Buffer.from(ceaPda2.toBytes()).toString('hex'));
3231
+ const svmPayload = (0, payload_builders_1.encodeSvmCeaToUeaPayload)({
3232
+ gatewayProgramHex,
3233
+ drainAmount,
3234
+ tokenMintHex,
3235
+ revertRecipientHex: ceaPdaHex2,
3236
+ });
3237
+ // burnAmount = 1 (minimum for precompile; drain amount lives inside the ixData)
3238
+ const burnAmount = BigInt(1);
3239
+ const outboundReq = (0, payload_builders_1.buildOutboundRequest)(gatewayProgramHex, prc20Token, burnAmount, (_k = params.gasLimit) !== null && _k !== void 0 ? _k : BigInt(0), svmPayload, ueaAddress);
3240
+ return { payload: svmPayload, gatewayRequest: outboundReq };
3241
+ }
3242
+ const { cea: ceaAddress } = yield (0, cea_utils_1.getCEAAddress)(ueaAddress, sourceChain, (_l = this.rpcUrls[sourceChain]) === null || _l === void 0 ? void 0 : _l[0]);
3243
+ const gatewayAddr = chain_1.UNIVERSAL_GATEWAY_ADDRESSES[sourceChain];
3244
+ if (!gatewayAddr) {
3245
+ throw new Error(`No UniversalGateway address configured for chain ${sourceChain}`);
3246
+ }
3247
+ // Build CEA multicalls (self-calls via sendUniversalTxToUEA)
3248
+ const ceaMulticalls = [];
3249
+ let tokenAddress = selectors_1.ZERO_ADDRESS;
3250
+ let amount = BigInt(0);
3251
+ if ((_m = params.funds) === null || _m === void 0 ? void 0 : _m.amount) {
3252
+ const token = params.funds.token;
3253
+ if (token) {
3254
+ if (token.mechanism === 'native') {
3255
+ tokenAddress = selectors_1.ZERO_ADDRESS;
3256
+ amount = params.funds.amount;
3257
+ }
3258
+ else {
3259
+ tokenAddress = token.address;
3260
+ amount = params.funds.amount;
3261
+ // Approve gateway for ERC20 (CEA self-call, value=0)
3262
+ const approveData = (0, viem_1.encodeFunctionData)({
3263
+ abi: abi_1.ERC20_EVM,
3264
+ functionName: 'approve',
3265
+ args: [gatewayAddr, amount],
3266
+ });
3267
+ ceaMulticalls.push({
3268
+ to: tokenAddress,
3269
+ value: BigInt(0),
3270
+ data: approveData,
3271
+ });
972
3272
  }
973
- })();
974
- this.executeProgressHook(progress_hook_types_1.PROGRESS_HOOK.SEND_TX_99_02, errMessage);
975
- throw err;
976
- }
977
- finally {
978
- // Restore original progressHook
979
- this.progressHook = originalHook;
3273
+ }
3274
+ }
3275
+ else if (params.value && params.value > BigInt(0)) {
3276
+ tokenAddress = selectors_1.ZERO_ADDRESS;
3277
+ amount = params.value;
3278
+ }
3279
+ // Fetch UEA nonce for inbound UniversalPayload
3280
+ const ueaCodeHop = yield this.pushClient.publicClient.getCode({ address: ueaAddress });
3281
+ const ueaNonceHop = ueaCodeHop !== undefined ? yield this.getUEANonce(ueaAddress) : BigInt(0);
3282
+ // Build Push Chain payload (what executes after inbound arrives)
3283
+ // Wrap in UniversalPayload struct for the relay.
3284
+ let pushPayload = '0x';
3285
+ if (params.data) {
3286
+ const multicallData = (0, payload_builders_1.buildExecuteMulticall)({
3287
+ execute: {
3288
+ to: pushDestination,
3289
+ value: params.value,
3290
+ data: params.data,
3291
+ },
3292
+ ueaAddress,
3293
+ });
3294
+ const multicallPayload = this._buildMulticallPayloadData(pushDestination, multicallData);
3295
+ pushPayload = (0, payload_builders_1.buildInboundUniversalPayload)(multicallPayload, { nonce: ueaNonceHop + BigInt(1) });
3296
+ }
3297
+ // Build sendUniversalTxToUEA self-call on CEA (to=CEA, value=0)
3298
+ const sendCall = (0, payload_builders_1.buildSendUniversalTxToUEA)(ceaAddress, tokenAddress, amount, pushPayload, ceaAddress);
3299
+ ceaMulticalls.push(sendCall);
3300
+ const ceaPayload = (0, payload_builders_1.buildCeaMulticallPayload)(ceaMulticalls);
3301
+ const prc20Token = this.getNativePRC20ForChain(sourceChain);
3302
+ // burnAmount = actual amount needed by CEA. Vault deposits this as msg.value.
3303
+ // Fallback to BigInt(1) for payload-only outbound (precompile rejects amount=0).
3304
+ const burnAmount = amount > BigInt(0) ? amount : BigInt(1);
3305
+ const outboundReq = (0, payload_builders_1.buildOutboundRequest)(ceaAddress, prc20Token, burnAmount, (_o = params.gasLimit) !== null && _o !== void 0 ? _o : BigInt(0), ceaPayload, ueaAddress);
3306
+ return { payload: ceaPayload, gatewayRequest: outboundReq };
3307
+ }
3308
+ default:
3309
+ throw new Error(`Cannot build payload for route: ${route}`);
980
3310
  }
981
3311
  });
982
3312
  }
3313
+ /**
3314
+ * Convert UniversalExecuteParams to ExecuteParams for backwards compatibility
3315
+ */
3316
+ toExecuteParams(params) {
3317
+ // Extract address from ChainTarget if needed
3318
+ const to = typeof params.to === 'string'
3319
+ ? params.to
3320
+ : params.to.address;
3321
+ return {
3322
+ to,
3323
+ value: params.value,
3324
+ data: params.data,
3325
+ funds: params.funds,
3326
+ gasLimit: params.gasLimit,
3327
+ maxFeePerGas: params.maxFeePerGas,
3328
+ maxPriorityFeePerGas: params.maxPriorityFeePerGas,
3329
+ deadline: params.deadline,
3330
+ payGasWith: params.payGasWith,
3331
+ feeLockTxHash: params.feeLockTxHash,
3332
+ };
3333
+ }
3334
+ /**
3335
+ * Get the UniversalGatewayPC address for the current Push network
3336
+ */
3337
+ getUniversalGatewayPCAddress() {
3338
+ // UniversalGatewayPC is a precompile at a fixed address on Push Chain
3339
+ // Address: 0x00000000000000000000000000000000000000C1
3340
+ return '0x00000000000000000000000000000000000000C1';
3341
+ }
3342
+ /**
3343
+ * Get the native PRC-20 token address on Push Chain for a target external chain.
3344
+ * Maps chains to their native asset representations on Push Chain.
3345
+ *
3346
+ * @param targetChain - The target external chain
3347
+ * @returns PRC-20 token address on Push Chain
3348
+ */
3349
+ getNativePRC20ForChain(targetChain) {
3350
+ const synthetics = chain_1.SYNTHETIC_PUSH_ERC20[this.pushNetwork];
3351
+ switch (targetChain) {
3352
+ case enums_1.CHAIN.ETHEREUM_SEPOLIA:
3353
+ case enums_1.CHAIN.ETHEREUM_MAINNET:
3354
+ return synthetics.pETH;
3355
+ case enums_1.CHAIN.ARBITRUM_SEPOLIA:
3356
+ return synthetics.pETH_ARB;
3357
+ case enums_1.CHAIN.BASE_SEPOLIA:
3358
+ return synthetics.pETH_BASE;
3359
+ case enums_1.CHAIN.BNB_TESTNET:
3360
+ return synthetics.pETH_BNB;
3361
+ case enums_1.CHAIN.SOLANA_DEVNET:
3362
+ case enums_1.CHAIN.SOLANA_TESTNET:
3363
+ case enums_1.CHAIN.SOLANA_MAINNET:
3364
+ return synthetics.pSOL;
3365
+ default:
3366
+ throw new Error(`No native PRC-20 token mapping for chain ${targetChain}. ` +
3367
+ `Use 'funds' parameter to specify the token explicitly.`);
3368
+ }
3369
+ }
3370
+ /**
3371
+ * Get CAIP-2 chain namespace for a chain
3372
+ */
3373
+ getChainNamespace(chain) {
3374
+ const { vm, chainId } = chain_1.CHAIN_INFO[chain];
3375
+ const namespace = chain_1.VM_NAMESPACE[vm];
3376
+ return `${namespace}:${chainId}`;
3377
+ }
983
3378
  /**
984
3379
  * Locks a fee on the origin chain by interacting with the chain's fee-locker contract.
985
3380
  *
@@ -1015,22 +3410,7 @@ class Orchestrator {
1015
3410
  (nativeTokenUsdPrice - BigInt(1))) /
1016
3411
  nativeTokenUsdPrice;
1017
3412
  nativeAmount = nativeAmount + BigInt(1);
1018
- // const req: UniversalTxRequest = {
1019
- // recipient: zeroAddress,
1020
- // token: zeroAddress,
1021
- // amount: BigInt(0),
1022
- // payload: payloadBytes,
1023
- // revertInstruction: revertCFG,
1024
- // signatureData: '0x',
1025
- // } as unknown as never;
1026
- const txHash = yield evmClient.writeContract({
1027
- abi: abi_1.UNIVERSAL_GATEWAY_V0,
1028
- address: lockerContract,
1029
- functionName: 'sendUniversalTx',
1030
- args: [req],
1031
- signer: this.universalSigner,
1032
- value: nativeAmount,
1033
- });
3413
+ const txHash = yield this.sendGatewayTxWithFallback(evmClient, lockerContract, req, this.universalSigner, nativeAmount);
1034
3414
  return (0, viem_1.hexToBytes)(txHash);
1035
3415
  }
1036
3416
  case enums_1.VM.SVM: {
@@ -1058,21 +3438,19 @@ class Orchestrator {
1058
3438
  const [rateLimitConfigPda] = web3_js_1.PublicKey.findProgramAddressSync([(0, viem_1.stringToBytes)('rate_limit_config')], programId);
1059
3439
  const [tokenRateLimitPda] = web3_js_1.PublicKey.findProgramAddressSync([(0, viem_1.stringToBytes)('rate_limit'), web3_js_1.PublicKey.default.toBuffer()], programId);
1060
3440
  const userPk = new web3_js_1.PublicKey(this.universalSigner.account.address);
1061
- const revertSvm = {
1062
- fundRecipient: userPk,
1063
- revertMsg: Buffer.from([]),
1064
- };
3441
+ const { feeVaultPda, protocolFeeLamports } = yield this._getSvmProtocolFee(svmClient, programId);
1065
3442
  const gasReq = this._buildSvmUniversalTxRequestFromReq(req, userPk);
1066
3443
  try {
1067
3444
  const txHash = yield svmClient.writeContract({
1068
3445
  abi: abi_1.SVM_GATEWAY_IDL,
1069
3446
  address: programId.toBase58(),
1070
3447
  functionName: 'sendUniversalTx',
1071
- args: [gasReq, nativeAmount],
3448
+ args: [gasReq, nativeAmount + protocolFeeLamports],
1072
3449
  signer: this.universalSigner,
1073
3450
  accounts: {
1074
3451
  config: configPda,
1075
3452
  vault: vaultPda,
3453
+ feeVault: feeVaultPda,
1076
3454
  userTokenAccount: vaultPda,
1077
3455
  gatewayTokenAccount: vaultPda,
1078
3456
  user: userPk,
@@ -1109,12 +3487,200 @@ class Orchestrator {
1109
3487
  signatureData: '0x',
1110
3488
  };
1111
3489
  }
3490
+ /**
3491
+ * Returns the resolved gateway version for the current origin chain.
3492
+ * Checks the per-chain runtime cache first, then falls back to static config.
3493
+ * TODO: Remove V0 fallback once all chains are upgraded to V1.
3494
+ */
3495
+ getGatewayVersion() {
3496
+ var _a;
3497
+ const chain = this.universalSigner.account.chain;
3498
+ // 1. Check runtime cache (set after a successful tx)
3499
+ const cached = this.gatewayVersionCache.get(chain);
3500
+ if (cached)
3501
+ return cached;
3502
+ // 2. Fall back to static config
3503
+ return (_a = chain_1.CHAIN_INFO[chain].gatewayVersion) !== null && _a !== void 0 ? _a : 'v0';
3504
+ }
3505
+ /**
3506
+ * Returns true if the current origin chain uses the V1 gateway (revertRecipient address).
3507
+ */
3508
+ isV1Gateway() {
3509
+ return this.getGatewayVersion() === 'v1';
3510
+ }
3511
+ /**
3512
+ * Returns the correct gateway ABI for the given version.
3513
+ */
3514
+ getGatewayAbiForVersion(version) {
3515
+ return version === 'v1'
3516
+ ? abi_1.UNIVERSAL_GATEWAY_V1_SEND
3517
+ : abi_1.UNIVERSAL_GATEWAY_V0;
3518
+ }
3519
+ /**
3520
+ * Returns the correct gateway ABI based on the origin chain's gateway version.
3521
+ */
3522
+ getGatewayAbi() {
3523
+ return this.getGatewayAbiForVersion(this.getGatewayVersion());
3524
+ }
3525
+ /**
3526
+ * Converts a V0 UniversalTxRequest to V1 format.
3527
+ */
3528
+ toGatewayRequestV1(req) {
3529
+ return {
3530
+ recipient: req.recipient,
3531
+ token: req.token,
3532
+ amount: req.amount,
3533
+ payload: req.payload,
3534
+ revertRecipient: req.revertInstruction.fundRecipient,
3535
+ signatureData: req.signatureData,
3536
+ };
3537
+ }
3538
+ /**
3539
+ * Converts a V0 UniversalTxRequest to the correct format based on gateway version.
3540
+ */
3541
+ toGatewayRequest(req) {
3542
+ if (!this.isV1Gateway())
3543
+ return req;
3544
+ return this.toGatewayRequestV1(req);
3545
+ }
3546
+ /**
3547
+ * Converts a V0 UniversalTokenTxRequest to V1 format.
3548
+ */
3549
+ toGatewayTokenRequestV1(req) {
3550
+ return {
3551
+ recipient: req.recipient,
3552
+ token: req.token,
3553
+ amount: req.amount,
3554
+ gasToken: req.gasToken,
3555
+ gasAmount: req.gasAmount,
3556
+ payload: req.payload,
3557
+ revertRecipient: req.revertInstruction.fundRecipient,
3558
+ signatureData: req.signatureData,
3559
+ amountOutMinETH: req.amountOutMinETH,
3560
+ deadline: req.deadline,
3561
+ };
3562
+ }
3563
+ /**
3564
+ * Converts a V0 UniversalTokenTxRequest to the correct format based on gateway version.
3565
+ */
3566
+ toGatewayTokenRequest(req) {
3567
+ if (!this.isV1Gateway())
3568
+ return req;
3569
+ return this.toGatewayTokenRequestV1(req);
3570
+ }
3571
+ /**
3572
+ * Sends a gateway transaction with V1-first, V0-fallback strategy.
3573
+ *
3574
+ * Strategy:
3575
+ * 1. Try V1 (revertRecipient address format) first.
3576
+ * 2. If V1 fails (contract not yet upgraded), retry with V0 (revertInstruction struct).
3577
+ * 3. Cache the working version per chain so subsequent calls skip the fallback.
3578
+ *
3579
+ * TODO: Remove V0 fallback once all chains are upgraded to V1. After that,
3580
+ * delete this method and call writeContract directly with V1 ABI.
3581
+ */
3582
+ sendGatewayTxWithFallback(evmClient, address, req, signer, value) {
3583
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
3584
+ const chain = this.universalSigner.account.chain;
3585
+ const currentVersion = this.getGatewayVersion();
3586
+ // If we already know the version (from cache or config), use it directly — no fallback needed.
3587
+ if (this.gatewayVersionCache.has(chain)) {
3588
+ return evmClient.writeContract({
3589
+ abi: this.getGatewayAbiForVersion(currentVersion),
3590
+ address,
3591
+ functionName: 'sendUniversalTx',
3592
+ args: [currentVersion === 'v1' ? this.toGatewayRequestV1(req) : req],
3593
+ signer,
3594
+ value,
3595
+ });
3596
+ }
3597
+ // No cached version yet — try V1 first, fall back to V0.
3598
+ // TODO: Remove this try/catch block once all chains are on V1.
3599
+ try {
3600
+ this.printLog(`[Gateway] Trying V1 format for chain ${chain}...`);
3601
+ const txHash = yield evmClient.writeContract({
3602
+ abi: this.getGatewayAbiForVersion('v1'),
3603
+ address,
3604
+ functionName: 'sendUniversalTx',
3605
+ args: [this.toGatewayRequestV1(req)],
3606
+ signer,
3607
+ value,
3608
+ });
3609
+ // V1 succeeded — cache it
3610
+ this.gatewayVersionCache.set(chain, 'v1');
3611
+ this.printLog(`[Gateway] V1 succeeded for chain ${chain}, cached.`);
3612
+ return txHash;
3613
+ }
3614
+ catch (v1Error) {
3615
+ this.printLog(`[Gateway] V1 failed for chain ${chain}, falling back to V0... Error: ${v1Error}`);
3616
+ }
3617
+ // V0 fallback — contract not yet upgraded on this chain
3618
+ // TODO: Remove this block once all chains are upgraded to V1.
3619
+ try {
3620
+ const txHash = yield evmClient.writeContract({
3621
+ abi: this.getGatewayAbiForVersion('v0'),
3622
+ address,
3623
+ functionName: 'sendUniversalTx',
3624
+ args: [req],
3625
+ signer,
3626
+ value,
3627
+ });
3628
+ // V0 succeeded — cache it
3629
+ this.gatewayVersionCache.set(chain, 'v0');
3630
+ this.printLog(`[Gateway] V0 succeeded for chain ${chain}, cached.`);
3631
+ return txHash;
3632
+ }
3633
+ catch (v0Error) {
3634
+ // Both versions failed — throw the V0 error (more likely to be the real issue on non-upgraded chains)
3635
+ throw v0Error;
3636
+ }
3637
+ });
3638
+ }
3639
+ /**
3640
+ * Sends a gateway token transaction with V1-first, V0-fallback strategy.
3641
+ * Same as sendGatewayTxWithFallback but for UniversalTokenTxRequest (gas token path).
3642
+ *
3643
+ * TODO: Remove V0 fallback once all chains are upgraded to V1.
3644
+ */
3645
+ sendGatewayTokenTxWithFallback(evmClient, address, req, signer, value) {
3646
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
3647
+ const chain = this.universalSigner.account.chain;
3648
+ const currentVersion = this.getGatewayVersion();
3649
+ // If we already know the version (from cache or config), use it directly.
3650
+ if (this.gatewayVersionCache.has(chain)) {
3651
+ return evmClient.writeContract(Object.assign({ abi: this.getGatewayAbiForVersion(currentVersion), address, functionName: 'sendUniversalTx', args: [currentVersion === 'v1' ? this.toGatewayTokenRequestV1(req) : req], signer }, (value !== undefined && { value })));
3652
+ }
3653
+ // No cached version yet — try V1 first, fall back to V0.
3654
+ // TODO: Remove this try/catch block once all chains are on V1.
3655
+ try {
3656
+ this.printLog(`[Gateway] Trying V1 token format for chain ${chain}...`);
3657
+ const txHash = yield evmClient.writeContract(Object.assign({ abi: this.getGatewayAbiForVersion('v1'), address, functionName: 'sendUniversalTx', args: [this.toGatewayTokenRequestV1(req)], signer }, (value !== undefined && { value })));
3658
+ this.gatewayVersionCache.set(chain, 'v1');
3659
+ this.printLog(`[Gateway] V1 token tx succeeded for chain ${chain}, cached.`);
3660
+ return txHash;
3661
+ }
3662
+ catch (v1Error) {
3663
+ this.printLog(`[Gateway] V1 token tx failed for chain ${chain}, falling back to V0... Error: ${v1Error}`);
3664
+ }
3665
+ // V0 fallback
3666
+ // TODO: Remove this block once all chains are upgraded to V1.
3667
+ try {
3668
+ const txHash = yield evmClient.writeContract(Object.assign({ abi: this.getGatewayAbiForVersion('v0'), address, functionName: 'sendUniversalTx', args: [req], signer }, (value !== undefined && { value })));
3669
+ this.gatewayVersionCache.set(chain, 'v0');
3670
+ this.printLog(`[Gateway] V0 token tx succeeded for chain ${chain}, cached.`);
3671
+ return txHash;
3672
+ }
3673
+ catch (v0Error) {
3674
+ throw v0Error;
3675
+ }
3676
+ });
3677
+ }
1112
3678
  /**
1113
3679
  * Builds the SVM UniversalTxRequest object from an existing EVM-style UniversalTxRequest.
1114
3680
  * This allows reusing the same request shape for both EVM and SVM while only translating
1115
3681
  * field encodings (addresses, bytes) to the Solana program format.
1116
3682
  */
1117
- _buildSvmUniversalTxRequestFromReq(req, fundRecipient, signatureDataOverride) {
3683
+ _buildSvmUniversalTxRequestFromReq(req, revertRecipient, signatureDataOverride) {
1118
3684
  // recipient in EVM is a 20-byte address; the SVM gateway expects this as [u8; 20]
1119
3685
  const recipientBytes = (0, viem_1.hexToBytes)(req.recipient);
1120
3686
  const recipient = Array.from(recipientBytes.subarray(0, 20));
@@ -1143,7 +3709,7 @@ class Orchestrator {
1143
3709
  token,
1144
3710
  amount: req.amount,
1145
3711
  payload: req.payload,
1146
- fundRecipient,
3712
+ revertRecipient,
1147
3713
  signatureData: signatureDataOverride !== null && signatureDataOverride !== void 0 ? signatureDataOverride : req.signatureData,
1148
3714
  });
1149
3715
  }
@@ -1151,7 +3717,7 @@ class Orchestrator {
1151
3717
  * Builds the SVM UniversalTxRequest object expected by the Solana gateway program.
1152
3718
  * Shape mirrors the request used in `svm-gateway` tests.
1153
3719
  */
1154
- _buildSvmUniversalTxRequest({ recipient, token, amount, payload, fundRecipient, signatureData, }) {
3720
+ _buildSvmUniversalTxRequest({ recipient, token, amount, payload, revertRecipient, signatureData, }) {
1155
3721
  const payloadBuf = typeof payload === 'string' && payload.startsWith('0x')
1156
3722
  ? (() => {
1157
3723
  const hex = payload.slice(2);
@@ -1188,13 +3754,29 @@ class Orchestrator {
1188
3754
  token,
1189
3755
  amount,
1190
3756
  payload: payloadBuf,
1191
- revertInstruction: {
1192
- fundRecipient,
1193
- revertMsg: Buffer.alloc(0),
1194
- },
3757
+ revertRecipient: revertRecipient,
1195
3758
  signatureData: signatureBuf,
1196
3759
  };
1197
3760
  }
3761
+ _getSvmProtocolFee(svmClient, programId) {
3762
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
3763
+ var _a, _b, _c;
3764
+ const [feeVaultPda] = web3_js_1.PublicKey.findProgramAddressSync([(0, viem_1.stringToBytes)('fee_vault')], programId);
3765
+ try {
3766
+ const feeVault = yield svmClient.readContract({
3767
+ abi: abi_1.SVM_GATEWAY_IDL,
3768
+ address: abi_1.SVM_GATEWAY_IDL.address,
3769
+ functionName: 'feeVault',
3770
+ args: [feeVaultPda.toBase58()],
3771
+ });
3772
+ const protocolFeeLamports = BigInt((_c = (_b = ((_a = feeVault.protocolFeeLamports) !== null && _a !== void 0 ? _a : feeVault.protocol_fee_lamports)) === null || _b === void 0 ? void 0 : _b.toString()) !== null && _c !== void 0 ? _c : '0');
3773
+ return { feeVaultPda, protocolFeeLamports };
3774
+ }
3775
+ catch (_d) {
3776
+ return { feeVaultPda, protocolFeeLamports: BigInt(0) };
3777
+ }
3778
+ });
3779
+ }
1198
3780
  signUniversalPayload(universalPayload, verifyingContract, version) {
1199
3781
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
1200
3782
  const chain = this.universalSigner.account.chain;
@@ -1246,7 +3828,7 @@ class Orchestrator {
1246
3828
  */
1247
3829
  sendUniversalTx(isUEADeployed_1, feeLockTxHash_1, universalPayload_1, verificationData_1) {
1248
3830
  return tslib_1.__awaiter(this, arguments, void 0, function* (isUEADeployed, feeLockTxHash, universalPayload, verificationData, eventBuffer = []) {
1249
- var _a, _b;
3831
+ var _a, _b, _c, _d;
1250
3832
  const { chain, address } = this.universalSigner.account;
1251
3833
  const { vm, chainId } = chain_1.CHAIN_INFO[chain];
1252
3834
  const universalAccountId = {
@@ -1293,12 +3875,40 @@ class Orchestrator {
1293
3875
  const txRaw = yield this.pushClient.signCosmosTx(txBody);
1294
3876
  const tx = yield this.pushClient.broadcastCosmosTx(txRaw);
1295
3877
  if (tx.code !== 0) {
3878
+ // Try to extract ethereum tx hash even from failed tx
3879
+ const failedEthTxHashes = (_b = (_a = tx.events) === null || _a === void 0 ? void 0 : _a.filter((e) => e.type === 'ethereum_tx').flatMap((e) => {
3880
+ var _a;
3881
+ return (_a = e.attributes) === null || _a === void 0 ? void 0 : _a.filter((attr) => attr.key === 'ethereumTxHash').map((attr) => attr.value);
3882
+ })) !== null && _b !== void 0 ? _b : [];
3883
+ // Print clean transaction failure summary
3884
+ console.error(`\n${'='.repeat(80)}`);
3885
+ console.error(`PUSH CHAIN TRANSACTION FAILED`);
3886
+ console.error(`${'='.repeat(80)}`);
3887
+ console.error(`\n--- TRANSACTION INFO ---`);
3888
+ console.error(`Cosmos TX Hash: ${tx.transactionHash}`);
3889
+ console.error(`Block Height: ${tx.height}`);
3890
+ console.error(`TX Code: ${tx.code} (error)`);
3891
+ console.error(`Gas Used: ${tx.gasUsed}`);
3892
+ console.error(`Gas Wanted: ${tx.gasWanted}`);
3893
+ if (failedEthTxHashes.length > 0) {
3894
+ console.error(`Ethereum TX Hash(es): ${failedEthTxHashes.join(', ')}`);
3895
+ }
3896
+ console.error(`\n--- ERROR ---`);
3897
+ console.error(`${tx.rawLog}`);
3898
+ console.error(`\n--- QUERY COMMANDS ---`);
3899
+ console.error(`# Query via Cosmos RPC:`);
3900
+ console.error(`curl -s "https://donut.rpc.push.org/tx?hash=0x${tx.transactionHash}"`);
3901
+ if (failedEthTxHashes.length > 0) {
3902
+ console.error(`\n# View on explorer:`);
3903
+ console.error(`https://explorer.push.org/tx/${failedEthTxHashes[0]}`);
3904
+ }
3905
+ console.error(`\n${'='.repeat(80)}\n`);
1296
3906
  throw new Error(tx.rawLog);
1297
3907
  }
1298
- const ethTxHashes = (_b = (_a = tx.events) === null || _a === void 0 ? void 0 : _a.filter((e) => e.type === 'ethereum_tx').flatMap((e) => {
3908
+ const ethTxHashes = (_d = (_c = tx.events) === null || _c === void 0 ? void 0 : _c.filter((e) => e.type === 'ethereum_tx').flatMap((e) => {
1299
3909
  var _a;
1300
3910
  return (_a = e.attributes) === null || _a === void 0 ? void 0 : _a.filter((attr) => attr.key === 'ethereumTxHash').map((attr) => attr.value);
1301
- })) !== null && _b !== void 0 ? _b : [];
3911
+ })) !== null && _d !== void 0 ? _d : [];
1302
3912
  if (ethTxHashes.length === 0) {
1303
3913
  throw new Error('No ethereumTxHash found in transaction events');
1304
3914
  }
@@ -1307,7 +3917,8 @@ class Orchestrator {
1307
3917
  return yield this.pushClient.getTransaction(hash);
1308
3918
  })));
1309
3919
  // Pass eventBuffer only to the last transaction (which is the one returned to user)
1310
- return yield Promise.all(evmTxs.map((tx, index) => this.transformToUniversalTxResponse(tx, index === evmTxs.length - 1 ? eventBuffer : [])));
3920
+ const responses = yield Promise.all(evmTxs.map((tx, index) => this.transformToUniversalTxResponse(tx, index === evmTxs.length - 1 ? eventBuffer : [])));
3921
+ return responses;
1311
3922
  });
1312
3923
  }
1313
3924
  /**
@@ -1318,9 +3929,68 @@ class Orchestrator {
1318
3929
  */
1319
3930
  sendPushTx(execute_1) {
1320
3931
  return tslib_1.__awaiter(this, arguments, void 0, function* (execute, eventBuffer = []) {
1321
- // For PushChain, multicall is not supported. Ensure data is hex string.
3932
+ // If data is a multicall array, execute each call as a separate transaction
3933
+ // EOAs on Push Chain cannot process UEA_MULTICALL encoded data — they are not contracts
3934
+ // Nonces and gas are managed locally:
3935
+ // - Nonces: avoids stale nonce from estimateGas simulation on Cosmos-EVM
3936
+ // - Gas: avoids estimateGas failures when prior txs in the batch aren't committed yet
3937
+ // Each tx is confirmed before sending the next to ensure state visibility.
1322
3938
  if (Array.isArray(execute.data)) {
1323
- throw new Error('Multicall is not supported on PushChain');
3939
+ const PUSH_CHAIN_GAS_LIMIT = BigInt(500000);
3940
+ const MAX_NONCE_RETRIES = 3;
3941
+ // Fetch nonce once before the batch using 'pending' to include mempool txs
3942
+ let nonce = yield this.pushClient.publicClient.getTransactionCount({
3943
+ address: this.universalSigner.account.address,
3944
+ blockTag: 'pending',
3945
+ });
3946
+ let lastTxHash = '0x';
3947
+ const calls = execute.data;
3948
+ for (let i = 0; i < calls.length; i++) {
3949
+ const call = calls[i];
3950
+ let txSent = false;
3951
+ for (let retry = 0; retry < MAX_NONCE_RETRIES && !txSent; retry++) {
3952
+ try {
3953
+ this.printLog(`sendPushTx — executing multicall operation ${i + 1}/${calls.length} to: ${call.to} (nonce: ${nonce})`);
3954
+ lastTxHash = yield this.pushClient.sendTransaction({
3955
+ to: call.to,
3956
+ data: (call.data || '0x'),
3957
+ value: call.value,
3958
+ signer: this.universalSigner,
3959
+ nonce,
3960
+ gas: PUSH_CHAIN_GAS_LIMIT,
3961
+ });
3962
+ txSent = true;
3963
+ }
3964
+ catch (err) {
3965
+ const msg = (err === null || err === void 0 ? void 0 : err.message) || (err === null || err === void 0 ? void 0 : err.details) || '';
3966
+ if (msg.includes('invalid nonce') || msg.includes('invalid sequence')) {
3967
+ // Re-fetch nonce from pending state and retry
3968
+ this.printLog(`sendPushTx — nonce mismatch on operation ${i + 1}/${calls.length} (retry ${retry + 1}/${MAX_NONCE_RETRIES}), re-fetching nonce`);
3969
+ nonce = yield this.pushClient.publicClient.getTransactionCount({
3970
+ address: this.universalSigner.account.address,
3971
+ blockTag: 'pending',
3972
+ });
3973
+ }
3974
+ else {
3975
+ throw err;
3976
+ }
3977
+ }
3978
+ }
3979
+ if (!txSent) {
3980
+ throw new Error(`sendPushTx — multicall operation ${i + 1}/${calls.length} failed after ${MAX_NONCE_RETRIES} nonce retries`);
3981
+ }
3982
+ // Wait for tx receipt and verify it succeeded before sending next tx
3983
+ const receipt = yield this.pushClient.publicClient.waitForTransactionReceipt({
3984
+ hash: lastTxHash,
3985
+ });
3986
+ if (receipt.status === 'reverted') {
3987
+ throw new Error(`sendPushTx — multicall operation ${i + 1}/${calls.length} reverted (to: ${call.to}, txHash: ${lastTxHash})`);
3988
+ }
3989
+ this.printLog(`sendPushTx — operation ${i + 1}/${calls.length} confirmed in block ${receipt.blockNumber}`);
3990
+ nonce++; // increment locally for next tx
3991
+ }
3992
+ const txResponse = yield this.pushClient.getTransaction(lastTxHash);
3993
+ return yield this.transformToUniversalTxResponse(txResponse, eventBuffer);
1324
3994
  }
1325
3995
  const txHash = yield this.pushClient.sendTransaction({
1326
3996
  to: execute.to,
@@ -1632,7 +4302,7 @@ class Orchestrator {
1632
4302
  try {
1633
4303
  // Attempt to find UniversalTx by searching pcTx entries
1634
4304
  // For now, we'll try to look up by the tx hash
1635
- const utxResponse = yield this.pushClient.getUniversalTxById(txHash);
4305
+ const utxResponse = yield this.pushClient.getUniversalTxByIdV2(txHash);
1636
4306
  if (utxResponse === null || utxResponse === void 0 ? void 0 : utxResponse.universalTx) {
1637
4307
  universalTxData = utxResponse.universalTx;
1638
4308
  }
@@ -1665,6 +4335,442 @@ class Orchestrator {
1665
4335
  return enums_1.CHAIN.PUSH_LOCALNET;
1666
4336
  }
1667
4337
  }
4338
+ // ============================================================================
4339
+ // Outbound Transaction Tracking
4340
+ // ============================================================================
4341
+ /**
4342
+ * Compute UniversalTxId for Push Chain originated outbound transactions.
4343
+ * Formula: keccak256("eip155:{pushChainId}:{pushChainTxHash}")
4344
+ *
4345
+ * @param pushChainTxHash - The Push Chain transaction hash
4346
+ * @returns The UniversalTxId (keccak256 hash)
4347
+ */
4348
+ computeUniversalTxId(pushChainTxHash) {
4349
+ const pushChain = this.getPushChainForNetwork();
4350
+ const pushChainId = chain_1.CHAIN_INFO[pushChain].chainId;
4351
+ const input = `eip155:${pushChainId}:${pushChainTxHash}`;
4352
+ return (0, viem_1.keccak256)((0, viem_1.toBytes)(input));
4353
+ }
4354
+ /**
4355
+ * Extract universalsubTxId from a Push Chain transaction by fetching Cosmos events.
4356
+ * The universalsubTxId is found in the 'outbound_created' Cosmos event (attribute 'utx_id'),
4357
+ * NOT in the EVM event data.
4358
+ *
4359
+ * @param pushChainTxHash - The Push Chain transaction hash
4360
+ * @returns The universalsubTxId or null if not found
4361
+ */
4362
+ extractUniversalSubTxIdFromTx(pushChainTxHash) {
4363
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
4364
+ var _a;
4365
+ this.printLog(`[extractUniversalSubTxIdFromTx] Fetching Cosmos tx for: ${pushChainTxHash}`);
4366
+ try {
4367
+ // Query Cosmos transaction to get events
4368
+ const cosmosTx = yield this.pushClient.getCosmosTx(pushChainTxHash);
4369
+ if (!(cosmosTx === null || cosmosTx === void 0 ? void 0 : cosmosTx.events)) {
4370
+ this.printLog(`[extractUniversalSubTxIdFromTx] No events in Cosmos tx`);
4371
+ return null;
4372
+ }
4373
+ // Find the 'outbound_created' event which contains utx_id
4374
+ for (const event of cosmosTx.events) {
4375
+ if (event.type === 'outbound_created') {
4376
+ // Find the utx_id attribute
4377
+ const utxIdAttr = (_a = event.attributes) === null || _a === void 0 ? void 0 : _a.find((attr) => attr.key === 'utx_id');
4378
+ if (utxIdAttr === null || utxIdAttr === void 0 ? void 0 : utxIdAttr.value) {
4379
+ // The utx_id is stored without 0x prefix in Cosmos events
4380
+ const universalsubTxId = utxIdAttr.value.startsWith('0x')
4381
+ ? utxIdAttr.value
4382
+ : `0x${utxIdAttr.value}`;
4383
+ this.printLog(`[extractUniversalSubTxIdFromTx] Found universalsubTxId from outbound_created event: ${universalsubTxId}`);
4384
+ return universalsubTxId;
4385
+ }
4386
+ }
4387
+ }
4388
+ this.printLog(`[extractUniversalSubTxIdFromTx] No outbound_created event found`);
4389
+ return null;
4390
+ }
4391
+ catch (error) {
4392
+ this.printLog(`[extractUniversalSubTxIdFromTx] Error: ${error instanceof Error ? error.message : String(error)}`);
4393
+ return null;
4394
+ }
4395
+ });
4396
+ }
4397
+ /**
4398
+ * Extract ALL universalSubTxIds from a Push Chain transaction.
4399
+ * For cascaded transactions, a single Push Chain tx may emit multiple
4400
+ * outbound_created events, each with its own utx_id.
4401
+ *
4402
+ * @param pushChainTxHash - The Push Chain transaction hash
4403
+ * @returns Array of universalSubTxIds (may be empty)
4404
+ */
4405
+ extractAllUniversalSubTxIds(pushChainTxHash) {
4406
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
4407
+ var _a;
4408
+ this.printLog(`[extractAllUniversalSubTxIds] Fetching Cosmos tx for: ${pushChainTxHash}`);
4409
+ try {
4410
+ const cosmosTx = yield this.pushClient.getCosmosTx(pushChainTxHash);
4411
+ if (!(cosmosTx === null || cosmosTx === void 0 ? void 0 : cosmosTx.events)) {
4412
+ this.printLog(`[extractAllUniversalSubTxIds] No events in Cosmos tx`);
4413
+ return [];
4414
+ }
4415
+ const subTxIds = [];
4416
+ for (const event of cosmosTx.events) {
4417
+ if (event.type === 'outbound_created') {
4418
+ const utxIdAttr = (_a = event.attributes) === null || _a === void 0 ? void 0 : _a.find((attr) => attr.key === 'utx_id');
4419
+ if (utxIdAttr === null || utxIdAttr === void 0 ? void 0 : utxIdAttr.value) {
4420
+ const id = utxIdAttr.value.startsWith('0x')
4421
+ ? utxIdAttr.value
4422
+ : `0x${utxIdAttr.value}`;
4423
+ subTxIds.push(id);
4424
+ }
4425
+ }
4426
+ }
4427
+ this.printLog(`[extractAllUniversalSubTxIds] Found ${subTxIds.length} sub-tx IDs: ${subTxIds.join(', ')}`);
4428
+ return subTxIds;
4429
+ }
4430
+ catch (error) {
4431
+ this.printLog(`[extractAllUniversalSubTxIds] Error: ${error instanceof Error ? error.message : String(error)}`);
4432
+ return [];
4433
+ }
4434
+ });
4435
+ }
4436
+ /**
4437
+ * Convert CAIP-2 namespace (e.g., "eip155:97") to CHAIN enum
4438
+ *
4439
+ * @param namespace - CAIP-2 chain namespace string
4440
+ * @returns CHAIN enum value or null if not found
4441
+ */
4442
+ chainFromNamespace(namespace) {
4443
+ for (const [chainKey, info] of Object.entries(chain_1.CHAIN_INFO)) {
4444
+ const expected = `${chain_1.VM_NAMESPACE[info.vm]}:${info.chainId}`;
4445
+ if (expected === namespace) {
4446
+ return chainKey;
4447
+ }
4448
+ }
4449
+ return null;
4450
+ }
4451
+ /**
4452
+ * Wait for outbound transaction to complete and return external chain details.
4453
+ * @internal Used by .wait() for outbound routes - not part of public API.
4454
+ * Uses polling with configurable initial wait, interval, and timeout.
4455
+ *
4456
+ * Default strategy: 30s initial wait, then poll every 5s, 120s total timeout.
4457
+ *
4458
+ * @param pushChainTxHash - The Push Chain transaction hash
4459
+ * @param options - Polling configuration options
4460
+ * @returns External chain tx details
4461
+ * @throws Error on timeout
4462
+ */
4463
+ waitForOutboundTx(pushChainTxHash_1) {
4464
+ return tslib_1.__awaiter(this, arguments, void 0, function* (pushChainTxHash, options = {}) {
4465
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
4466
+ const { initialWaitMs = Orchestrator.OUTBOUND_INITIAL_WAIT_MS, pollingIntervalMs = Orchestrator.OUTBOUND_POLL_INTERVAL_MS, timeout = Orchestrator.OUTBOUND_MAX_TIMEOUT_MS, progressHook, _resolvedSubTxId, _expectedDestinationChain, } = options;
4467
+ // Terminal failure states — fail fast instead of polling until timeout
4468
+ const TERMINAL_FAILURE_STATES = new Set([
4469
+ types_1.UniversalTxStatus.OUTBOUND_FAILED,
4470
+ types_1.UniversalTxStatus.PC_EXECUTED_FAILED,
4471
+ types_1.UniversalTxStatus.CANCELED,
4472
+ ]);
4473
+ const startTime = Date.now();
4474
+ this.printLog(`[waitForOutboundTx] Starting | txHash: ${pushChainTxHash} | initialWait: ${initialWaitMs}ms | pollInterval: ${pollingIntervalMs}ms | timeout: ${timeout}ms`);
4475
+ // Emit initial waiting status
4476
+ progressHook === null || progressHook === void 0 ? void 0 : progressHook({ status: 'waiting', elapsed: 0 });
4477
+ // Initial wait before first poll
4478
+ this.printLog(`[waitForOutboundTx] Initial wait of ${initialWaitMs}ms...`);
4479
+ yield new Promise((resolve) => setTimeout(resolve, initialWaitMs));
4480
+ // Start polling
4481
+ this.printLog(`[waitForOutboundTx] Initial wait done. Starting polling. Elapsed: ${Date.now() - startTime}ms`);
4482
+ progressHook === null || progressHook === void 0 ? void 0 : progressHook({ status: 'polling', elapsed: Date.now() - startTime });
4483
+ // Cache the universalSubTxId after first extraction to avoid redundant receipt fetches.
4484
+ // If a pre-resolved ID was provided (cascade per-hop tracking), use it directly.
4485
+ let cachedUniversalSubTxId = _resolvedSubTxId;
4486
+ let pollCount = 0;
4487
+ while (Date.now() - startTime < timeout) {
4488
+ pollCount++;
4489
+ const pollStart = Date.now();
4490
+ this.printLog(`[waitForOutboundTx] Poll #${pollCount} | Elapsed: ${pollStart - startTime}ms / ${timeout}ms`);
4491
+ // First poll: extract the ID. Subsequent polls: reuse cached ID.
4492
+ if (!cachedUniversalSubTxId) {
4493
+ cachedUniversalSubTxId = (_a = (yield this.extractUniversalSubTxIdFromTx(pushChainTxHash))) !== null && _a !== void 0 ? _a : undefined;
4494
+ if (!cachedUniversalSubTxId) {
4495
+ cachedUniversalSubTxId = this.computeUniversalTxId(pushChainTxHash);
4496
+ }
4497
+ this.printLog(`[waitForOutboundTx] Extracted & cached universalSubTxId: ${cachedUniversalSubTxId}`);
4498
+ }
4499
+ // Query with cached ID
4500
+ const queryId = cachedUniversalSubTxId.startsWith('0x')
4501
+ ? cachedUniversalSubTxId.slice(2)
4502
+ : cachedUniversalSubTxId;
4503
+ try {
4504
+ const utxResponse = yield this.pushClient.getUniversalTxByIdV2(queryId);
4505
+ const statusNum = (_b = utxResponse === null || utxResponse === void 0 ? void 0 : utxResponse.universalTx) === null || _b === void 0 ? void 0 : _b.universalStatus;
4506
+ const statusName = (_c = types_1.UniversalTxStatus[statusNum]) !== null && _c !== void 0 ? _c : statusNum;
4507
+ const outbounds = ((_d = utxResponse === null || utxResponse === void 0 ? void 0 : utxResponse.universalTx) === null || _d === void 0 ? void 0 : _d.outboundTx) || [];
4508
+ this.printLog(`[waitForOutboundTx] Poll #${pollCount} | status: ${statusNum} (${statusName}) | outboundTx count: ${outbounds.length} | first txHash: '${((_f = (_e = outbounds[0]) === null || _e === void 0 ? void 0 : _e.observedTx) === null || _f === void 0 ? void 0 : _f.txHash) || ''}' | first dest: '${((_g = outbounds[0]) === null || _g === void 0 ? void 0 : _g.destinationChain) || ''}'`);
4509
+ // Check for terminal failure states — fail fast
4510
+ if (TERMINAL_FAILURE_STATES.has(statusNum)) {
4511
+ this.printLog(`[waitForOutboundTx] Terminal failure state: ${statusName}`);
4512
+ progressHook === null || progressHook === void 0 ? void 0 : progressHook({ status: 'failed', elapsed: Date.now() - startTime });
4513
+ throw new Error(`Outbound transaction failed with status ${statusName}. Push Chain TX: ${pushChainTxHash}.`);
4514
+ }
4515
+ // Iterate V2 outbound array
4516
+ for (const ob of outbounds) {
4517
+ // Fail fast on per-outbound REVERTED status
4518
+ if (ob.outboundStatus === types_2.OutboundStatus.REVERTED) {
4519
+ this.printLog(`[waitForOutboundTx] Outbound to ${ob.destinationChain} REVERTED`);
4520
+ progressHook === null || progressHook === void 0 ? void 0 : progressHook({ status: 'failed', elapsed: Date.now() - startTime });
4521
+ throw new Error(`Outbound to ${ob.destinationChain} reverted: ${((_h = ob.observedTx) === null || _h === void 0 ? void 0 : _h.errorMsg) || 'Unknown'}. Push Chain TX: ${pushChainTxHash}.`);
4522
+ }
4523
+ if ((_j = ob.observedTx) === null || _j === void 0 ? void 0 : _j.txHash) {
4524
+ // If a destination chain filter is set, skip outbound entries that don't match
4525
+ if (_expectedDestinationChain && ob.destinationChain !== _expectedDestinationChain) {
4526
+ this.printLog(`[waitForOutboundTx] Poll #${pollCount} | outbound chain '${ob.destinationChain}' does not match expected '${_expectedDestinationChain}', skipping`);
4527
+ continue;
4528
+ }
4529
+ const chain = this.chainFromNamespace(ob.destinationChain);
4530
+ if (chain) {
4531
+ const explorerBaseUrl = (_k = chain_1.CHAIN_INFO[chain]) === null || _k === void 0 ? void 0 : _k.explorerUrl;
4532
+ const isSvm = ((_l = chain_1.CHAIN_INFO[chain]) === null || _l === void 0 ? void 0 : _l.vm) === enums_1.VM.SVM;
4533
+ // For SVM chains, convert hex txHash to base58 and append cluster param
4534
+ let displayTxHash = ob.observedTx.txHash;
4535
+ let explorerUrl = '';
4536
+ if (isSvm && ob.observedTx.txHash.startsWith('0x')) {
4537
+ const bytes = new Uint8Array(Buffer.from(ob.observedTx.txHash.slice(2), 'hex'));
4538
+ displayTxHash = anchor_1.utils.bytes.bs58.encode(bytes);
4539
+ const cluster = chain === enums_1.CHAIN.SOLANA_DEVNET ? '?cluster=devnet'
4540
+ : chain === enums_1.CHAIN.SOLANA_TESTNET ? '?cluster=testnet' : '';
4541
+ explorerUrl = explorerBaseUrl ? `${explorerBaseUrl}/tx/${displayTxHash}${cluster}` : '';
4542
+ }
4543
+ else {
4544
+ explorerUrl = explorerBaseUrl ? `${explorerBaseUrl}/tx/${ob.observedTx.txHash}` : '';
4545
+ }
4546
+ const details = {
4547
+ externalTxHash: ob.observedTx.txHash,
4548
+ destinationChain: chain,
4549
+ explorerUrl,
4550
+ recipient: ob.recipient,
4551
+ amount: ob.amount,
4552
+ assetAddr: ob.externalAssetAddr,
4553
+ };
4554
+ this.printLog(`[waitForOutboundTx] FOUND on poll #${pollCount} | elapsed: ${Date.now() - startTime}ms | externalTxHash: ${details.externalTxHash}`);
4555
+ progressHook === null || progressHook === void 0 ? void 0 : progressHook({ status: 'found', elapsed: Date.now() - startTime });
4556
+ return details;
4557
+ }
4558
+ }
4559
+ }
4560
+ }
4561
+ catch (error) {
4562
+ // Re-throw terminal failure and reverted errors
4563
+ if (error instanceof Error && (error.message.includes('Outbound transaction failed') || error.message.includes('reverted'))) {
4564
+ throw error;
4565
+ }
4566
+ this.printLog(`[waitForOutboundTx] Poll #${pollCount} ERROR: ${error instanceof Error ? error.message : String(error)}`);
4567
+ }
4568
+ this.printLog(`[waitForOutboundTx] Poll #${pollCount} not ready yet (${Date.now() - pollStart}ms). Waiting ${pollingIntervalMs}ms...`);
4569
+ // Wait before next poll
4570
+ yield new Promise((resolve) => setTimeout(resolve, pollingIntervalMs));
4571
+ }
4572
+ this.printLog(`[waitForOutboundTx] TIMEOUT after ${pollCount} polls | elapsed: ${Date.now() - startTime}ms`);
4573
+ progressHook === null || progressHook === void 0 ? void 0 : progressHook({ status: 'timeout', elapsed: Date.now() - startTime });
4574
+ throw new Error(`Timeout waiting for outbound transaction. Push Chain TX: ${pushChainTxHash}. ` +
4575
+ `Timeout: ${timeout}ms. The relay may still be processing.`);
4576
+ });
4577
+ }
4578
+ /**
4579
+ * Tracks ALL outbound transactions for a cascade with multiple outbound hops
4580
+ * (e.g., BNB + Solana). Uses V2 API which returns outboundTx[] with per-outbound
4581
+ * status tracking, matching each outbound to the correct hop by destination chain.
4582
+ */
4583
+ waitForAllOutboundTxsV2(pushChainTxHash, outboundHops, options) {
4584
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
4585
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
4586
+ const { initialWaitMs, pollingIntervalMs, timeout, progressHook } = options;
4587
+ const startTime = Date.now();
4588
+ // Build a map: CAIP-2 namespace -> hop(s) for matching outbound entries
4589
+ const chainToHops = new Map();
4590
+ for (const hop of outboundHops) {
4591
+ const chainInfo = chain_1.CHAIN_INFO[hop.executionChain];
4592
+ if (chainInfo) {
4593
+ const namespace = `${chain_1.VM_NAMESPACE[chainInfo.vm]}:${chainInfo.chainId}`;
4594
+ const existing = chainToHops.get(namespace) || [];
4595
+ existing.push(hop);
4596
+ chainToHops.set(namespace, existing);
4597
+ }
4598
+ }
4599
+ const expectedChains = [...chainToHops.keys()];
4600
+ this.printLog(`[waitForAllOutboundTxsV2] Starting | txHash: ${pushChainTxHash} | expectedChains: ${expectedChains.join(', ')} | timeout: ${timeout}ms`);
4601
+ // Emit initial waiting status for all outbound hops
4602
+ for (const hop of outboundHops) {
4603
+ progressHook === null || progressHook === void 0 ? void 0 : progressHook({
4604
+ hopIndex: hop.hopIndex,
4605
+ route: hop.route,
4606
+ chain: hop.executionChain,
4607
+ status: 'waiting',
4608
+ });
4609
+ }
4610
+ // Initial wait before first poll
4611
+ const waitMs = Math.min(initialWaitMs, timeout);
4612
+ this.printLog(`[waitForAllOutboundTxsV2] Initial wait of ${waitMs}ms...`);
4613
+ yield new Promise((resolve) => setTimeout(resolve, waitMs));
4614
+ // Emit polling status for all hops
4615
+ for (const hop of outboundHops) {
4616
+ progressHook === null || progressHook === void 0 ? void 0 : progressHook({
4617
+ hopIndex: hop.hopIndex,
4618
+ route: hop.route,
4619
+ chain: hop.executionChain,
4620
+ status: 'polling',
4621
+ });
4622
+ }
4623
+ // Extract sub-tx ID for V2 query
4624
+ let cachedQueryId;
4625
+ let pollCount = 0;
4626
+ let consecutiveErrors = 0;
4627
+ const MAX_CONSECUTIVE_ERRORS = 10;
4628
+ while (Date.now() - startTime < timeout) {
4629
+ pollCount++;
4630
+ const elapsed = Date.now() - startTime;
4631
+ this.printLog(`[waitForAllOutboundTxsV2] Poll #${pollCount} | Elapsed: ${elapsed}ms / ${timeout}ms`);
4632
+ // Resolve query ID on first poll
4633
+ if (!cachedQueryId) {
4634
+ const allSubTxIds = yield this.extractAllUniversalSubTxIds(pushChainTxHash);
4635
+ const subTxId = allSubTxIds.length > 0 ? allSubTxIds[0] : this.computeUniversalTxId(pushChainTxHash);
4636
+ cachedQueryId = subTxId.startsWith('0x') ? subTxId.slice(2) : subTxId;
4637
+ this.printLog(`[waitForAllOutboundTxsV2] Resolved queryId: ${cachedQueryId}`);
4638
+ }
4639
+ try {
4640
+ const v2Response = yield this.pushClient.getUniversalTxByIdV2(cachedQueryId);
4641
+ consecutiveErrors = 0; // Reset on successful RPC call
4642
+ const utx = v2Response === null || v2Response === void 0 ? void 0 : v2Response.universalTx;
4643
+ const statusNum = utx === null || utx === void 0 ? void 0 : utx.universalStatus;
4644
+ const statusName = (_a = types_1.UniversalTxStatus[statusNum]) !== null && _a !== void 0 ? _a : statusNum;
4645
+ this.printLog(`[waitForAllOutboundTxsV2] Poll #${pollCount} | status: ${statusNum} (${statusName}) | outboundTx count: ${(_c = (_b = utx === null || utx === void 0 ? void 0 : utx.outboundTx) === null || _b === void 0 ? void 0 : _b.length) !== null && _c !== void 0 ? _c : 0}`);
4646
+ if ((_d = utx === null || utx === void 0 ? void 0 : utx.outboundTx) === null || _d === void 0 ? void 0 : _d.length) {
4647
+ for (const ob of utx.outboundTx) {
4648
+ const destChain = ob.destinationChain;
4649
+ const hopsForChain = chainToHops.get(destChain);
4650
+ if (!hopsForChain)
4651
+ continue;
4652
+ const unconfirmedForChain = hopsForChain.filter((h) => h.status !== 'confirmed' && h.status !== 'failed');
4653
+ if (unconfirmedForChain.length === 0)
4654
+ continue;
4655
+ // Fail fast on per-outbound REVERTED
4656
+ if (ob.outboundStatus === types_2.OutboundStatus.REVERTED) {
4657
+ for (const hop of unconfirmedForChain) {
4658
+ hop.status = 'failed';
4659
+ this.printLog(`[waitForAllOutboundTxsV2] Outbound to ${destChain} REVERTED | hop ${hop.hopIndex} | error: ${((_e = ob.observedTx) === null || _e === void 0 ? void 0 : _e.errorMsg) || 'Unknown'}`);
4660
+ progressHook === null || progressHook === void 0 ? void 0 : progressHook({
4661
+ hopIndex: hop.hopIndex,
4662
+ route: hop.route,
4663
+ chain: hop.executionChain,
4664
+ status: 'failed',
4665
+ });
4666
+ }
4667
+ return { success: false, failedAt: unconfirmedForChain[0].hopIndex };
4668
+ }
4669
+ // Check for OBSERVED with txHash
4670
+ const externalTxHash = (_f = ob.observedTx) === null || _f === void 0 ? void 0 : _f.txHash;
4671
+ if (externalTxHash && (ob.outboundStatus === types_2.OutboundStatus.OBSERVED || ob.outboundStatus === 0)) {
4672
+ const chain = this.chainFromNamespace(destChain);
4673
+ let explorerUrl = '';
4674
+ if (chain && externalTxHash) {
4675
+ const explorerBaseUrl = (_g = chain_1.CHAIN_INFO[chain]) === null || _g === void 0 ? void 0 : _g.explorerUrl;
4676
+ const isSvm = ((_h = chain_1.CHAIN_INFO[chain]) === null || _h === void 0 ? void 0 : _h.vm) === enums_1.VM.SVM;
4677
+ if (isSvm && externalTxHash.startsWith('0x')) {
4678
+ const bytes = new Uint8Array(Buffer.from(externalTxHash.slice(2), 'hex'));
4679
+ const base58Hash = anchor_1.utils.bytes.bs58.encode(bytes);
4680
+ const cluster = chain === enums_1.CHAIN.SOLANA_DEVNET ? '?cluster=devnet'
4681
+ : chain === enums_1.CHAIN.SOLANA_TESTNET ? '?cluster=testnet' : '';
4682
+ explorerUrl = explorerBaseUrl ? `${explorerBaseUrl}/tx/${base58Hash}${cluster}` : '';
4683
+ }
4684
+ else {
4685
+ explorerUrl = explorerBaseUrl ? `${explorerBaseUrl}/tx/${externalTxHash}` : '';
4686
+ }
4687
+ }
4688
+ for (const hop of unconfirmedForChain) {
4689
+ hop.status = 'confirmed';
4690
+ hop.txHash = externalTxHash;
4691
+ hop.outboundDetails = {
4692
+ externalTxHash,
4693
+ destinationChain: chain || hop.executionChain,
4694
+ explorerUrl,
4695
+ recipient: ob.recipient,
4696
+ amount: ob.amount,
4697
+ assetAddr: ob.externalAssetAddr,
4698
+ };
4699
+ this.printLog(`[waitForAllOutboundTxsV2] FOUND outbound for ${destChain} | hop ${hop.hopIndex} | externalTxHash: ${externalTxHash}`);
4700
+ progressHook === null || progressHook === void 0 ? void 0 : progressHook({
4701
+ hopIndex: hop.hopIndex,
4702
+ route: hop.route,
4703
+ chain: hop.executionChain,
4704
+ status: 'confirmed',
4705
+ txHash: externalTxHash,
4706
+ });
4707
+ }
4708
+ }
4709
+ }
4710
+ }
4711
+ // Check if all hops are now confirmed
4712
+ if (outboundHops.every((h) => h.status === 'confirmed')) {
4713
+ this.printLog(`[waitForAllOutboundTxsV2] All ${outboundHops.length} hops confirmed via V2`);
4714
+ return { success: true };
4715
+ }
4716
+ // If PC_EXECUTED_SUCCESS but some hops still unresolved, check outbound status.
4717
+ // Only auto-confirm if there are NO pending outbound txs (status=1 with empty hash).
4718
+ // Pending outbounds are still in flight on the relay — keep polling for their hashes.
4719
+ if (statusNum === types_1.UniversalTxStatus.PC_EXECUTED_SUCCESS) {
4720
+ const stillUnresolved = outboundHops.filter((h) => h.status !== 'confirmed');
4721
+ if (stillUnresolved.length > 0) {
4722
+ const hasPendingOutbound = (_j = utx === null || utx === void 0 ? void 0 : utx.outboundTx) === null || _j === void 0 ? void 0 : _j.some((ob) => {
4723
+ var _a;
4724
+ return ob.outboundStatus === types_2.OutboundStatus.PENDING &&
4725
+ (!((_a = ob.observedTx) === null || _a === void 0 ? void 0 : _a.txHash) || ob.observedTx.txHash === 'EMPTY');
4726
+ });
4727
+ if (!hasPendingOutbound) {
4728
+ // No pending outbounds — safe to auto-confirm remaining hops
4729
+ for (const hop of stillUnresolved) {
4730
+ hop.status = 'confirmed';
4731
+ this.printLog(`[waitForAllOutboundTxsV2] Auto-confirmed hop ${hop.hopIndex} (${hop.executionChain}) based on PC_EXECUTED_SUCCESS (no pending outbounds)`);
4732
+ progressHook === null || progressHook === void 0 ? void 0 : progressHook({
4733
+ hopIndex: hop.hopIndex,
4734
+ route: hop.route,
4735
+ chain: hop.executionChain,
4736
+ status: 'confirmed',
4737
+ });
4738
+ }
4739
+ return { success: true };
4740
+ }
4741
+ // Pending outbound txs still in flight — continue polling
4742
+ this.printLog(`[waitForAllOutboundTxsV2] ${stillUnresolved.length} hop(s) unresolved, pending outbound txs in flight — continuing to poll`);
4743
+ }
4744
+ }
4745
+ }
4746
+ catch (error) {
4747
+ consecutiveErrors++;
4748
+ this.printLog(`[waitForAllOutboundTxsV2] Poll #${pollCount} ERROR (${consecutiveErrors}/${MAX_CONSECUTIVE_ERRORS}): ${error instanceof Error ? error.message : String(error)}`);
4749
+ if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
4750
+ this.printLog(`[waitForAllOutboundTxsV2] Aborting — ${MAX_CONSECUTIVE_ERRORS} consecutive RPC errors`);
4751
+ break;
4752
+ }
4753
+ }
4754
+ yield new Promise((resolve) => setTimeout(resolve, pollingIntervalMs));
4755
+ }
4756
+ // Timeout: fail any unresolved hops
4757
+ const timedOutHops = outboundHops.filter((h) => h.status !== 'confirmed');
4758
+ if (timedOutHops.length > 0) {
4759
+ this.printLog(`[waitForAllOutboundTxsV2] TIMEOUT after ${pollCount} polls | ${timedOutHops.length} hop(s) unresolved`);
4760
+ for (const hop of timedOutHops) {
4761
+ hop.status = 'failed';
4762
+ progressHook === null || progressHook === void 0 ? void 0 : progressHook({
4763
+ hopIndex: hop.hopIndex,
4764
+ route: hop.route,
4765
+ chain: hop.executionChain,
4766
+ status: 'failed',
4767
+ });
4768
+ }
4769
+ return { success: false, failedAt: timedOutHops[0].hopIndex };
4770
+ }
4771
+ return { success: true };
4772
+ });
4773
+ }
1668
4774
  /**
1669
4775
  * Computes UEA for given UniversalAccount
1670
4776
  * @dev - This fn calls a view fn of Factory Contract
@@ -1771,38 +4877,34 @@ class Orchestrator {
1771
4877
  throw new Error('Token address is required for bridge path');
1772
4878
  }
1773
4879
  const isNative = mechanism === 'native' || execute.funds.token.symbol === 'SOL';
1774
- const revertSvm2 = {
1775
- fundRecipient: userPk,
1776
- revertMsg: Buffer.from([]),
1777
- };
4880
+ const { feeVaultPda, protocolFeeLamports } = yield this._getSvmProtocolFee(svmClient, programId);
1778
4881
  // Compute signature for universal payload on SVM
1779
4882
  const ueaAddressSvm = this.computeUEAOffchain();
1780
4883
  const ueaVersion = yield this.fetchUEAVersion();
1781
4884
  const svmSignature = yield this.signUniversalPayload(universalPayload, ueaAddressSvm, ueaVersion);
1782
4885
  if (isNative) {
1783
4886
  // Native SOL as bridge + gas
1784
- const [whitelistPdaLocal] = web3_js_1.PublicKey.findProgramAddressSync([(0, viem_1.stringToBytes)('whitelist')], programId);
1785
4887
  const [tokenRateLimitPda] = web3_js_1.PublicKey.findProgramAddressSync([(0, viem_1.stringToBytes)('rate_limit'), web3_js_1.PublicKey.default.toBuffer()], programId);
1786
4888
  const nativeReq = this._buildSvmUniversalTxRequest({
1787
4889
  recipient: Array.from(Buffer.alloc(20, 0)),
1788
4890
  token: web3_js_1.PublicKey.default,
1789
4891
  amount: bridgeAmount,
1790
4892
  payload: Uint8Array.from(this.encodeUniversalPayloadSvm(universalPayload)),
1791
- fundRecipient: userPk,
4893
+ revertRecipient: userPk,
1792
4894
  signatureData: svmSignature,
1793
4895
  });
1794
4896
  return yield svmClient.writeContract({
1795
4897
  abi: abi_1.SVM_GATEWAY_IDL,
1796
4898
  address: programId.toBase58(),
1797
4899
  functionName: 'sendUniversalTx',
1798
- args: [nativeReq, nativeAmount],
4900
+ args: [nativeReq, nativeAmount + protocolFeeLamports],
1799
4901
  signer: this.universalSigner,
1800
4902
  accounts: {
1801
4903
  config: configPda,
1802
4904
  vault: vaultPda,
1803
- tokenWhitelist: whitelistPdaLocal,
1804
- userTokenAccount: vaultPda, // dummy for native SOL
1805
- gatewayTokenAccount: vaultPda, // dummy for native SOL
4905
+ feeVault: feeVaultPda,
4906
+ userTokenAccount: vaultPda,
4907
+ gatewayTokenAccount: vaultPda,
1806
4908
  user: userPk,
1807
4909
  priceUpdate: priceUpdatePk,
1808
4910
  tokenProgram: new web3_js_1.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'),
@@ -1822,26 +4924,25 @@ class Orchestrator {
1822
4924
  const ASSOCIATED_TOKEN_PROGRAM_ID = new web3_js_1.PublicKey('ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL');
1823
4925
  const userAta = web3_js_1.PublicKey.findProgramAddressSync([userPk.toBuffer(), TOKEN_PROGRAM_ID.toBuffer(), mintPk.toBuffer()], ASSOCIATED_TOKEN_PROGRAM_ID)[0];
1824
4926
  const vaultAta = web3_js_1.PublicKey.findProgramAddressSync([vaultPda.toBuffer(), TOKEN_PROGRAM_ID.toBuffer(), mintPk.toBuffer()], ASSOCIATED_TOKEN_PROGRAM_ID)[0];
1825
- const [whitelistPdaLocal] = web3_js_1.PublicKey.findProgramAddressSync([(0, viem_1.stringToBytes)('whitelist')], programId);
1826
4927
  const [tokenRateLimitPda] = web3_js_1.PublicKey.findProgramAddressSync([(0, viem_1.stringToBytes)('rate_limit'), mintPk.toBuffer()], programId);
1827
4928
  const splReq = this._buildSvmUniversalTxRequest({
1828
4929
  recipient: Array.from(Buffer.alloc(20, 0)),
1829
4930
  token: mintPk,
1830
4931
  amount: bridgeAmount,
1831
4932
  payload: Uint8Array.from(this.encodeUniversalPayloadSvm(universalPayload)),
1832
- fundRecipient: userPk,
4933
+ revertRecipient: userPk,
1833
4934
  signatureData: svmSignature,
1834
4935
  });
1835
4936
  return yield svmClient.writeContract({
1836
4937
  abi: abi_1.SVM_GATEWAY_IDL,
1837
4938
  address: programId.toBase58(),
1838
4939
  functionName: 'sendUniversalTx',
1839
- args: [splReq, nativeAmount],
4940
+ args: [splReq, nativeAmount + protocolFeeLamports],
1840
4941
  signer: this.universalSigner,
1841
4942
  accounts: {
1842
4943
  config: configPda,
1843
4944
  vault: vaultPda,
1844
- tokenWhitelist: whitelistPdaLocal,
4945
+ feeVault: feeVaultPda,
1845
4946
  userTokenAccount: userAta,
1846
4947
  gatewayTokenAccount: vaultAta,
1847
4948
  user: userPk,
@@ -2563,6 +5664,7 @@ class Orchestrator {
2563
5664
  // 6. Utilities
2564
5665
  wait: () => tslib_1.__awaiter(this, void 0, void 0, function* () {
2565
5666
  // Use trackTransaction with registered hook if available
5667
+ let baseReceipt;
2566
5668
  if (registeredProgressHook) {
2567
5669
  const trackedResponse = yield this.trackTransaction(tx.hash, {
2568
5670
  waitForCompletion: true,
@@ -2570,10 +5672,31 @@ class Orchestrator {
2570
5672
  });
2571
5673
  // Get receipt from the tracked response
2572
5674
  const receipt = yield tx.wait();
2573
- return this.transformToUniversalTxReceipt(receipt, trackedResponse);
5675
+ baseReceipt = this.transformToUniversalTxReceipt(receipt, trackedResponse);
5676
+ }
5677
+ else {
5678
+ const receipt = yield tx.wait();
5679
+ baseReceipt = this.transformToUniversalTxReceipt(receipt, universalTxResponse);
2574
5680
  }
2575
- const receipt = yield tx.wait();
2576
- return this.transformToUniversalTxReceipt(receipt, universalTxResponse);
5681
+ // If outbound route (Route 2: UOA → CEA, Route 3: CEA → Push), poll for external chain details
5682
+ if (universalTxResponse.route === route_detector_1.TransactionRoute.UOA_TO_CEA ||
5683
+ universalTxResponse.route === route_detector_1.TransactionRoute.CEA_TO_PUSH) {
5684
+ try {
5685
+ const outboundDetails = yield this.waitForOutboundTx(tx.hash, {
5686
+ initialWaitMs: Orchestrator.OUTBOUND_INITIAL_WAIT_MS,
5687
+ pollingIntervalMs: Orchestrator.OUTBOUND_POLL_INTERVAL_MS,
5688
+ timeout: Orchestrator.OUTBOUND_MAX_TIMEOUT_MS,
5689
+ });
5690
+ // Merge external chain details into receipt
5691
+ baseReceipt = Object.assign(Object.assign({}, baseReceipt), { externalTxHash: outboundDetails.externalTxHash, externalChain: outboundDetails.destinationChain, externalExplorerUrl: outboundDetails.explorerUrl, externalRecipient: outboundDetails.recipient, externalAmount: outboundDetails.amount, externalAssetAddr: outboundDetails.assetAddr });
5692
+ }
5693
+ catch (error) {
5694
+ // Outbound polling timed out - return partial receipt (don't throw)
5695
+ // Push Chain tx succeeded, external tracking can be retried later
5696
+ this.printLog(`[wait] External chain tracking failed: ${error instanceof Error ? error.message : String(error)}`);
5697
+ }
5698
+ }
5699
+ return baseReceipt;
2577
5700
  }),
2578
5701
  progressHook: (callback) => {
2579
5702
  registeredProgressHook = callback;
@@ -2759,12 +5882,12 @@ class Orchestrator {
2759
5882
  try {
2760
5883
  const universalTxResp = yield this.pushClient.getUniversalTxById(idHex);
2761
5884
  universalTxObj = universalTxResp === null || universalTxResp === void 0 ? void 0 : universalTxResp.universalTx;
2762
- if (universalTxObj)
5885
+ if (universalTxObj) {
2763
5886
  break;
5887
+ }
2764
5888
  }
2765
5889
  catch (error) {
2766
5890
  // ignore and retry
2767
- // console.log(error);
2768
5891
  }
2769
5892
  // Linear delay for first N attempts, then exponential backoff
2770
5893
  let delay;
@@ -2780,7 +5903,7 @@ class Orchestrator {
2780
5903
  }
2781
5904
  return universalTxObj;
2782
5905
  }
2783
- catch (_d) {
5906
+ catch (err) {
2784
5907
  return undefined;
2785
5908
  }
2786
5909
  });
@@ -3018,4 +6141,8 @@ class Orchestrator {
3018
6141
  }
3019
6142
  }
3020
6143
  exports.Orchestrator = Orchestrator;
6144
+ // Outbound sync configuration constants
6145
+ Orchestrator.OUTBOUND_INITIAL_WAIT_MS = 30000; // 30s
6146
+ Orchestrator.OUTBOUND_POLL_INTERVAL_MS = 5000; // 5s
6147
+ Orchestrator.OUTBOUND_MAX_TIMEOUT_MS = 180000; // 180s (3 min)
3021
6148
  //# sourceMappingURL=orchestrator.js.map