@pushchain/core 5.1.18 → 6.0.1

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 (57) hide show
  1. package/package.json +1 -1
  2. package/src/lib/constants/abi/cea.evm.d.ts +52 -0
  3. package/src/lib/constants/abi/cea.evm.js +66 -1
  4. package/src/lib/constants/abi/prc20.evm.d.ts +117 -11
  5. package/src/lib/constants/abi/prc20.evm.js +38 -11
  6. package/src/lib/constants/abi/uea.evm.js +41 -0
  7. package/src/lib/constants/abi/universalGatewayPC.evm.d.ts +118 -2
  8. package/src/lib/constants/abi/universalGatewayPC.evm.js +41 -2
  9. package/src/lib/constants/abi/universalGatewayV0.json +1 -1
  10. package/src/lib/constants/chain.d.ts +2 -0
  11. package/src/lib/constants/chain.js +18 -14
  12. package/src/lib/constants/index.d.ts +1 -1
  13. package/src/lib/constants/tokens.d.ts +10 -1
  14. package/src/lib/constants/tokens.js +5 -2
  15. package/src/lib/formatters.d.ts +6 -0
  16. package/src/lib/formatters.js +14 -0
  17. package/src/lib/index.d.ts +2 -0
  18. package/src/lib/index.js +7 -1
  19. package/src/lib/orchestrator/internals/cascade.d.ts +3 -1
  20. package/src/lib/orchestrator/internals/cascade.js +189 -103
  21. package/src/lib/orchestrator/internals/errors.js +1 -1
  22. package/src/lib/orchestrator/internals/execute-funds-only.js +3 -2
  23. package/src/lib/orchestrator/internals/execute-funds-payload.js +2 -1
  24. package/src/lib/orchestrator/internals/execute-standard.js +4 -4
  25. package/src/lib/orchestrator/internals/gas-calculator.d.ts +1 -0
  26. package/src/lib/orchestrator/internals/gas-calculator.js +13 -11
  27. package/src/lib/orchestrator/internals/helpers.js +2 -0
  28. package/src/lib/orchestrator/internals/inbound-tracker.js +4 -2
  29. package/src/lib/orchestrator/internals/outbound-sync.js +73 -60
  30. package/src/lib/orchestrator/internals/pc-usd-oracle.d.ts +1 -1
  31. package/src/lib/orchestrator/internals/pc-usd-oracle.js +4 -3
  32. package/src/lib/orchestrator/internals/preflight.d.ts +8 -5
  33. package/src/lib/orchestrator/internals/preflight.js +35 -21
  34. package/src/lib/orchestrator/internals/push-chain-tx.js +5 -1
  35. package/src/lib/orchestrator/internals/route-handlers.js +175 -202
  36. package/src/lib/orchestrator/internals/signing.js +7 -2
  37. package/src/lib/orchestrator/internals/svm-helpers.js +3 -3
  38. package/src/lib/orchestrator/internals/svm-rent.d.ts +42 -9
  39. package/src/lib/orchestrator/internals/svm-rent.js +157 -14
  40. package/src/lib/orchestrator/internals/tx-transformer.js +5 -2
  41. package/src/lib/orchestrator/max-pc-for-gas.d.ts +60 -0
  42. package/src/lib/orchestrator/max-pc-for-gas.js +66 -0
  43. package/src/lib/orchestrator/orchestrator.d.ts +6 -6
  44. package/src/lib/orchestrator/orchestrator.js +10 -3
  45. package/src/lib/orchestrator/orchestrator.types.d.ts +48 -10
  46. package/src/lib/orchestrator/payload-builders.d.ts +7 -6
  47. package/src/lib/orchestrator/payload-builders.js +19 -7
  48. package/src/lib/progress-hook/progress-hook.js +59 -27
  49. package/src/lib/progress-hook/progress-hook.types.d.ts +3 -3
  50. package/src/lib/progress-hook/progress-hook.types.js +4 -2
  51. package/src/lib/push-chain/push-chain.d.ts +10 -4
  52. package/src/lib/push-chain/push-chain.js +2 -2
  53. package/src/lib/universal-tx-detector/events.d.ts +3 -1
  54. package/src/lib/universal-tx-detector/events.js +3 -1
  55. package/src/lib/universal-tx-detector/svm-events.d.ts +1 -1
  56. package/src/lib/universal-tx-detector/svm-events.js +1 -1
  57. package/src/lib/utils.js +1 -1
@@ -137,13 +137,14 @@ function buildHopDescriptor(ctx, params, route, ueaAddress) {
137
137
  params,
138
138
  route: routeStr,
139
139
  gasLimit,
140
+ maxPCForGas: (_c = params.maxPCForGas) !== null && _c !== void 0 ? _c : BigInt(0),
140
141
  ueaAddress,
141
142
  revertRecipient: ueaAddress,
142
143
  };
143
144
  switch (route) {
144
145
  case route_detector_1.TransactionRoute.UOA_TO_PUSH: {
145
146
  // Route 1: Build Push Chain multicalls
146
- const seedAmount = (_c = params.value) !== null && _c !== void 0 ? _c : BigInt(0);
147
+ const seedAmount = (_d = params.value) !== null && _d !== void 0 ? _d : BigInt(0);
147
148
  if (isValueOnlyNativeSeedToOwnUea(params, ueaAddress)) {
148
149
  return Object.assign(Object.assign({}, baseDescriptor), { pushMulticalls: [], nativeSeedOnly: true, nativeSeedAmount: seedAmount });
149
150
  }
@@ -168,7 +169,7 @@ function buildHopDescriptor(ctx, params, route, ueaAddress) {
168
169
  });
169
170
  let prc20Token = selectors_1.ZERO_ADDRESS;
170
171
  let burnAmount = BigInt(0);
171
- if ((_d = params.funds) === null || _d === void 0 ? void 0 : _d.amount) {
172
+ if ((_e = params.funds) === null || _e === void 0 ? void 0 : _e.amount) {
172
173
  const token = params.funds.token;
173
174
  if (token) {
174
175
  prc20Token = push_chain_1.PushChain.utils.tokens.getPRC20Address(token).address;
@@ -190,35 +191,46 @@ function buildHopDescriptor(ctx, params, route, ueaAddress) {
190
191
  }
191
192
  let gasToken = selectors_1.ZERO_ADDRESS;
192
193
  let gasFee = BigInt(0);
194
+ let gasPrice = BigInt(0);
193
195
  let sizing;
194
- // Conditional CEA-ATA rent bump (SPL-only). Mirrors single-route
195
- // executeUoaToCeaSvm path. See svm-rent.ts for rationale.
196
- const splMintBase58 = burnAmount > BigInt(0)
197
- ? (_f = (_e = params.funds) === null || _e === void 0 ? void 0 : _e.token) === null || _f === void 0 ? void 0 : _f.address
196
+ // SVM finalize budget: compare the quoted gasFee in lamports against
197
+ // the gateway finalize minimum, then re-query with a bumped gasLimit
198
+ // if the default quote is too low.
199
+ const svmFundsToken = burnAmount > BigInt(0)
200
+ ? (_f = params.funds) === null || _f === void 0 ? void 0 : _f.token
198
201
  : undefined;
199
- let effectiveGasLimit = yield (0, svm_rent_1.maybeBumpForCeaAtaRent)({
200
- ctx,
201
- ueaAddress,
202
- targetChain,
203
- splMintBase58,
204
- burnAmount,
205
- effectiveGasLimit: gasLimit,
206
- });
202
+ const splMintBase58 = (svmFundsToken === null || svmFundsToken === void 0 ? void 0 : svmFundsToken.mechanism) === 'approve'
203
+ ? svmFundsToken.address
204
+ : undefined;
205
+ let effectiveGasLimit = gasLimit;
207
206
  if (prc20Token !== selectors_1.ZERO_ADDRESS) {
208
- const result = yield (0, gas_calculator_1.queryOutboundGasFee)(ctx, prc20Token, effectiveGasLimit, targetChain);
207
+ let result = yield (0, gas_calculator_1.queryOutboundGasFee)(ctx, prc20Token, effectiveGasLimit, targetChain);
208
+ result = yield (0, svm_rent_1.ensureSvmFinalizeGasBudgetQuote)({
209
+ ctx,
210
+ ueaAddress,
211
+ targetChain,
212
+ prc20Token,
213
+ quote: result,
214
+ splMintBase58,
215
+ burnAmount,
216
+ pathTag: 'buildHopDescriptor',
217
+ });
209
218
  gasToken = result.gasToken;
210
219
  gasFee = result.gasFee;
220
+ gasPrice = result.gasPrice;
211
221
  sizing = result.sizing;
212
- if (!params.gasLimit && result.gasPrice > BigInt(0)) {
213
- effectiveGasLimit = result.gasFee / result.gasPrice;
214
- (0, context_1.printLog)(ctx, `buildHopDescriptor — SVM derived effectiveGasLimit: ${effectiveGasLimit} (gasFee=${result.gasFee} / gasPrice=${result.gasPrice})`);
222
+ effectiveGasLimit = result.gasLimitUsed;
223
+ if (!params.gasLimit) {
224
+ (0, context_1.printLog)(ctx, `buildHopDescriptor — SVM resolved effectiveGasLimit: ${effectiveGasLimit} ` +
225
+ `(gasFee=${result.gasFee}, gasPrice=${result.gasPrice})`);
215
226
  }
216
227
  }
217
228
  return Object.assign(Object.assign({}, baseDescriptor), { targetChain, isSvmTarget: true, svmPayload,
218
229
  prc20Token,
219
230
  burnAmount,
220
231
  gasToken,
221
- gasFee, gasLimit: effectiveGasLimit, sizing });
232
+ gasFee,
233
+ gasPrice, gasLimit: effectiveGasLimit, sizing });
222
234
  }
223
235
  // EVM path: Resolve CEA address + build CEA multicalls
224
236
  const { cea: ceaAddress } = yield (0, cea_utils_1.getCEAAddress)(ueaAddress, targetChain, (_g = ctx.rpcUrls[targetChain]) === null || _g === void 0 ? void 0 : _g[0]);
@@ -228,18 +240,21 @@ function buildHopDescriptor(ctx, params, route, ueaAddress) {
228
240
  const burnAmount = BigInt(0); // Migration is logic-only — no funds. CEA rejects msg.value != 0.
229
241
  let gasToken = selectors_1.ZERO_ADDRESS;
230
242
  let gasFee = BigInt(0);
243
+ let gasPrice = BigInt(0);
231
244
  let sizing;
232
245
  if (prc20Token !== selectors_1.ZERO_ADDRESS) {
233
246
  const result = yield (0, gas_calculator_1.queryOutboundGasFee)(ctx, prc20Token, gasLimit, targetChain);
234
247
  gasToken = result.gasToken;
235
248
  gasFee = result.gasFee;
249
+ gasPrice = result.gasPrice;
236
250
  sizing = result.sizing;
237
251
  }
238
252
  return Object.assign(Object.assign({}, baseDescriptor), { targetChain,
239
253
  ceaAddress, ceaMulticalls: [], prc20Token,
240
254
  burnAmount,
241
255
  gasToken,
242
- gasFee, isMigration: true, sizing });
256
+ gasFee,
257
+ gasPrice, isMigration: true, sizing });
243
258
  }
244
259
  // Build CEA multicalls
245
260
  const ceaMulticalls = [];
@@ -342,11 +357,13 @@ function buildHopDescriptor(ctx, params, route, ueaAddress) {
342
357
  // Query gas fee
343
358
  let gasToken = selectors_1.ZERO_ADDRESS;
344
359
  let gasFee = BigInt(0);
360
+ let gasPrice = BigInt(0);
345
361
  let sizing;
346
362
  if (prc20Token !== selectors_1.ZERO_ADDRESS) {
347
363
  const result = yield (0, gas_calculator_1.queryOutboundGasFee)(ctx, prc20Token, gasLimit, targetChain);
348
364
  gasToken = result.gasToken;
349
365
  gasFee = result.gasFee;
366
+ gasPrice = result.gasPrice;
350
367
  sizing = result.sizing;
351
368
  }
352
369
  return Object.assign(Object.assign({}, baseDescriptor), { targetChain,
@@ -356,6 +373,7 @@ function buildHopDescriptor(ctx, params, route, ueaAddress) {
356
373
  burnAmount,
357
374
  gasToken,
358
375
  gasFee,
376
+ gasPrice,
359
377
  sizing });
360
378
  }
361
379
  case route_detector_1.TransactionRoute.CEA_TO_PUSH: {
@@ -401,19 +419,33 @@ function buildHopDescriptor(ctx, params, route, ueaAddress) {
401
419
  }
402
420
  let gasToken = selectors_1.ZERO_ADDRESS;
403
421
  let gasFee = BigInt(0);
422
+ let gasPrice = BigInt(0);
404
423
  let sizing;
424
+ let effectiveGasLimit = gasLimit;
405
425
  if (prc20Token !== selectors_1.ZERO_ADDRESS) {
406
- const result = yield (0, gas_calculator_1.queryOutboundGasFee)(ctx, prc20Token, gasLimit, sourceChain);
426
+ let result = yield (0, gas_calculator_1.queryOutboundGasFee)(ctx, prc20Token, effectiveGasLimit, sourceChain);
427
+ result = yield (0, svm_rent_1.ensureSvmFinalizeGasBudgetQuote)({
428
+ ctx,
429
+ ueaAddress,
430
+ targetChain: sourceChain,
431
+ prc20Token,
432
+ quote: result,
433
+ splMintBase58: undefined,
434
+ burnAmount: BigInt(0),
435
+ pathTag: 'buildHopDescriptor',
436
+ });
407
437
  gasToken = result.gasToken;
408
438
  gasFee = result.gasFee;
439
+ gasPrice = result.gasPrice;
409
440
  sizing = result.sizing;
441
+ effectiveGasLimit = result.gasLimitUsed;
410
442
  }
411
443
  return Object.assign(Object.assign({}, baseDescriptor), { sourceChain, ceaAddress: ceaPdaHex, isSvmTarget: true, prc20Token,
412
444
  // Route 3 is payload-only on Push: the drain amount lives inside the
413
445
  // SVM payload. Burning here would require the UEA to pre-hold pSOL.
414
446
  burnAmount: BigInt(0), gasToken,
415
447
  gasFee,
416
- sizing });
448
+ gasPrice, gasLimit: effectiveGasLimit, sizing });
417
449
  }
418
450
  const { cea: ceaAddress } = yield (0, cea_utils_1.getCEAAddress)(ueaAddress, sourceChain, (_p = ctx.rpcUrls[sourceChain]) === null || _p === void 0 ? void 0 : _p[0]);
419
451
  // CEA auto-deploys on-chain: Vault.finalizeUniversalTx calls CEAFactory.deployCEA()
@@ -443,11 +475,13 @@ function buildHopDescriptor(ctx, params, route, ueaAddress) {
443
475
  const prc20Token = (0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork);
444
476
  let gasToken = selectors_1.ZERO_ADDRESS;
445
477
  let gasFee = BigInt(0);
478
+ let gasPrice = BigInt(0);
446
479
  let sizing;
447
480
  if (prc20Token !== selectors_1.ZERO_ADDRESS) {
448
481
  const result = yield (0, gas_calculator_1.queryOutboundGasFee)(ctx, prc20Token, gasLimit, sourceChain);
449
482
  gasToken = result.gasToken;
450
483
  gasFee = result.gasFee;
484
+ gasPrice = result.gasPrice;
451
485
  sizing = result.sizing;
452
486
  }
453
487
  return Object.assign(Object.assign({}, baseDescriptor), { sourceChain,
@@ -458,6 +492,7 @@ function buildHopDescriptor(ctx, params, route, ueaAddress) {
458
492
  // token it is trying to receive from the external CEA.
459
493
  burnAmount: BigInt(0), gasToken,
460
494
  gasFee,
495
+ gasPrice,
461
496
  sizing });
462
497
  }
463
498
  default:
@@ -512,6 +547,8 @@ function classifyIntoSegments(hops) {
512
547
  // Accumulate gas fees
513
548
  currentSegment.gasFee =
514
549
  (currentSegment.gasFee || BigInt(0)) + (hop.gasFee || BigInt(0));
550
+ currentSegment.gasPrice = mergeGasPrice(currentSegment.gasPrice, hop.gasPrice);
551
+ currentSegment.maxPCForGas = mergeMaxPCForGas(currentSegment.maxPCForGas, hop.maxPCForGas);
515
552
  }
516
553
  else if (segType === 'INBOUND_FROM_CEA') {
517
554
  // Sum burn amounts so the single sendUniversalTxToUEA carries the
@@ -524,6 +561,8 @@ function classifyIntoSegments(hops) {
524
561
  }
525
562
  currentSegment.gasFee =
526
563
  (currentSegment.gasFee || BigInt(0)) + (hop.gasFee || BigInt(0));
564
+ currentSegment.gasPrice = mergeGasPrice(currentSegment.gasPrice, hop.gasPrice);
565
+ currentSegment.maxPCForGas = mergeMaxPCForGas(currentSegment.maxPCForGas, hop.maxPCForGas);
527
566
  }
528
567
  else if (segType === 'PUSH_EXECUTION') {
529
568
  currentSegment.mergedPushMulticalls = [
@@ -561,7 +600,9 @@ function classifyIntoSegments(hops) {
561
600
  prc20Token: hop.prc20Token,
562
601
  gasToken: hop.gasToken,
563
602
  gasFee: hop.gasFee,
603
+ gasPrice: hop.gasPrice,
564
604
  gasLimit: hop.gasLimit,
605
+ maxPCForGas: hop.maxPCForGas,
565
606
  sizing: hop.sizing,
566
607
  };
567
608
  segments.push(currentSegment);
@@ -573,6 +614,18 @@ function classifyIntoSegments(hops) {
573
614
  function categoryRank(c) {
574
615
  return c === 'C' ? 2 : c === 'B' ? 1 : 0;
575
616
  }
617
+ function mergeMaxPCForGas(current, next) {
618
+ const a = current !== null && current !== void 0 ? current : BigInt(0);
619
+ const b = next !== null && next !== void 0 ? next : BigInt(0);
620
+ if (a === BigInt(0) || b === BigInt(0))
621
+ return BigInt(0);
622
+ return a + b;
623
+ }
624
+ function mergeGasPrice(current, next) {
625
+ const a = current !== null && current !== void 0 ? current : BigInt(0);
626
+ const b = next !== null && next !== void 0 ? next : BigInt(0);
627
+ return a > b ? a : b;
628
+ }
576
629
  // ============================================================================
577
630
  // getSegmentType
578
631
  // ============================================================================
@@ -615,6 +668,34 @@ function isNativeSeedOnlySegment(segment) {
615
668
  function segmentNeedsOutboundGas(segment) {
616
669
  return segment.type !== 'PUSH_EXECUTION' && !isNativeSeedOnlySegment(segment);
617
670
  }
671
+ function buildRootOutboundIndexByHop(segments, hops) {
672
+ var _a;
673
+ const hopIndexByDescriptor = new Map();
674
+ hops.forEach((hop, index) => hopIndexByDescriptor.set(hop, index));
675
+ const nextIndexByChain = new Map();
676
+ const result = new Map();
677
+ for (const segment of segments) {
678
+ let chain;
679
+ if (segment.type === 'OUTBOUND_TO_CEA') {
680
+ chain = segment.targetChain;
681
+ }
682
+ else if (segment.type === 'INBOUND_FROM_CEA' &&
683
+ !isNativeSeedOnlySegment(segment)) {
684
+ chain = segment.sourceChain;
685
+ }
686
+ if (!chain)
687
+ continue;
688
+ const outboundIndex = (_a = nextIndexByChain.get(chain)) !== null && _a !== void 0 ? _a : 0;
689
+ for (const hop of segment.hops) {
690
+ const hopIndex = hopIndexByDescriptor.get(hop);
691
+ if (hopIndex !== undefined) {
692
+ result.set(hopIndex, outboundIndex);
693
+ }
694
+ }
695
+ nextIndexByChain.set(chain, outboundIndex + 1);
696
+ }
697
+ return result;
698
+ }
618
699
  function sumNativeSeedAmount(hops) {
619
700
  return hops.reduce((sum, hop) => { var _a; return sum + ((_a = hop.nativeSeedAmount) !== null && _a !== void 0 ? _a : BigInt(0)); }, BigInt(0));
620
701
  }
@@ -672,15 +753,17 @@ function getInboundFundingForBurnToken(segments, beforeSegmentIndex, burnToken,
672
753
  }
673
754
  return total;
674
755
  }
675
- function composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce) {
756
+ function composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce, options) {
676
757
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
677
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
758
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u;
678
759
  let accumulatedPushMulticalls = [];
679
760
  let requiredNativeValue = BigInt(0);
680
761
  const gatewayPcAddress = (0, helpers_1.getUniversalGatewayPCAddress)();
681
762
  const currentUeaBalance = ueaBalance !== null && ueaBalance !== void 0 ? ueaBalance : BigInt(0);
682
763
  const cascadeNativeSeed = getCascadeNativeSeed(segments);
683
764
  const effectiveUeaBalance = currentUeaBalance + cascadeNativeSeed;
765
+ const enforceGasCheck = (options === null || options === void 0 ? void 0 : options.enforceGasCheck) === true ||
766
+ segments.some((segment) => segment.hops.some((hop) => { var _a; return ((_a = hop.params.options) === null || _a === void 0 ? void 0 : _a.enforceGasCheck) === true; }));
684
767
  if (cascadeNativeSeed > BigInt(0)) {
685
768
  (0, context_1.printLog)(ctx, `composeCascade — counting cascade native seed ${cascadeNativeSeed.toString()} for cascade sizing (current UEA balance ${currentUeaBalance.toString()})`);
686
769
  }
@@ -692,8 +775,8 @@ function composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce)
692
775
  // Cascade pre-flight: catches the zero-balance silent-segment-zero failure
693
776
  // mode. When `ueaBalance <= CASCADE_GAS_RESERVE`, `perOutboundNativeValue`
694
777
  // is left undefined and downstream segments silently allocate value=0,
695
- // producing under-funded swaps that revert inside Uniswap. Throw early
696
- // with a structured error so the caller knows which path / hop failed.
778
+ // producing under-funded swaps that revert inside Uniswap. Warn by default
779
+ // and throw only when enforceGasCheck=true.
697
780
  if (numOutbounds > 0) {
698
781
  (0, preflight_1.runPreflight)({
699
782
  ctx,
@@ -704,6 +787,7 @@ function composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce)
704
787
  requiredValue: BigInt(1),
705
788
  gasReserve: CASCADE_GAS_RESERVE,
706
789
  pathTag: 'CASCADE',
790
+ enforceGasCheck,
707
791
  });
708
792
  }
709
793
  let perOutboundNativeValue;
@@ -729,7 +813,7 @@ function composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce)
729
813
  const universalCoreAddress = yield ctx.pushClient.readContract({
730
814
  address: gatewayPcAddress,
731
815
  abi: abi_1.UNIVERSAL_GATEWAY_PC,
732
- functionName: 'UNIVERSAL_CORE',
816
+ functionName: 'universalCore',
733
817
  args: [],
734
818
  });
735
819
  // Compute per-SVM budget = ueaBalance − (PC needed for EVM outbounds) − 1 PC safety
@@ -786,6 +870,17 @@ function composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce)
786
870
  // knows which hop's pool is misbehaving.
787
871
  if (estimated > BigInt(0) && value < estimated) {
788
872
  const shortfall = estimated - value;
873
+ (0, context_2.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_003_03, estimated, value, false, ueaAddress, 'CASCADE', {
874
+ kind: 'NATIVE',
875
+ segmentIndex: cascadeIdx,
876
+ enforceGasCheck,
877
+ shortfall,
878
+ });
879
+ if (!enforceGasCheck) {
880
+ (0, context_1.printLog)(ctx, `composeCascade — SVM outbound segment ${cascadeIdx} has native shortfall ${shortfall.toString()}; proceeding because enforceGasCheck=false`);
881
+ svmNativeValueBySegment.set(seg, value);
882
+ continue;
883
+ }
789
884
  (0, context_2.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_003_04, estimated, value, shortfall, ueaAddress, 'CASCADE', { kind: 'NATIVE', segmentIndex: cascadeIdx });
790
885
  throw new errors_1.InsufficientUEABalanceError({
791
886
  required: estimated,
@@ -838,7 +933,10 @@ function composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce)
838
933
  // EVM: build CEA payload from merged multicalls
839
934
  outboundPayload = (0, payload_builders_1.buildCeaMulticallPayload)(segment.mergedCeaMulticalls || []);
840
935
  // Get CEA address from the first hop
841
- targetForOutbound = (firstHop === null || firstHop === void 0 ? void 0 : firstHop.ceaAddress) || ueaAddress;
936
+ targetForOutbound = outboundPayload === '0x'
937
+ ? selectors_1.ZERO_ADDRESS
938
+ : (firstHop === null || firstHop === void 0 ? void 0 : firstHop.ceaAddress) || ueaAddress;
939
+ (0, payload_builders_1.assertCeaFundsParkingInvariant)(targetForOutbound, outboundPayload);
842
940
  }
843
941
  // Per-segment PRC-20 burn-balance pre-check (cascade R2-style segments
844
942
  // only — INBOUND_FROM_CEA segments are payload-only with totalBurnAmount=0
@@ -856,7 +954,13 @@ function composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce)
856
954
  args: [ueaAddress],
857
955
  });
858
956
  const sufficient = onHand >= segBurnAmount;
859
- (0, context_2.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_003_03, segBurnAmount, onHand, sufficient, ueaAddress, 'CASCADE', { kind: 'PRC20', burnToken: segBurnToken, segmentIndex: segIdxLoop });
957
+ (0, context_2.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_003_03, segBurnAmount, onHand, sufficient, ueaAddress, 'CASCADE', {
958
+ kind: 'PRC20',
959
+ burnToken: segBurnToken,
960
+ segmentIndex: segIdxLoop,
961
+ enforceGasCheck,
962
+ shortfall: sufficient ? BigInt(0) : segBurnAmount - onHand,
963
+ });
860
964
  const priorPushExecutionMayFundBurn = segments
861
965
  .slice(0, segIdxLoop)
862
966
  .some((s) => s.type === 'PUSH_EXECUTION');
@@ -867,21 +971,26 @@ function composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce)
867
971
  !priorInboundMayFundBurn) {
868
972
  const projectedOnHand = onHand + priorInboundFundingForBurn;
869
973
  const shortfall = segBurnAmount - projectedOnHand;
870
- (0, context_2.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_003_04, segBurnAmount, projectedOnHand, shortfall, ueaAddress, 'CASCADE', {
871
- kind: 'PRC20',
872
- burnToken: segBurnToken,
873
- segmentIndex: segIdxLoop,
874
- });
875
- throw new errors_1.InsufficientUEABalanceError({
876
- required: segBurnAmount,
877
- available: projectedOnHand,
878
- shortfall,
879
- ueaAddress,
880
- pathTag: 'CASCADE',
881
- reason: 'PRC20',
882
- burnToken: segBurnToken,
883
- segmentIndex: segIdxLoop,
884
- });
974
+ if (!enforceGasCheck) {
975
+ (0, context_1.printLog)(ctx, `composeCascade — PRC-20 shortfall ${shortfall.toString()} for segment ${segIdxLoop}; proceeding because enforceGasCheck=false`);
976
+ }
977
+ else {
978
+ (0, context_2.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_003_04, segBurnAmount, projectedOnHand, shortfall, ueaAddress, 'CASCADE', {
979
+ kind: 'PRC20',
980
+ burnToken: segBurnToken,
981
+ segmentIndex: segIdxLoop,
982
+ });
983
+ throw new errors_1.InsufficientUEABalanceError({
984
+ required: segBurnAmount,
985
+ available: projectedOnHand,
986
+ shortfall,
987
+ ueaAddress,
988
+ pathTag: 'CASCADE',
989
+ reason: 'PRC20',
990
+ burnToken: segBurnToken,
991
+ segmentIndex: segIdxLoop,
992
+ });
993
+ }
885
994
  }
886
995
  else if (!sufficient && priorPushExecutionMayFundBurn) {
887
996
  (0, context_1.printLog)(ctx, `composeCascade — skipping PRC-20 preflight failure for segment ${segIdxLoop}; earlier Push execution may fund ${segBurnToken} before this outbound runs`);
@@ -891,7 +1000,7 @@ function composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce)
891
1000
  }
892
1001
  }
893
1002
  // Build outbound request
894
- const outboundReq = (0, payload_builders_1.buildOutboundRequest)(targetForOutbound, segment.prc20Token || selectors_1.ZERO_ADDRESS, segment.totalBurnAmount || BigInt(0), (_e = segment.gasLimit) !== null && _e !== void 0 ? _e : BigInt(0), outboundPayload, ueaAddress);
1003
+ const outboundReq = (0, payload_builders_1.buildOutboundRequest)(targetForOutbound, segment.prc20Token || selectors_1.ZERO_ADDRESS, segment.totalBurnAmount || BigInt(0), (_e = segment.gasLimit) !== null && _e !== void 0 ? _e : BigInt(0), outboundPayload, ueaAddress, (_f = segment.maxPCForGas) !== null && _f !== void 0 ? _f : BigInt(0), (_g = segment.gasPrice) !== null && _g !== void 0 ? _g : BigInt(0));
895
1004
  // Build approval + outbound multicalls
896
1005
  const segGasFee = segment.gasFee || BigInt(0);
897
1006
  const svmOverride = svmNativeValueBySegment.get(segment);
@@ -908,7 +1017,7 @@ function composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce)
908
1017
  else {
909
1018
  evmFallbackValue = segGasFee * BigInt(1000000);
910
1019
  }
911
- let nativeValueForGas = (_f = svmOverride !== null && svmOverride !== void 0 ? svmOverride : perOutboundNativeValue) !== null && _f !== void 0 ? _f : evmFallbackValue;
1020
+ let nativeValueForGas = (_h = svmOverride !== null && svmOverride !== void 0 ? svmOverride : perOutboundNativeValue) !== null && _h !== void 0 ? _h : evmFallbackValue;
912
1021
  // SDK 5.2 Case C: when the segment's sizing is category C, compose
913
1022
  // R2/OUTBOUND_TO_CEA segments no longer apply Case A/B/C sizing
914
1023
  // (scoped to R1 + R3 only). segment.sizing may still be populated
@@ -949,7 +1058,7 @@ function composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce)
949
1058
  // UEA multicall so no nested outbound is needed.
950
1059
  const mergedHopMulticalls = [];
951
1060
  for (const h of segment.hops) {
952
- if (((_g = h === null || h === void 0 ? void 0 : h.params) === null || _g === void 0 ? void 0 : _g.data) || ((_h = h === null || h === void 0 ? void 0 : h.params) === null || _h === void 0 ? void 0 : _h.value)) {
1061
+ if (((_j = h === null || h === void 0 ? void 0 : h.params) === null || _j === void 0 ? void 0 : _j.data) || ((_k = h === null || h === void 0 ? void 0 : h.params) === null || _k === void 0 ? void 0 : _k.value)) {
953
1062
  const hopCalls = (0, payload_builders_1.buildExecuteMulticall)({
954
1063
  execute: {
955
1064
  to: h.params.to,
@@ -993,7 +1102,7 @@ function composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce)
993
1102
  let drainAmount = BigInt(0);
994
1103
  let tokenMintHex;
995
1104
  const params = hop.params;
996
- if (((_j = params.funds) === null || _j === void 0 ? void 0 : _j.amount) && params.funds.amount > BigInt(0)) {
1105
+ if (((_l = params.funds) === null || _l === void 0 ? void 0 : _l.amount) && params.funds.amount > BigInt(0)) {
997
1106
  drainAmount = params.funds.amount;
998
1107
  const token = params.funds.token;
999
1108
  if (token && token.address) {
@@ -1022,7 +1131,7 @@ function composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce)
1022
1131
  });
1023
1132
  const svmBurnAmount = segment.totalBurnAmount || BigInt(0);
1024
1133
  const outboundReq = (0, payload_builders_1.buildOutboundRequest)(gatewayProgramHex, segment.prc20Token ||
1025
- (0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork), svmBurnAmount, (_k = segment.gasLimit) !== null && _k !== void 0 ? _k : BigInt(0), svmPayload, ueaAddress);
1134
+ (0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork), svmBurnAmount, (_m = segment.gasLimit) !== null && _m !== void 0 ? _m : BigInt(0), svmPayload, ueaAddress, (_o = segment.maxPCForGas) !== null && _o !== void 0 ? _o : BigInt(0), (_p = segment.gasPrice) !== null && _p !== void 0 ? _p : BigInt(0));
1026
1135
  const inboundGasFee = segment.gasFee || BigInt(0);
1027
1136
  let svmInboundFallback;
1028
1137
  if (effectiveUeaBalance > BigInt(0)) {
@@ -1036,7 +1145,7 @@ function composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce)
1036
1145
  // SDK 5.2 R3-style Case C bump: when the inbound segment's sizing
1037
1146
  // is C, top up the swap budget by the overflow. No bridge-swap —
1038
1147
  // R3 has no destination funds delivery.
1039
- if (((_l = segment.sizing) === null || _l === void 0 ? void 0 : _l.category) === 'C' &&
1148
+ if (((_q = segment.sizing) === null || _q === void 0 ? void 0 : _q.category) === 'C' &&
1040
1149
  segment.sizing.overflowNativePc > BigInt(0)) {
1041
1150
  const bumped = svmInboundNativeValue + segment.sizing.overflowNativePc;
1042
1151
  (0, context_1.printLog)(ctx, `composeCascade — INBOUND_FROM_CEA (SVM) Case C: bumping nativeValueForGas from ${svmInboundNativeValue} to ${bumped} (overflow=${segment.sizing.overflowNativePc})`);
@@ -1056,7 +1165,8 @@ function composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce)
1056
1165
  accumulatedPushMulticalls = [...outboundMulticalls];
1057
1166
  break;
1058
1167
  }
1059
- // EVM path: Build CEA multicall: [approve?, sendUniversalTxFromCEA(payload)]
1168
+ // EVM path: Build CEA multicall: sendUniversalTxToUEA(payload).
1169
+ // CEA handles ERC20 gateway approval internally.
1060
1170
  const ceaMulticalls = [];
1061
1171
  // Add primary hop's own CEA operations if any
1062
1172
  // (e.g., approve + swap before bridging back)
@@ -1072,7 +1182,7 @@ function composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce)
1072
1182
  let amount = BigInt(0);
1073
1183
  for (const mergedHop of segment.hops) {
1074
1184
  const hParams = mergedHop.params;
1075
- if ((_m = hParams.funds) === null || _m === void 0 ? void 0 : _m.amount) {
1185
+ if ((_r = hParams.funds) === null || _r === void 0 ? void 0 : _r.amount) {
1076
1186
  const token = hParams.funds.token;
1077
1187
  if (token) {
1078
1188
  if (token.mechanism === 'native') {
@@ -1086,32 +1196,6 @@ function composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce)
1086
1196
  }
1087
1197
  tokenAddress = addr;
1088
1198
  amount += hParams.funds.amount;
1089
- // CEA approves gateway once with the cumulative allowance.
1090
- const gatewayAddr = chain_1.UNIVERSAL_GATEWAY_ADDRESSES[sourceChain];
1091
- if (!gatewayAddr) {
1092
- throw new Error(`No UniversalGateway address configured for chain ${sourceChain}`);
1093
- }
1094
- // Reset allowance to 0 first for USDT-style tokens
1095
- const approveZeroData = (0, viem_1.encodeFunctionData)({
1096
- abi: erc20_evm_1.ERC20_EVM,
1097
- functionName: 'approve',
1098
- args: [gatewayAddr, BigInt(0)],
1099
- });
1100
- ceaMulticalls.push({
1101
- to: tokenAddress,
1102
- value: BigInt(0),
1103
- data: approveZeroData,
1104
- });
1105
- const approveData = (0, viem_1.encodeFunctionData)({
1106
- abi: erc20_evm_1.ERC20_EVM,
1107
- functionName: 'approve',
1108
- args: [gatewayAddr, hParams.funds.amount],
1109
- });
1110
- ceaMulticalls.push({
1111
- to: tokenAddress,
1112
- value: BigInt(0),
1113
- data: approveData,
1114
- });
1115
1199
  }
1116
1200
  }
1117
1201
  }
@@ -1134,7 +1218,7 @@ function composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce)
1134
1218
  ? segment.gasLimit
1135
1219
  : selectors_1.DEFAULT_CEA_TO_PUSH_GAS_LIMIT;
1136
1220
  const outboundReq = (0, payload_builders_1.buildOutboundRequest)(ceaAddress, segment.prc20Token ||
1137
- (0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork), segment.totalBurnAmount || BigInt(0), effectiveGasLimit, ceaPayload, ueaAddress);
1221
+ (0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork), segment.totalBurnAmount || BigInt(0), effectiveGasLimit, ceaPayload, ueaAddress, (_s = segment.maxPCForGas) !== null && _s !== void 0 ? _s : BigInt(0), (_t = segment.gasPrice) !== null && _t !== void 0 ? _t : BigInt(0));
1138
1222
  const inboundGasFee = segment.gasFee || BigInt(0);
1139
1223
  let evmInboundFallback;
1140
1224
  if (effectiveUeaBalance > BigInt(0)) {
@@ -1148,7 +1232,7 @@ function composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce)
1148
1232
  // SDK 5.2 R3-style Case C bump: when the inbound segment's sizing
1149
1233
  // is C, top up the swap budget by the overflow. No bridge-swap —
1150
1234
  // R3 has no destination funds delivery.
1151
- if (((_o = segment.sizing) === null || _o === void 0 ? void 0 : _o.category) === 'C' &&
1235
+ if (((_u = segment.sizing) === null || _u === void 0 ? void 0 : _u.category) === 'C' &&
1152
1236
  segment.sizing.overflowNativePc > BigInt(0)) {
1153
1237
  const bumped = evmInboundNativeValue + segment.sizing.overflowNativePc;
1154
1238
  (0, context_1.printLog)(ctx, `composeCascade — INBOUND_FROM_CEA (EVM) Case C: bumping nativeValueForGas from ${evmInboundNativeValue} to ${bumped} (overflow=${segment.sizing.overflowNativePc})`);
@@ -1193,11 +1277,14 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook) {
1193
1277
  const ueaAddress = (0, uea_manager_1.computeUEAOffchain)(ctx);
1194
1278
  // Extract HopDescriptors
1195
1279
  const hops = preparedTxs.map((tx) => tx._hop);
1280
+ const enforceGasCheck = hops.some((hop) => { var _a; return ((_a = hop.params.options) === null || _a === void 0 ? void 0 : _a.enforceGasCheck) === true; });
1281
+ const withCascadeOptions = (params) => enforceGasCheck
1282
+ ? Object.assign(Object.assign({}, params), { options: Object.assign(Object.assign({}, params.options), { enforceGasCheck: true }) }) : params;
1196
1283
  // Classify into segments
1197
1284
  const segments = classifyIntoSegments(hops);
1198
1285
  // Check if this is a single-hop Route 1 (no composition needed)
1199
1286
  if (preparedTxs.length === 1 && preparedTxs[0].route === 'UOA_TO_PUSH') {
1200
- const response = yield callbacks.executeFn(hops[0].params);
1287
+ const response = yield callbacks.executeFn(withCascadeOptions(hops[0].params));
1201
1288
  const singleRoute1Result = {
1202
1289
  initialTxHash: response.hash,
1203
1290
  initialTxResponse: response,
@@ -1231,7 +1318,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook) {
1231
1318
  }
1232
1319
  // Check if single-hop Route 2 (just execute directly)
1233
1320
  if (preparedTxs.length === 1 && preparedTxs[0].route === 'UOA_TO_CEA') {
1234
- const response = yield callbacks.executeFn(hops[0].params);
1321
+ const response = yield callbacks.executeFn(withCascadeOptions(hops[0].params));
1235
1322
  const targetChain = hops[0].targetChain || (0, helpers_1.getPushChainForNetwork)(ctx.pushNetwork);
1236
1323
  const singleRoute2Result = {
1237
1324
  initialTxHash: response.hash,
@@ -1276,7 +1363,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook) {
1276
1363
  let composedMulticalls;
1277
1364
  let cascadeRequiredNativeValue = BigInt(0);
1278
1365
  try {
1279
- const composed = yield composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonceCascade);
1366
+ const composed = yield composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonceCascade, { enforceGasCheck });
1280
1367
  composedMulticalls = composed.multicalls;
1281
1368
  cascadeRequiredNativeValue = composed.requiredNativeValue;
1282
1369
  }
@@ -1319,7 +1406,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook) {
1319
1406
  hops: hopInfos,
1320
1407
  hopCount: hops.length,
1321
1408
  waitForAll: (opts) => tslib_1.__awaiter(this, void 0, void 0, function* () {
1322
- var _a, _b, _c, _d, _e, _f, _g;
1409
+ var _a, _b, _c, _d, _e, _f, _g, _h;
1323
1410
  const { pollingIntervalMs = 10000, timeout = 300000, progressHook: cascadeProgressHook, eventHook = defaultEventHook, } = opts || {};
1324
1411
  const startTime = Date.now();
1325
1412
  // Per-hop progress emission. Fans out cascade markers to BOTH the
@@ -1398,8 +1485,9 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook) {
1398
1485
  yield response.wait();
1399
1486
  const ceaToPushIndex = hopInfos.findIndex((h, index) => h.route === 'CEA_TO_PUSH' && !isNativeSeedOnlyHop(hops[index]));
1400
1487
  const isAfterCeaToPush = (index) => ceaToPushIndex >= 0 && index > ceaToPushIndex;
1401
- const trackOutboundHops = (outboundHops, pushTxHash, resolvedSubTxId) => tslib_1.__awaiter(this, void 0, void 0, function* () {
1402
- var _a, _b, _c;
1488
+ const rootOutboundIndexByHop = buildRootOutboundIndexByHop(segments, hops);
1489
+ const trackOutboundHops = (outboundHops, pushTxHash, resolvedSubTxId, outboundIndexByHop) => tslib_1.__awaiter(this, void 0, void 0, function* () {
1490
+ var _a, _b, _c, _d;
1403
1491
  if (outboundHops.length === 0)
1404
1492
  return null;
1405
1493
  if (outboundHops.length === 1) {
@@ -1434,13 +1522,11 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook) {
1434
1522
  });
1435
1523
  let lastEmittedStatus;
1436
1524
  try {
1437
- const outboundDetails = yield callbacks.waitForOutboundTxFn(pushTxHash, {
1438
- initialWaitMs: Math.min(60000, remainingTimeout),
1439
- pollingIntervalMs,
1440
- timeout: remainingTimeout,
1441
- _resolvedSubTxId: resolvedSubTxId,
1442
- _expectedDestinationChain: hop.executionChain,
1443
- progressHook: (event) => {
1525
+ const outboundDetails = yield callbacks.waitForOutboundTxFn(pushTxHash, Object.assign(Object.assign({ initialWaitMs: Math.min(60000, remainingTimeout), pollingIntervalMs, timeout: remainingTimeout, _resolvedSubTxId: resolvedSubTxId, _expectedDestinationChain: hop.executionChain }, (outboundIndexByHop
1526
+ ? {
1527
+ _outboundIndex: (_a = outboundIndexByHop.get(hop.hopIndex)) !== null && _a !== void 0 ? _a : 0,
1528
+ }
1529
+ : {})), { progressHook: (event) => {
1444
1530
  cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
1445
1531
  hopIndex: hop.hopIndex,
1446
1532
  route: hop.route,
@@ -1453,14 +1539,13 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook) {
1453
1539
  lastEmittedStatus = 'polling';
1454
1540
  emitHopEvent(hopHooks.polling(hop.executionChain, event.elapsed));
1455
1541
  }
1456
- },
1457
- });
1542
+ } }));
1458
1543
  // `outboundDetails.externalTxHash` from waitForOutboundTx
1459
1544
  // is the raw `0x`-hex form (internal canonical). For the
1460
1545
  // user-facing `hop.txHash`, `hop.outboundDetails`, and
1461
1546
  // cascade progress event, normalize to base58 for SVM so
1462
1547
  // consumers can paste it straight into a Solana explorer.
1463
- const displayTxHash = (_a = (0, helpers_1.toExternalTxHashDisplay)(outboundDetails.destinationChain, outboundDetails.externalTxHash)) !== null && _a !== void 0 ? _a : outboundDetails.externalTxHash;
1548
+ const displayTxHash = (_b = (0, helpers_1.toExternalTxHashDisplay)(outboundDetails.destinationChain, outboundDetails.externalTxHash)) !== null && _b !== void 0 ? _b : outboundDetails.externalTxHash;
1464
1549
  const userFacingOutboundDetails = Object.assign(Object.assign({}, outboundDetails), { externalTxHash: displayTxHash });
1465
1550
  hop.status = 'confirmed';
1466
1551
  hop.txHash = displayTxHash;
@@ -1549,12 +1634,12 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook) {
1549
1634
  },
1550
1635
  });
1551
1636
  if (!allOutboundDetails.success) {
1552
- const failedIdx = (_b = allOutboundDetails.failedAt) !== null && _b !== void 0 ? _b : 0;
1637
+ const failedIdx = (_c = allOutboundDetails.failedAt) !== null && _c !== void 0 ? _c : 0;
1553
1638
  const failedHop = outboundHops.find((h) => h.hopIndex === failedIdx);
1554
1639
  const failMsg = failedHop
1555
1640
  ? `Outbound failed for hop ${failedIdx} on ${failedHop.executionChain}`
1556
1641
  : `Outbound cascade failed at hop ${failedIdx}`;
1557
- emitHopEvent((0, progress_route_hooks_1.pickWaitHooks)(failedHop === null || failedHop === void 0 ? void 0 : failedHop.route).failed((_c = failedHop === null || failedHop === void 0 ? void 0 : failedHop.executionChain) !== null && _c !== void 0 ? _c : '', failMsg));
1642
+ emitHopEvent((0, progress_route_hooks_1.pickWaitHooks)(failedHop === null || failedHop === void 0 ? void 0 : failedHop.route).failed((_d = failedHop === null || failedHop === void 0 ? void 0 : failedHop.executionChain) !== null && _d !== void 0 ? _d : '', failMsg));
1558
1643
  emitCascadeFailed(failedIdx, failMsg);
1559
1644
  return {
1560
1645
  success: false,
@@ -1603,7 +1688,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook) {
1603
1688
  // 2. Track direct outbound hops (Route 2: UOA_TO_CEA) that belong
1604
1689
  // to the root Push UTX.
1605
1690
  const rootOutboundHops = hopInfos.filter((h) => h.route === 'UOA_TO_CEA' && !isAfterCeaToPush(h.hopIndex));
1606
- const rootOutboundFailure = yield trackOutboundHops(rootOutboundHops, response.hash);
1691
+ const rootOutboundFailure = yield trackOutboundHops(rootOutboundHops, response.hash, undefined, rootOutboundIndexByHop);
1607
1692
  if (rootOutboundFailure)
1608
1693
  return rootOutboundFailure;
1609
1694
  let childInbound;
@@ -1638,6 +1723,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook) {
1638
1723
  pollingIntervalMs,
1639
1724
  timeout: remainingTimeout,
1640
1725
  _expectedDestinationChain: inboundHop.executionChain,
1726
+ _outboundIndex: (_d = rootOutboundIndexByHop.get(inboundHop.hopIndex)) !== null && _d !== void 0 ? _d : 0,
1641
1727
  progressHook: (event) => {
1642
1728
  cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
1643
1729
  hopIndex: inboundHop.hopIndex,
@@ -1666,7 +1752,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook) {
1666
1752
  emitCascadeFailed(inboundHop.hopIndex, errMsg);
1667
1753
  inboundHop.status = 'failed';
1668
1754
  const failedExternalTxHash = err === null || err === void 0 ? void 0 : err.externalTxHash;
1669
- const failedDisplayTxHash = (_d = (0, helpers_1.toExternalTxHashDisplay)(inboundHop.executionChain, failedExternalTxHash)) !== null && _d !== void 0 ? _d : failedExternalTxHash;
1755
+ const failedDisplayTxHash = (_e = (0, helpers_1.toExternalTxHashDisplay)(inboundHop.executionChain, failedExternalTxHash)) !== null && _e !== void 0 ? _e : failedExternalTxHash;
1670
1756
  if (failedDisplayTxHash)
1671
1757
  inboundHop.txHash = failedDisplayTxHash;
1672
1758
  cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
@@ -1683,7 +1769,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook) {
1683
1769
  failedAt: inboundHop.hopIndex,
1684
1770
  };
1685
1771
  }
1686
- const displayExternalTxHash = (_e = (0, helpers_1.toExternalTxHashDisplay)(sourceOutboundDetails.destinationChain, sourceOutboundDetails.externalTxHash)) !== null && _e !== void 0 ? _e : sourceOutboundDetails.externalTxHash;
1772
+ const displayExternalTxHash = (_f = (0, helpers_1.toExternalTxHashDisplay)(sourceOutboundDetails.destinationChain, sourceOutboundDetails.externalTxHash)) !== null && _f !== void 0 ? _f : sourceOutboundDetails.externalTxHash;
1687
1773
  inboundHop.outboundDetails = Object.assign(Object.assign({}, sourceOutboundDetails), { externalTxHash: displayExternalTxHash });
1688
1774
  inboundHop.txHash = displayExternalTxHash;
1689
1775
  emitHopEvent(hopHooks.success(inboundHop.outboundDetails));
@@ -1691,7 +1777,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook) {
1691
1777
  let inboundLastStatus;
1692
1778
  const inboundRemainingTimeout = timeout - (Date.now() - startTime);
1693
1779
  try {
1694
- childInbound = yield ((_f = callbacks.waitForInboundPushTxFn) !== null && _f !== void 0 ? _f : ((externalTxHash, sourceChain, opts) => (0, inbound_tracker_1.waitForInboundPushTx)(ctx, externalTxHash, sourceChain, opts)))(sourceOutboundDetails.externalTxHash, inboundHop.executionChain, {
1780
+ childInbound = yield ((_g = callbacks.waitForInboundPushTxFn) !== null && _g !== void 0 ? _g : ((externalTxHash, sourceChain, opts) => (0, inbound_tracker_1.waitForInboundPushTx)(ctx, externalTxHash, sourceChain, opts)))(sourceOutboundDetails.externalTxHash, inboundHop.executionChain, {
1695
1781
  initialWaitMs: Math.min(60000, inboundRemainingTimeout),
1696
1782
  pollingIntervalMs,
1697
1783
  timeout: inboundRemainingTimeout,
@@ -1729,7 +1815,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook) {
1729
1815
  };
1730
1816
  }
1731
1817
  if (childInbound.status !== 'confirmed') {
1732
- const failMsg = (_g = childInbound.errorMessage) !== null && _g !== void 0 ? _g : 'inbound execution failed on Push Chain';
1818
+ const failMsg = (_h = childInbound.errorMessage) !== null && _h !== void 0 ? _h : 'inbound execution failed on Push Chain';
1733
1819
  inboundHop.status = 'failed';
1734
1820
  inboundHop.txHash =
1735
1821
  childInbound.pushTxHash || displayExternalTxHash;