@pushchain/core 5.1.17 → 5.1.18

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 (36) hide show
  1. package/package.json +1 -1
  2. package/src/lib/constants/index.d.ts +1 -1
  3. package/src/lib/constants/index.js +2 -1
  4. package/src/lib/constants/selectors.d.ts +8 -0
  5. package/src/lib/constants/selectors.js +9 -1
  6. package/src/lib/formatters.d.ts +1 -0
  7. package/src/lib/formatters.js +32 -0
  8. package/src/lib/orchestrator/internals/cascade.d.ts +8 -1
  9. package/src/lib/orchestrator/internals/cascade.js +486 -143
  10. package/src/lib/orchestrator/internals/error-decoder.d.ts +2 -6
  11. package/src/lib/orchestrator/internals/error-decoder.js +10 -10
  12. package/src/lib/orchestrator/internals/errors.d.ts +1 -1
  13. package/src/lib/orchestrator/internals/errors.js +7 -4
  14. package/src/lib/orchestrator/internals/execute-standard.js +7 -6
  15. package/src/lib/orchestrator/internals/gas-calculator.js +78 -11
  16. package/src/lib/orchestrator/internals/helpers.d.ts +2 -1
  17. package/src/lib/orchestrator/internals/helpers.js +13 -11
  18. package/src/lib/orchestrator/internals/outbound-sync.d.ts +1 -0
  19. package/src/lib/orchestrator/internals/outbound-sync.js +93 -37
  20. package/src/lib/orchestrator/internals/preflight.d.ts +2 -16
  21. package/src/lib/orchestrator/internals/preflight.js +2 -36
  22. package/src/lib/orchestrator/internals/response-builder.js +23 -7
  23. package/src/lib/orchestrator/internals/route-handlers.js +34 -49
  24. package/src/lib/orchestrator/orchestrator.d.ts +7 -1
  25. package/src/lib/orchestrator/orchestrator.js +40 -2
  26. package/src/lib/orchestrator/orchestrator.types.d.ts +4 -14
  27. package/src/lib/progress-hook/progress-hook.js +13 -20
  28. package/src/lib/push-chain/push-chain.d.ts +11 -1
  29. package/src/lib/push-chain/push-chain.js +2 -2
  30. package/src/lib/universal-tx-detector/cascade.js +14 -4
  31. package/src/lib/universal-tx-detector/child-inbounds.js +9 -1
  32. package/src/lib/universal-tx-detector/detector.js +9 -3
  33. package/src/lib/universal-tx-detector/normalize.d.ts +42 -0
  34. package/src/lib/universal-tx-detector/normalize.js +127 -0
  35. package/src/lib/utils/external-tx-hash.d.ts +37 -0
  36. package/src/lib/utils/external-tx-hash.js +62 -0
@@ -11,6 +11,7 @@ exports.prepareTransaction = prepareTransaction;
11
11
  exports.buildHopDescriptor = buildHopDescriptor;
12
12
  exports.classifyIntoSegments = classifyIntoSegments;
13
13
  exports.getSegmentType = getSegmentType;
14
+ exports.composeCascadeDetailed = composeCascadeDetailed;
14
15
  exports.composeCascade = composeCascade;
15
16
  exports.createCascadedBuilder = createCascadedBuilder;
16
17
  const tslib_1 = require("tslib");
@@ -38,6 +39,7 @@ const preflight_1 = require("./preflight");
38
39
  const svm_rent_1 = require("./svm-rent");
39
40
  const errors_1 = require("./errors");
40
41
  const context_2 = require("./context");
42
+ const inbound_tracker_1 = require("./inbound-tracker");
41
43
  // ============================================================================
42
44
  // dispatchCascadeProgressEvent
43
45
  // ============================================================================
@@ -120,9 +122,16 @@ function prepareTransaction(ctx, params, callbacks) {
120
122
  // ============================================================================
121
123
  function buildHopDescriptor(ctx, params, route, ueaAddress) {
122
124
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
123
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
124
- // Pass 0 when user omits gasLimit → contract uses per-chain baseGasLimitByChainNamespace
125
- const gasLimit = (_a = params.gasLimit) !== null && _a !== void 0 ? _a : BigInt(0);
125
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
126
+ // Pass 0 when user omits gasLimit so the contract uses the per-chain base,
127
+ // except EVM Route 3 where a CEA deploy + sendUniversalTxToUEA needs more
128
+ // than the generic 500k source-chain base.
129
+ const defaultGasLimit = route === route_detector_1.TransactionRoute.CEA_TO_PUSH &&
130
+ ((_a = params.from) === null || _a === void 0 ? void 0 : _a.chain) &&
131
+ !(0, payload_builders_1.isSvmChain)(params.from.chain)
132
+ ? selectors_1.DEFAULT_CEA_TO_PUSH_GAS_LIMIT
133
+ : BigInt(0);
134
+ const gasLimit = (_b = params.gasLimit) !== null && _b !== void 0 ? _b : defaultGasLimit;
126
135
  const routeStr = route;
127
136
  const baseDescriptor = {
128
137
  params,
@@ -134,6 +143,10 @@ function buildHopDescriptor(ctx, params, route, ueaAddress) {
134
143
  switch (route) {
135
144
  case route_detector_1.TransactionRoute.UOA_TO_PUSH: {
136
145
  // Route 1: Build Push Chain multicalls
146
+ const seedAmount = (_c = params.value) !== null && _c !== void 0 ? _c : BigInt(0);
147
+ if (isValueOnlyNativeSeedToOwnUea(params, ueaAddress)) {
148
+ return Object.assign(Object.assign({}, baseDescriptor), { pushMulticalls: [], nativeSeedOnly: true, nativeSeedAmount: seedAmount });
149
+ }
137
150
  const executeParams = (0, helpers_1.toExecuteParams)(params);
138
151
  const pushMulticalls = (0, payload_builders_1.buildExecuteMulticall)({
139
152
  execute: executeParams,
@@ -155,7 +168,7 @@ function buildHopDescriptor(ctx, params, route, ueaAddress) {
155
168
  });
156
169
  let prc20Token = selectors_1.ZERO_ADDRESS;
157
170
  let burnAmount = BigInt(0);
158
- if ((_b = params.funds) === null || _b === void 0 ? void 0 : _b.amount) {
171
+ if ((_d = params.funds) === null || _d === void 0 ? void 0 : _d.amount) {
159
172
  const token = params.funds.token;
160
173
  if (token) {
161
174
  prc20Token = push_chain_1.PushChain.utils.tokens.getPRC20Address(token).address;
@@ -181,9 +194,9 @@ function buildHopDescriptor(ctx, params, route, ueaAddress) {
181
194
  // Conditional CEA-ATA rent bump (SPL-only). Mirrors single-route
182
195
  // executeUoaToCeaSvm path. See svm-rent.ts for rationale.
183
196
  const splMintBase58 = burnAmount > BigInt(0)
184
- ? (_d = (_c = params.funds) === null || _c === void 0 ? void 0 : _c.token) === null || _d === void 0 ? void 0 : _d.address
197
+ ? (_f = (_e = params.funds) === null || _e === void 0 ? void 0 : _e.token) === null || _f === void 0 ? void 0 : _f.address
185
198
  : undefined;
186
- const effectiveGasLimit = yield (0, svm_rent_1.maybeBumpForCeaAtaRent)({
199
+ let effectiveGasLimit = yield (0, svm_rent_1.maybeBumpForCeaAtaRent)({
187
200
  ctx,
188
201
  ueaAddress,
189
202
  targetChain,
@@ -196,16 +209,19 @@ function buildHopDescriptor(ctx, params, route, ueaAddress) {
196
209
  gasToken = result.gasToken;
197
210
  gasFee = result.gasFee;
198
211
  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})`);
215
+ }
199
216
  }
200
217
  return Object.assign(Object.assign({}, baseDescriptor), { targetChain, isSvmTarget: true, svmPayload,
201
218
  prc20Token,
202
219
  burnAmount,
203
220
  gasToken,
204
- gasFee,
205
- sizing });
221
+ gasFee, gasLimit: effectiveGasLimit, sizing });
206
222
  }
207
223
  // EVM path: Resolve CEA address + build CEA multicalls
208
- const { cea: ceaAddress } = yield (0, cea_utils_1.getCEAAddress)(ueaAddress, targetChain, (_e = ctx.rpcUrls[targetChain]) === null || _e === void 0 ? void 0 : _e[0]);
224
+ const { cea: ceaAddress } = yield (0, cea_utils_1.getCEAAddress)(ueaAddress, targetChain, (_g = ctx.rpcUrls[targetChain]) === null || _g === void 0 ? void 0 : _g[0]);
209
225
  // Migration path: raw MIGRATION_SELECTOR payload, no multicall wrapping
210
226
  if (params.migration) {
211
227
  const prc20Token = (0, helpers_1.getNativePRC20ForChain)(targetChain, ctx.pushNetwork);
@@ -235,7 +251,7 @@ function buildHopDescriptor(ctx, params, route, ueaAddress) {
235
251
  // When ERC-20 funds are provided with a single payload, auto-prepend a
236
252
  // transfer() call so the tokens minted to the CEA are forwarded to the
237
253
  // target address. This mirrors the Route 1 behavior in buildExecuteMulticall.
238
- if ((_f = params.funds) === null || _f === void 0 ? void 0 : _f.amount) {
254
+ if ((_h = params.funds) === null || _h === void 0 ? void 0 : _h.amount) {
239
255
  const token = params.funds.token;
240
256
  if (token && token.mechanism !== 'native') {
241
257
  const erc20Transfer = (0, viem_1.encodeFunctionData)({
@@ -255,7 +271,7 @@ function buildHopDescriptor(ctx, params, route, ueaAddress) {
255
271
  // native value to the CEA, and the multicall forwards it to the target.
256
272
  ceaMulticalls.push({
257
273
  to: target.address,
258
- value: (_g = params.value) !== null && _g !== void 0 ? _g : BigInt(0),
274
+ value: (_j = params.value) !== null && _j !== void 0 ? _j : BigInt(0),
259
275
  data: params.data,
260
276
  });
261
277
  }
@@ -271,7 +287,7 @@ function buildHopDescriptor(ctx, params, route, ueaAddress) {
271
287
  });
272
288
  }
273
289
  }
274
- else if ((_h = params.funds) === null || _h === void 0 ? void 0 : _h.amount) {
290
+ else if ((_k = params.funds) === null || _k === void 0 ? void 0 : _k.amount) {
275
291
  // Funds-only transfer (no data, no value). Without this branch the
276
292
  // CEA multicall is empty and relayed funds stay in the CEA instead
277
293
  // of reaching the caller's recipient.
@@ -303,7 +319,7 @@ function buildHopDescriptor(ctx, params, route, ueaAddress) {
303
319
  // Determine PRC-20 token and burn amount
304
320
  let prc20Token = selectors_1.ZERO_ADDRESS;
305
321
  let burnAmount = BigInt(0);
306
- if ((_j = params.funds) === null || _j === void 0 ? void 0 : _j.amount) {
322
+ if ((_l = params.funds) === null || _l === void 0 ? void 0 : _l.amount) {
307
323
  const token = params.funds.token;
308
324
  if (token) {
309
325
  prc20Token = push_chain_1.PushChain.utils.tokens.getPRC20Address(token).address;
@@ -345,6 +361,10 @@ function buildHopDescriptor(ctx, params, route, ueaAddress) {
345
361
  case route_detector_1.TransactionRoute.CEA_TO_PUSH: {
346
362
  // Route 3: Build CEA multicalls for sendUniversalTxFromCEA
347
363
  const sourceChain = params.from.chain;
364
+ const seedAmount = (_m = params.value) !== null && _m !== void 0 ? _m : BigInt(0);
365
+ if (isValueOnlyNativeSeedToOwnUea(params, ueaAddress)) {
366
+ return Object.assign(Object.assign({}, baseDescriptor), { sourceChain, burnAmount: BigInt(0), gasToken: selectors_1.ZERO_ADDRESS, gasFee: BigInt(0), nativeSeedOnly: true, nativeSeedAmount: seedAmount });
367
+ }
348
368
  // SVM chains use PDA-based CEA, not factory-deployed CEA
349
369
  if ((0, payload_builders_1.isSvmChain)(sourceChain)) {
350
370
  const lockerContract = chain_1.CHAIN_INFO[sourceChain].lockerContract;
@@ -361,7 +381,7 @@ function buildHopDescriptor(ctx, params, route, ueaAddress) {
361
381
  Buffer.from(ceaPda.toBytes()).toString('hex'));
362
382
  let amount = BigInt(0);
363
383
  let prc20Token;
364
- if (((_k = params.funds) === null || _k === void 0 ? void 0 : _k.amount) && params.funds.amount > BigInt(0)) {
384
+ if (((_o = params.funds) === null || _o === void 0 ? void 0 : _o.amount) && params.funds.amount > BigInt(0)) {
365
385
  amount = params.funds.amount;
366
386
  const token = params.funds.token;
367
387
  if (token && token.address) {
@@ -388,18 +408,21 @@ function buildHopDescriptor(ctx, params, route, ueaAddress) {
388
408
  gasFee = result.gasFee;
389
409
  sizing = result.sizing;
390
410
  }
391
- return Object.assign(Object.assign({}, baseDescriptor), { sourceChain, ceaAddress: ceaPdaHex, isSvmTarget: true, prc20Token, burnAmount: amount, gasToken,
411
+ return Object.assign(Object.assign({}, baseDescriptor), { sourceChain, ceaAddress: ceaPdaHex, isSvmTarget: true, prc20Token,
412
+ // Route 3 is payload-only on Push: the drain amount lives inside the
413
+ // SVM payload. Burning here would require the UEA to pre-hold pSOL.
414
+ burnAmount: BigInt(0), gasToken,
392
415
  gasFee,
393
416
  sizing });
394
417
  }
395
- const { cea: ceaAddress } = yield (0, cea_utils_1.getCEAAddress)(ueaAddress, sourceChain, (_l = ctx.rpcUrls[sourceChain]) === null || _l === void 0 ? void 0 : _l[0]);
418
+ const { cea: ceaAddress } = yield (0, cea_utils_1.getCEAAddress)(ueaAddress, sourceChain, (_p = ctx.rpcUrls[sourceChain]) === null || _p === void 0 ? void 0 : _p[0]);
396
419
  // CEA auto-deploys on-chain: Vault.finalizeUniversalTx calls CEAFactory.deployCEA()
397
420
  // if CEA doesn't exist yet. No SDK-side blocking needed.
398
421
  // Determine token/amount for the inbound
399
422
  let tokenAddress = selectors_1.ZERO_ADDRESS;
400
423
  let amount = BigInt(0);
401
424
  let nativeValue = BigInt(0);
402
- if ((_m = params.funds) === null || _m === void 0 ? void 0 : _m.amount) {
425
+ if ((_q = params.funds) === null || _q === void 0 ? void 0 : _q.amount) {
403
426
  const token = params.funds.token;
404
427
  if (token) {
405
428
  if (token.mechanism === 'native') {
@@ -429,7 +452,11 @@ function buildHopDescriptor(ctx, params, route, ueaAddress) {
429
452
  }
430
453
  return Object.assign(Object.assign({}, baseDescriptor), { sourceChain,
431
454
  ceaAddress,
432
- prc20Token, burnAmount: amount, gasToken,
455
+ prc20Token,
456
+ // Route 3 is payload-only on Push: the CEA-side sendUniversalTxToUEA
457
+ // carries `amount`. Burning here would require the UEA to pre-hold the
458
+ // token it is trying to receive from the external CEA.
459
+ burnAmount: BigInt(0), gasToken,
433
460
  gasFee,
434
461
  sizing });
435
462
  }
@@ -463,7 +490,9 @@ function classifyIntoSegments(hops) {
463
490
  ? currentSegment.targetChain === hop.targetChain && !hop.isSvmTarget
464
491
  : segType === 'INBOUND_FROM_CEA'
465
492
  ? currentSegment.sourceChain === hop.sourceChain &&
466
- !(0, payload_builders_1.isSvmChain)(hop.sourceChain)
493
+ !(0, payload_builders_1.isSvmChain)(hop.sourceChain) &&
494
+ isNativeSeedOnlyHop(currentSegment.hops[0]) ===
495
+ isNativeSeedOnlyHop(hop)
467
496
  : segType === 'PUSH_EXECUTION');
468
497
  if (canMerge && currentSegment) {
469
498
  // Merge into current segment
@@ -559,46 +588,128 @@ function getSegmentType(route) {
559
588
  return 'PUSH_EXECUTION';
560
589
  }
561
590
  }
562
- // ============================================================================
563
- // composeCascade
564
- // ============================================================================
565
- function composeCascade(ctx, segments, ueaAddress, ueaBalance, ueaNonce) {
591
+ function sumMulticallNativeValue(multicalls) {
592
+ return (multicalls !== null && multicalls !== void 0 ? multicalls : []).reduce((sum, mc) => { var _a; return sum + ((_a = mc.value) !== null && _a !== void 0 ? _a : BigInt(0)); }, BigInt(0));
593
+ }
594
+ function isEmptyPayloadData(data) {
595
+ return (data === undefined ||
596
+ data === '0x' ||
597
+ (Array.isArray(data) && data.length === 0));
598
+ }
599
+ function isValueOnlyNativeSeedToOwnUea(params, ueaAddress) {
600
+ var _a, _b;
601
+ return (((_a = params.value) !== null && _a !== void 0 ? _a : BigInt(0)) > BigInt(0) &&
602
+ !((_b = params.funds) === null || _b === void 0 ? void 0 : _b.amount) &&
603
+ isEmptyPayloadData(params.data) &&
604
+ typeof params.to === 'string' &&
605
+ params.to.toLowerCase() === ueaAddress.toLowerCase());
606
+ }
607
+ function isNativeSeedOnlyHop(hop) {
608
+ return (hop === null || hop === void 0 ? void 0 : hop.nativeSeedOnly) === true;
609
+ }
610
+ function isNativeSeedOnlySegment(segment) {
611
+ return (segment.type === 'INBOUND_FROM_CEA' &&
612
+ segment.hops.length > 0 &&
613
+ segment.hops.every((hop) => isNativeSeedOnlyHop(hop)));
614
+ }
615
+ function segmentNeedsOutboundGas(segment) {
616
+ return segment.type !== 'PUSH_EXECUTION' && !isNativeSeedOnlySegment(segment);
617
+ }
618
+ function sumNativeSeedAmount(hops) {
619
+ return hops.reduce((sum, hop) => { var _a; return sum + ((_a = hop.nativeSeedAmount) !== null && _a !== void 0 ? _a : BigInt(0)); }, BigInt(0));
620
+ }
621
+ function getCascadeNativeSeed(segments) {
622
+ var _a, _b, _c;
623
+ let total = BigInt(0);
624
+ for (const segment of segments) {
625
+ for (const hop of segment.hops) {
626
+ const value = (_a = hop.params.value) !== null && _a !== void 0 ? _a : BigInt(0);
627
+ if (isNativeSeedOnlyHop(hop)) {
628
+ total += (_b = hop.nativeSeedAmount) !== null && _b !== void 0 ? _b : value;
629
+ continue;
630
+ }
631
+ // In a CEA->Push hop that also supplies `funds`, `value` is native PC
632
+ // forwarded inside the inbound UEA payload. That value is available to
633
+ // later Push-side cascade calls and must be counted for sizing fresh UEAs.
634
+ if (segment.type === 'INBOUND_FROM_CEA' &&
635
+ value > BigInt(0) &&
636
+ ((_c = hop.params.funds) === null || _c === void 0 ? void 0 : _c.amount)) {
637
+ total += value;
638
+ }
639
+ }
640
+ }
641
+ return total;
642
+ }
643
+ function getInboundFundingForBurnToken(segments, beforeSegmentIndex, burnToken, pushNetwork) {
644
+ var _a;
645
+ let total = BigInt(0);
646
+ const normalizedBurnToken = burnToken.toLowerCase();
647
+ for (const segment of segments.slice(0, beforeSegmentIndex)) {
648
+ if (segment.type !== 'INBOUND_FROM_CEA')
649
+ continue;
650
+ for (const hop of segment.hops) {
651
+ const params = hop.params;
652
+ let fundedToken;
653
+ let fundedAmount = BigInt(0);
654
+ if ((_a = params.funds) === null || _a === void 0 ? void 0 : _a.amount) {
655
+ const token = params.funds.token;
656
+ if (token) {
657
+ fundedToken = push_chain_1.PushChain.utils.tokens.getPRC20Address(token).address;
658
+ fundedAmount = params.funds.amount;
659
+ }
660
+ }
661
+ else if (params.value &&
662
+ params.value > BigInt(0) &&
663
+ hop.sourceChain &&
664
+ !isNativeSeedOnlyHop(hop)) {
665
+ fundedToken = (0, helpers_1.getNativePRC20ForChain)(hop.sourceChain, pushNetwork);
666
+ fundedAmount = params.value;
667
+ }
668
+ if ((fundedToken === null || fundedToken === void 0 ? void 0 : fundedToken.toLowerCase()) === normalizedBurnToken) {
669
+ total += fundedAmount;
670
+ }
671
+ }
672
+ }
673
+ return total;
674
+ }
675
+ function composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce) {
566
676
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
567
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r;
677
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
568
678
  let accumulatedPushMulticalls = [];
679
+ let requiredNativeValue = BigInt(0);
569
680
  const gatewayPcAddress = (0, helpers_1.getUniversalGatewayPCAddress)();
681
+ const currentUeaBalance = ueaBalance !== null && ueaBalance !== void 0 ? ueaBalance : BigInt(0);
682
+ const cascadeNativeSeed = getCascadeNativeSeed(segments);
683
+ const effectiveUeaBalance = currentUeaBalance + cascadeNativeSeed;
684
+ if (cascadeNativeSeed > BigInt(0)) {
685
+ (0, context_1.printLog)(ctx, `composeCascade — counting cascade native seed ${cascadeNativeSeed.toString()} for cascade sizing (current UEA balance ${currentUeaBalance.toString()})`);
686
+ }
570
687
  // Compute per-outbound nativeValueForGas from UEA balance
571
688
  // Each outbound segment needs native value for the gas swap on the destination chain.
572
689
  // The contract refunds excess, so over-allocating is safe.
573
- const numOutbounds = segments.filter((s) => s.type !== 'PUSH_EXECUTION').length;
690
+ const numOutbounds = segments.filter(segmentNeedsOutboundGas).length;
574
691
  const CASCADE_GAS_RESERVE = BigInt(3e18); // 3 PC reserve for gas costs
575
692
  // Cascade pre-flight: catches the zero-balance silent-segment-zero failure
576
693
  // mode. When `ueaBalance <= CASCADE_GAS_RESERVE`, `perOutboundNativeValue`
577
694
  // is left undefined and downstream segments silently allocate value=0,
578
695
  // producing under-funded swaps that revert inside Uniswap. Throw early
579
696
  // with a structured error so the caller knows which path / hop failed.
580
- // Honor the per-call opt-out from the first non-PUSH segment (segments share
581
- // the same UniversalExecuteParams.options.allowUnderfundedSwap shape).
582
- const firstOutboundSegment = segments.find((s) => s.type !== 'PUSH_EXECUTION');
583
- const cascadeAllowUnderfunded = ((_d = (_c = (_b = (_a = firstOutboundSegment === null || firstOutboundSegment === void 0 ? void 0 : firstOutboundSegment.hops) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.params) === null || _c === void 0 ? void 0 : _c.options) === null || _d === void 0 ? void 0 : _d.allowUnderfundedSwap) ===
584
- true;
585
697
  if (numOutbounds > 0) {
586
698
  (0, preflight_1.runPreflight)({
587
699
  ctx,
588
700
  ueaAddress,
589
- ueaBalance: ueaBalance !== null && ueaBalance !== void 0 ? ueaBalance : BigInt(0),
701
+ ueaBalance: effectiveUeaBalance,
590
702
  // Required = at least one wei of allocatable PC after reserve. The
591
703
  // per-segment min-swap check below catches finer-grained shortfalls.
592
704
  requiredValue: BigInt(1),
593
705
  gasReserve: CASCADE_GAS_RESERVE,
594
706
  pathTag: 'CASCADE',
595
- allowUnderfundedSwap: cascadeAllowUnderfunded,
596
707
  });
597
708
  }
598
709
  let perOutboundNativeValue;
599
- if (ueaBalance && numOutbounds > 0 && ueaBalance > CASCADE_GAS_RESERVE) {
710
+ if (numOutbounds > 0 && effectiveUeaBalance > CASCADE_GAS_RESERVE) {
600
711
  perOutboundNativeValue =
601
- (ueaBalance - CASCADE_GAS_RESERVE) / BigInt(numOutbounds);
712
+ (effectiveUeaBalance - CASCADE_GAS_RESERVE) / BigInt(numOutbounds);
602
713
  }
603
714
  // Pool-price-based native-value override for SVM outbounds.
604
715
  // The flat split above works for EVM gas tokens (pBNB/pETH — cheap per unit),
@@ -614,7 +725,7 @@ function composeCascade(ctx, segments, ueaAddress, ueaBalance, ueaNonce) {
614
725
  s.gasFee > BigInt(0);
615
726
  });
616
727
  const svmNativeValueBySegment = new Map();
617
- if (svmSegments.length > 0 && ueaBalance && ueaBalance > CASCADE_GAS_RESERVE) {
728
+ if (svmSegments.length > 0 && effectiveUeaBalance > CASCADE_GAS_RESERVE) {
618
729
  const universalCoreAddress = yield ctx.pushClient.readContract({
619
730
  address: gatewayPcAddress,
620
731
  abi: abi_1.UNIVERSAL_GATEWAY_PC,
@@ -629,11 +740,11 @@ function composeCascade(ctx, segments, ueaAddress, ueaBalance, ueaNonce) {
629
740
  ? perOutboundNativeValue * BigInt(numEvmOutbounds)
630
741
  : BigInt(0);
631
742
  const outerSafety = BigInt(1e18); // 1 PC for outer-tx gas
632
- const svmTotal = ueaBalance > evmReservation + outerSafety
633
- ? ueaBalance - evmReservation - outerSafety
743
+ const svmTotal = effectiveUeaBalance > evmReservation + outerSafety
744
+ ? effectiveUeaBalance - evmReservation - outerSafety
634
745
  : BigInt(0);
635
746
  const perSvmCap = svmTotal / BigInt(svmSegments.length);
636
- const UNCAPPED_BALANCE = BigInt(1e30); // effectively disable estimator's internal cap
747
+ const UNCAPPED_BALANCE = BigInt('1000000000000000000000000000000'); // effectively disable estimator's internal cap
637
748
  for (const seg of svmSegments) {
638
749
  // Cascade-level segment index (position in `segments`, not in `svmSegments`)
639
750
  // so the error's `segmentIndex` lines up with what the caller sees.
@@ -643,8 +754,13 @@ function composeCascade(ctx, segments, ueaAddress, ueaBalance, ueaNonce) {
643
754
  (0, preflight_1.maybeFireSvmWarnThreshold)(ctx, estimated, seg.gasToken, 'CASCADE');
644
755
  const flat = perOutboundNativeValue !== null && perOutboundNativeValue !== void 0 ? perOutboundNativeValue : BigInt(0);
645
756
  let value = estimated > flat ? estimated : flat;
646
- if (perSvmCap > BigInt(0) && value > perSvmCap)
647
- value = perSvmCap;
757
+ if (perSvmCap > BigInt(0) && value > perSvmCap) {
758
+ const cappedValue = perSvmCap >= estimated ? perSvmCap : estimated;
759
+ if (cappedValue !== perSvmCap) {
760
+ (0, context_1.printLog)(ctx, `composeCascade — SVM outbound to ${(_a = seg.hops[0]) === null || _a === void 0 ? void 0 : _a.targetChain}: perSvmCap=${perSvmCap} is below live pool quote=${estimated}; using quote to avoid underfunded gas swap`);
761
+ }
762
+ value = cappedValue;
763
+ }
648
764
  // Upward-allocation ceiling (mirrors R3 SVM Fix #4 in route-handlers.ts:1690).
649
765
  // When UEA balance is huge, the flat split or perSvmCap can produce a value
650
766
  // far above what the swap actually needs. Submitting that into a Uniswap
@@ -659,16 +775,16 @@ function composeCascade(ctx, segments, ueaAddress, ueaBalance, ueaNonce) {
659
775
  ? estimated * BigInt(5)
660
776
  : UPWARD_BASE_CEILING_CASCADE_SVM;
661
777
  if (value > upwardCeilingCascadeSvm) {
662
- (0, context_1.printLog)(ctx, `composeCascade — SVM outbound to ${(_e = seg.hops[0]) === null || _e === void 0 ? void 0 : _e.targetChain}: capping value from ${value} to upwardCeiling=${upwardCeilingCascadeSvm} (pool quote=${estimated})`);
778
+ (0, context_1.printLog)(ctx, `composeCascade — SVM outbound to ${(_b = seg.hops[0]) === null || _b === void 0 ? void 0 : _b.targetChain}: capping value from ${value} to upwardCeiling=${upwardCeilingCascadeSvm} (pool quote=${estimated})`);
663
779
  value = upwardCeilingCascadeSvm;
664
780
  }
665
- (0, context_1.printLog)(ctx, `composeCascade — SVM outbound to ${(_f = seg.hops[0]) === null || _f === void 0 ? void 0 : _f.targetChain}: gasFee=${seg.gasFee}, estimatedWpc=${estimated}, flat=${flat}, perSvmCap=${perSvmCap}, upwardCeiling=${upwardCeilingCascadeSvm}, chosen=${value}`);
781
+ (0, context_1.printLog)(ctx, `composeCascade — SVM outbound to ${(_c = seg.hops[0]) === null || _c === void 0 ? void 0 : _c.targetChain}: gasFee=${seg.gasFee}, estimatedWpc=${estimated}, flat=${flat}, perSvmCap=${perSvmCap}, upwardCeiling=${upwardCeilingCascadeSvm}, chosen=${value}`);
666
782
  // Per-segment minimum-viable-swap check: if the chosen `value` is below
667
783
  // the live pool quote (`estimated`), the cap squeezed the budget below
668
784
  // what the swap actually needs and the segment will revert inside
669
785
  // Uniswap with STF. Throw cleanly with `segmentIndex` so the caller
670
786
  // knows which hop's pool is misbehaving.
671
- if (estimated > BigInt(0) && value < estimated && !cascadeAllowUnderfunded) {
787
+ if (estimated > BigInt(0) && value < estimated) {
672
788
  const shortfall = estimated - value;
673
789
  (0, context_2.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_003_04, estimated, value, shortfall, ueaAddress, 'CASCADE', { kind: 'NATIVE', segmentIndex: cascadeIdx });
674
790
  throw new errors_1.InsufficientUEABalanceError({
@@ -689,6 +805,11 @@ function composeCascade(ctx, segments, ueaAddress, ueaBalance, ueaNonce) {
689
805
  switch (segment.type) {
690
806
  case 'PUSH_EXECUTION': {
691
807
  // Prepend Push Chain multicalls to accumulated
808
+ const seedAmount = sumNativeSeedAmount(segment.hops);
809
+ if (seedAmount > requiredNativeValue) {
810
+ requiredNativeValue = seedAmount;
811
+ }
812
+ requiredNativeValue += sumMulticallNativeValue(segment.mergedPushMulticalls);
692
813
  accumulatedPushMulticalls = [
693
814
  ...(segment.mergedPushMulticalls || []),
694
815
  ...accumulatedPushMulticalls,
@@ -703,7 +824,7 @@ function composeCascade(ctx, segments, ueaAddress, ueaBalance, ueaNonce) {
703
824
  const isMigration = (firstHop === null || firstHop === void 0 ? void 0 : firstHop.isMigration) === true;
704
825
  if (isSvmSegment) {
705
826
  // SVM: use the pre-built SVM payload from the hop descriptor
706
- outboundPayload = (_g = firstHop.svmPayload) !== null && _g !== void 0 ? _g : '0x';
827
+ outboundPayload = (_d = firstHop.svmPayload) !== null && _d !== void 0 ? _d : '0x';
707
828
  // For SVM, to.address IS the target (program for execute, recipient for withdraw)
708
829
  const svmTarget = firstHop.params.to;
709
830
  targetForOutbound = svmTarget.address;
@@ -736,12 +857,24 @@ function composeCascade(ctx, segments, ueaAddress, ueaBalance, ueaNonce) {
736
857
  });
737
858
  const sufficient = onHand >= segBurnAmount;
738
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 });
739
- if (!sufficient && !cascadeAllowUnderfunded) {
740
- const shortfall = segBurnAmount - onHand;
741
- (0, context_2.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_003_04, segBurnAmount, onHand, shortfall, ueaAddress, 'CASCADE', { kind: 'PRC20', burnToken: segBurnToken, segmentIndex: segIdxLoop });
860
+ const priorPushExecutionMayFundBurn = segments
861
+ .slice(0, segIdxLoop)
862
+ .some((s) => s.type === 'PUSH_EXECUTION');
863
+ const priorInboundFundingForBurn = getInboundFundingForBurnToken(segments, segIdxLoop, segBurnToken, ctx.pushNetwork);
864
+ const priorInboundMayFundBurn = onHand + priorInboundFundingForBurn >= segBurnAmount;
865
+ if (!sufficient &&
866
+ !priorPushExecutionMayFundBurn &&
867
+ !priorInboundMayFundBurn) {
868
+ const projectedOnHand = onHand + priorInboundFundingForBurn;
869
+ 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
+ });
742
875
  throw new errors_1.InsufficientUEABalanceError({
743
876
  required: segBurnAmount,
744
- available: onHand,
877
+ available: projectedOnHand,
745
878
  shortfall,
746
879
  ueaAddress,
747
880
  pathTag: 'CASCADE',
@@ -750,9 +883,15 @@ function composeCascade(ctx, segments, ueaAddress, ueaBalance, ueaNonce) {
750
883
  segmentIndex: segIdxLoop,
751
884
  });
752
885
  }
886
+ else if (!sufficient && priorPushExecutionMayFundBurn) {
887
+ (0, context_1.printLog)(ctx, `composeCascade — skipping PRC-20 preflight failure for segment ${segIdxLoop}; earlier Push execution may fund ${segBurnToken} before this outbound runs`);
888
+ }
889
+ else if (!sufficient && priorInboundMayFundBurn) {
890
+ (0, context_1.printLog)(ctx, `composeCascade — skipping PRC-20 preflight failure for segment ${segIdxLoop}; earlier inbound funds ${priorInboundFundingForBurn.toString()} units of ${segBurnToken} before this outbound runs`);
891
+ }
753
892
  }
754
893
  // Build outbound request
755
- const outboundReq = (0, payload_builders_1.buildOutboundRequest)(targetForOutbound, segment.prc20Token || selectors_1.ZERO_ADDRESS, segment.totalBurnAmount || BigInt(0), (_h = segment.gasLimit) !== null && _h !== void 0 ? _h : BigInt(0), outboundPayload, ueaAddress);
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);
756
895
  // Build approval + outbound multicalls
757
896
  const segGasFee = segment.gasFee || BigInt(0);
758
897
  const svmOverride = svmNativeValueBySegment.get(segment);
@@ -762,13 +901,14 @@ function composeCascade(ctx, segments, ueaAddress, ueaBalance, ueaNonce) {
762
901
  // across outbound segments. The contract refunds excess via
763
902
  // swapAndBurnGas, so overshooting is safe; undershooting causes STF.
764
903
  let evmFallbackValue;
765
- if (ueaBalance && ueaBalance > BigInt(0)) {
766
- evmFallbackValue = ueaBalance / BigInt(Math.max(numOutbounds, 1));
904
+ if (effectiveUeaBalance > BigInt(0)) {
905
+ evmFallbackValue =
906
+ effectiveUeaBalance / BigInt(Math.max(numOutbounds, 1));
767
907
  }
768
908
  else {
769
909
  evmFallbackValue = segGasFee * BigInt(1000000);
770
910
  }
771
- let nativeValueForGas = (_j = svmOverride !== null && svmOverride !== void 0 ? svmOverride : perOutboundNativeValue) !== null && _j !== void 0 ? _j : evmFallbackValue;
911
+ let nativeValueForGas = (_f = svmOverride !== null && svmOverride !== void 0 ? svmOverride : perOutboundNativeValue) !== null && _f !== void 0 ? _f : evmFallbackValue;
772
912
  // SDK 5.2 Case C: when the segment's sizing is category C, compose
773
913
  // R2/OUTBOUND_TO_CEA segments no longer apply Case A/B/C sizing
774
914
  // (scoped to R1 + R3 only). segment.sizing may still be populated
@@ -784,6 +924,7 @@ function composeCascade(ctx, segments, ueaAddress, ueaBalance, ueaNonce) {
784
924
  gatewayPcAddress,
785
925
  outboundRequest: outboundReq,
786
926
  });
927
+ requiredNativeValue += sumMulticallNativeValue(outboundMulticalls);
787
928
  // Prepend to accumulated
788
929
  accumulatedPushMulticalls = [
789
930
  ...outboundMulticalls,
@@ -794,13 +935,21 @@ function composeCascade(ctx, segments, ueaAddress, ueaBalance, ueaNonce) {
794
935
  case 'INBOUND_FROM_CEA': {
795
936
  // The accumulated multicalls = what runs on Push Chain AFTER inbound arrives.
796
937
  // Wrap in UniversalPayload struct with correct UEA nonce for the relay.
938
+ if (isNativeSeedOnlySegment(segment)) {
939
+ const seedAmount = sumNativeSeedAmount(segment.hops);
940
+ if (seedAmount > requiredNativeValue) {
941
+ requiredNativeValue = seedAmount;
942
+ }
943
+ (0, context_1.printLog)(ctx, `composeCascade — value-only CEA_TO_PUSH seed ${seedAmount.toString()} folded into root Push execution; no source-chain outbound needed`);
944
+ break;
945
+ }
797
946
  // Build push multicalls from EVERY merged hop's own data in original
798
947
  // order (e.g. counter.increment() + value transfer). Multi-hop R3
799
948
  // segments (same sourceChain) bundle all target ops into the single
800
949
  // UEA multicall so no nested outbound is needed.
801
950
  const mergedHopMulticalls = [];
802
951
  for (const h of segment.hops) {
803
- if (((_k = h === null || h === void 0 ? void 0 : h.params) === null || _k === void 0 ? void 0 : _k.data) || ((_l = h === null || h === void 0 ? void 0 : h.params) === null || _l === void 0 ? void 0 : _l.value)) {
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)) {
804
953
  const hopCalls = (0, payload_builders_1.buildExecuteMulticall)({
805
954
  execute: {
806
955
  to: h.params.to,
@@ -813,6 +962,7 @@ function composeCascade(ctx, segments, ueaAddress, ueaBalance, ueaNonce) {
813
962
  }
814
963
  }
815
964
  if (mergedHopMulticalls.length > 0) {
965
+ requiredNativeValue += sumMulticallNativeValue(mergedHopMulticalls);
816
966
  accumulatedPushMulticalls = [
817
967
  ...mergedHopMulticalls,
818
968
  ...accumulatedPushMulticalls,
@@ -824,7 +974,9 @@ function composeCascade(ctx, segments, ueaAddress, ueaBalance, ueaNonce) {
824
974
  if (accumulatedPushMulticalls.length > 0) {
825
975
  const multicallPayload = (0, payload_builder_1.buildMulticallPayloadData)(ctx, ueaAddress, accumulatedPushMulticalls);
826
976
  // +1: the outbound tx consumes one nonce via execute()
827
- intermediatePayload = (0, payload_builders_1.buildInboundUniversalPayload)(multicallPayload, { nonce: (ueaNonce !== null && ueaNonce !== void 0 ? ueaNonce : BigInt(0)) + BigInt(1) });
977
+ intermediatePayload = (0, payload_builders_1.buildInboundUniversalPayload)(multicallPayload, {
978
+ nonce: (ueaNonce !== null && ueaNonce !== void 0 ? ueaNonce : BigInt(0)) + BigInt(1),
979
+ });
828
980
  }
829
981
  const hop = segment.hops[0];
830
982
  const sourceChain = hop.sourceChain;
@@ -841,7 +993,7 @@ function composeCascade(ctx, segments, ueaAddress, ueaBalance, ueaNonce) {
841
993
  let drainAmount = BigInt(0);
842
994
  let tokenMintHex;
843
995
  const params = hop.params;
844
- if (((_m = params.funds) === null || _m === void 0 ? void 0 : _m.amount) && params.funds.amount > BigInt(0)) {
996
+ if (((_j = params.funds) === null || _j === void 0 ? void 0 : _j.amount) && params.funds.amount > BigInt(0)) {
845
997
  drainAmount = params.funds.amount;
846
998
  const token = params.funds.token;
847
999
  if (token && token.address) {
@@ -870,11 +1022,12 @@ function composeCascade(ctx, segments, ueaAddress, ueaBalance, ueaNonce) {
870
1022
  });
871
1023
  const svmBurnAmount = segment.totalBurnAmount || BigInt(0);
872
1024
  const outboundReq = (0, payload_builders_1.buildOutboundRequest)(gatewayProgramHex, segment.prc20Token ||
873
- (0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork), svmBurnAmount, (_o = segment.gasLimit) !== null && _o !== void 0 ? _o : BigInt(0), svmPayload, ueaAddress);
1025
+ (0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork), svmBurnAmount, (_k = segment.gasLimit) !== null && _k !== void 0 ? _k : BigInt(0), svmPayload, ueaAddress);
874
1026
  const inboundGasFee = segment.gasFee || BigInt(0);
875
1027
  let svmInboundFallback;
876
- if (ueaBalance && ueaBalance > BigInt(0)) {
877
- svmInboundFallback = ueaBalance / BigInt(Math.max(numOutbounds, 1));
1028
+ if (effectiveUeaBalance > BigInt(0)) {
1029
+ svmInboundFallback =
1030
+ effectiveUeaBalance / BigInt(Math.max(numOutbounds, 1));
878
1031
  }
879
1032
  else {
880
1033
  svmInboundFallback = inboundGasFee * BigInt(1000000);
@@ -883,7 +1036,7 @@ function composeCascade(ctx, segments, ueaAddress, ueaBalance, ueaNonce) {
883
1036
  // SDK 5.2 R3-style Case C bump: when the inbound segment's sizing
884
1037
  // is C, top up the swap budget by the overflow. No bridge-swap —
885
1038
  // R3 has no destination funds delivery.
886
- if (((_p = segment.sizing) === null || _p === void 0 ? void 0 : _p.category) === 'C' &&
1039
+ if (((_l = segment.sizing) === null || _l === void 0 ? void 0 : _l.category) === 'C' &&
887
1040
  segment.sizing.overflowNativePc > BigInt(0)) {
888
1041
  const bumped = svmInboundNativeValue + segment.sizing.overflowNativePc;
889
1042
  (0, context_1.printLog)(ctx, `composeCascade — INBOUND_FROM_CEA (SVM) Case C: bumping nativeValueForGas from ${svmInboundNativeValue} to ${bumped} (overflow=${segment.sizing.overflowNativePc})`);
@@ -899,6 +1052,7 @@ function composeCascade(ctx, segments, ueaAddress, ueaBalance, ueaNonce) {
899
1052
  gatewayPcAddress,
900
1053
  outboundRequest: outboundReq,
901
1054
  });
1055
+ requiredNativeValue += sumMulticallNativeValue(outboundMulticalls);
902
1056
  accumulatedPushMulticalls = [...outboundMulticalls];
903
1057
  break;
904
1058
  }
@@ -918,7 +1072,7 @@ function composeCascade(ctx, segments, ueaAddress, ueaBalance, ueaNonce) {
918
1072
  let amount = BigInt(0);
919
1073
  for (const mergedHop of segment.hops) {
920
1074
  const hParams = mergedHop.params;
921
- if ((_q = hParams.funds) === null || _q === void 0 ? void 0 : _q.amount) {
1075
+ if ((_m = hParams.funds) === null || _m === void 0 ? void 0 : _m.amount) {
922
1076
  const token = hParams.funds.token;
923
1077
  if (token) {
924
1078
  if (token.mechanism === 'native') {
@@ -965,7 +1119,8 @@ function composeCascade(ctx, segments, ueaAddress, ueaBalance, ueaNonce) {
965
1119
  amount += hParams.value;
966
1120
  }
967
1121
  }
968
- // Build sendUniversalTxToUEA self-call on CEA (to=CEA, value=0)
1122
+ // Build sendUniversalTxToUEA self-call on CEA. CEA self-calls must
1123
+ // always use value=0; native funds are spent from the CEA balance.
969
1124
  const sendCall = (0, payload_builders_1.buildSendUniversalTxToUEA)(ceaAddress, tokenAddress, amount, intermediatePayload, ceaAddress);
970
1125
  ceaMulticalls.push(sendCall);
971
1126
  // Wrap CEA multicall in outbound from Push Chain
@@ -974,17 +1129,17 @@ function composeCascade(ctx, segments, ueaAddress, ueaBalance, ueaNonce) {
974
1129
  // gasLimit — UGPC.sendUniversalTxOutbound reverts with
975
1130
  // "Missing or invalid parameters" on 0. When the hop didn't supply
976
1131
  // one (typical for a pure-R3 leg composed inside a cascade), fall
977
- // back to DEFAULT_OUTBOUND_GAS_LIMIT just like the initial outbound
978
- // path does.
1132
+ // back to the EVM Route 3 default.
979
1133
  const effectiveGasLimit = segment.gasLimit && segment.gasLimit > BigInt(0)
980
1134
  ? segment.gasLimit
981
- : selectors_1.DEFAULT_OUTBOUND_GAS_LIMIT;
1135
+ : selectors_1.DEFAULT_CEA_TO_PUSH_GAS_LIMIT;
982
1136
  const outboundReq = (0, payload_builders_1.buildOutboundRequest)(ceaAddress, segment.prc20Token ||
983
1137
  (0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork), segment.totalBurnAmount || BigInt(0), effectiveGasLimit, ceaPayload, ueaAddress);
984
1138
  const inboundGasFee = segment.gasFee || BigInt(0);
985
1139
  let evmInboundFallback;
986
- if (ueaBalance && ueaBalance > BigInt(0)) {
987
- evmInboundFallback = ueaBalance / BigInt(Math.max(numOutbounds, 1));
1140
+ if (effectiveUeaBalance > BigInt(0)) {
1141
+ evmInboundFallback =
1142
+ effectiveUeaBalance / BigInt(Math.max(numOutbounds, 1));
988
1143
  }
989
1144
  else {
990
1145
  evmInboundFallback = inboundGasFee * BigInt(1000000);
@@ -993,7 +1148,7 @@ function composeCascade(ctx, segments, ueaAddress, ueaBalance, ueaNonce) {
993
1148
  // SDK 5.2 R3-style Case C bump: when the inbound segment's sizing
994
1149
  // is C, top up the swap budget by the overflow. No bridge-swap —
995
1150
  // R3 has no destination funds delivery.
996
- if (((_r = segment.sizing) === null || _r === void 0 ? void 0 : _r.category) === 'C' &&
1151
+ if (((_o = segment.sizing) === null || _o === void 0 ? void 0 : _o.category) === 'C' &&
997
1152
  segment.sizing.overflowNativePc > BigInt(0)) {
998
1153
  const bumped = evmInboundNativeValue + segment.sizing.overflowNativePc;
999
1154
  (0, context_1.printLog)(ctx, `composeCascade — INBOUND_FROM_CEA (EVM) Case C: bumping nativeValueForGas from ${evmInboundNativeValue} to ${bumped} (overflow=${segment.sizing.overflowNativePc})`);
@@ -1009,19 +1164,29 @@ function composeCascade(ctx, segments, ueaAddress, ueaBalance, ueaNonce) {
1009
1164
  gatewayPcAddress,
1010
1165
  outboundRequest: outboundReq,
1011
1166
  });
1167
+ requiredNativeValue += sumMulticallNativeValue(outboundMulticalls);
1012
1168
  // Reset accumulated -- everything is now inside this outbound
1013
1169
  accumulatedPushMulticalls = [...outboundMulticalls];
1014
1170
  break;
1015
1171
  }
1016
1172
  }
1017
1173
  }
1018
- return accumulatedPushMulticalls;
1174
+ return {
1175
+ multicalls: accumulatedPushMulticalls,
1176
+ requiredNativeValue,
1177
+ };
1178
+ });
1179
+ }
1180
+ function composeCascade(ctx, segments, ueaAddress, ueaBalance, ueaNonce) {
1181
+ return tslib_1.__awaiter(this, void 0, void 0, function* () {
1182
+ const result = yield composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce);
1183
+ return result.multicalls;
1019
1184
  });
1020
1185
  }
1021
1186
  // ============================================================================
1022
1187
  // createCascadedBuilder
1023
1188
  // ============================================================================
1024
- function createCascadedBuilder(ctx, preparedTxs, callbacks) {
1189
+ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook) {
1025
1190
  return {
1026
1191
  send: () => tslib_1.__awaiter(this, void 0, void 0, function* () {
1027
1192
  var _a;
@@ -1031,8 +1196,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks) {
1031
1196
  // Classify into segments
1032
1197
  const segments = classifyIntoSegments(hops);
1033
1198
  // Check if this is a single-hop Route 1 (no composition needed)
1034
- if (preparedTxs.length === 1 &&
1035
- preparedTxs[0].route === 'UOA_TO_PUSH') {
1199
+ if (preparedTxs.length === 1 && preparedTxs[0].route === 'UOA_TO_PUSH') {
1036
1200
  const response = yield callbacks.executeFn(hops[0].params);
1037
1201
  const singleRoute1Result = {
1038
1202
  initialTxHash: response.hash,
@@ -1066,8 +1230,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks) {
1066
1230
  return singleRoute1Result;
1067
1231
  }
1068
1232
  // Check if single-hop Route 2 (just execute directly)
1069
- if (preparedTxs.length === 1 &&
1070
- preparedTxs[0].route === 'UOA_TO_CEA') {
1233
+ if (preparedTxs.length === 1 && preparedTxs[0].route === 'UOA_TO_CEA') {
1071
1234
  const response = yield callbacks.executeFn(hops[0].params);
1072
1235
  const targetChain = hops[0].targetChain || (0, helpers_1.getPushChainForNetwork)(ctx.pushNetwork);
1073
1236
  const singleRoute2Result = {
@@ -1111,8 +1274,11 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks) {
1111
1274
  ? yield (0, uea_manager_1.getUEANonce)(ctx, ueaAddress)
1112
1275
  : BigInt(0);
1113
1276
  let composedMulticalls;
1277
+ let cascadeRequiredNativeValue = BigInt(0);
1114
1278
  try {
1115
- composedMulticalls = yield composeCascade(ctx, segments, ueaAddress, ueaBalance, ueaNonceCascade);
1279
+ const composed = yield composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonceCascade);
1280
+ composedMulticalls = composed.multicalls;
1281
+ cascadeRequiredNativeValue = composed.requiredNativeValue;
1116
1282
  }
1117
1283
  catch (err) {
1118
1284
  // Pre-flight or per-segment min-swap failure inside composeCascade
@@ -1129,6 +1295,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks) {
1129
1295
  // Execute the composed multicall as a single Push Chain tx
1130
1296
  const executeParams = {
1131
1297
  to: ueaAddress,
1298
+ value: cascadeRequiredNativeValue,
1132
1299
  data: composedMulticalls,
1133
1300
  };
1134
1301
  const response = yield callbacks.executeFn(executeParams);
@@ -1136,7 +1303,9 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks) {
1136
1303
  const hopInfos = hops.map((hop, index) => ({
1137
1304
  hopIndex: index,
1138
1305
  route: hop.route,
1139
- executionChain: hop.targetChain || hop.sourceChain || (0, helpers_1.getPushChainForNetwork)(ctx.pushNetwork),
1306
+ executionChain: hop.targetChain ||
1307
+ hop.sourceChain ||
1308
+ (0, helpers_1.getPushChainForNetwork)(ctx.pushNetwork),
1140
1309
  status: 'pending',
1141
1310
  }));
1142
1311
  // Mark first hop as submitted
@@ -1150,8 +1319,8 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks) {
1150
1319
  hops: hopInfos,
1151
1320
  hopCount: hops.length,
1152
1321
  waitForAll: (opts) => tslib_1.__awaiter(this, void 0, void 0, function* () {
1153
- var _a, _b, _c, _d, _e;
1154
- const { pollingIntervalMs = 10000, timeout = 300000, progressHook: cascadeProgressHook, eventHook, } = opts || {};
1322
+ var _a, _b, _c, _d, _e, _f, _g;
1323
+ const { pollingIntervalMs = 10000, timeout = 300000, progressHook: cascadeProgressHook, eventHook = defaultEventHook, } = opts || {};
1155
1324
  const startTime = Date.now();
1156
1325
  // Per-hop progress emission. Fans out cascade markers to BOTH the
1157
1326
  // explicit `eventHook` (passed into waitForAll) AND the init-time
@@ -1180,9 +1349,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks) {
1180
1349
  if (!isMulti)
1181
1350
  return;
1182
1351
  const n = hopIndex0 + 1;
1183
- const fromChain = hopIndex0 === 0
1184
- ? signerOriginChain
1185
- : chainLabels[hopIndex0 - 1];
1352
+ const fromChain = hopIndex0 === 0 ? signerOriginChain : chainLabels[hopIndex0 - 1];
1186
1353
  const toChain = chainLabels[hopIndex0];
1187
1354
  emitHopEvent(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_002_01](n, totalHops, fromChain, toChain));
1188
1355
  };
@@ -1229,49 +1396,13 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks) {
1229
1396
  elapsed: Date.now() - startTime,
1230
1397
  });
1231
1398
  yield response.wait();
1232
- // Mark all Push Chain (Route 1) hops as confirmed
1233
- for (const hop of hopInfos) {
1234
- if (hop.route === 'UOA_TO_PUSH') {
1235
- hop.status = 'confirmed';
1236
- hop.txHash = response.hash;
1237
- cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
1238
- hopIndex: hop.hopIndex,
1239
- route: hop.route,
1240
- chain: (0, helpers_1.getPushChainForNetwork)(ctx.pushNetwork),
1241
- status: 'confirmed',
1242
- txHash: response.hash,
1243
- elapsed: Date.now() - startTime,
1244
- });
1245
- emitHopComplete(hop.hopIndex);
1246
- }
1247
- }
1248
- // 2. Track outbound hops (Route 2: UOA_TO_CEA)
1249
- // Hops after a CEA_TO_PUSH are "child outbounds" — they execute inside
1250
- // the inbound payload on Push Chain and live under a DIFFERENT utx_id
1251
- // (the inbound UTX, not the parent). We can't track them via the parent
1252
- // utx_id polling, so we auto-confirm them.
1253
- const ceaToPushIndex = hopInfos.findIndex((h) => h.route === 'CEA_TO_PUSH');
1254
- const outboundHops = hopInfos.filter((h, i) => {
1255
- if (h.route !== 'UOA_TO_CEA')
1256
- return false;
1257
- // Child outbounds (after CEA_TO_PUSH) live under the inbound UTX,
1258
- // not the parent — auto-confirm since we can't poll them here.
1259
- if (ceaToPushIndex >= 0 && i > ceaToPushIndex) {
1260
- h.status = 'confirmed';
1261
- cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
1262
- hopIndex: h.hopIndex,
1263
- route: h.route,
1264
- chain: h.executionChain,
1265
- status: 'confirmed',
1266
- elapsed: Date.now() - startTime,
1267
- });
1268
- return false;
1269
- }
1270
- return true;
1271
- });
1272
- if (outboundHops.length > 0) {
1399
+ const ceaToPushIndex = hopInfos.findIndex((h, index) => h.route === 'CEA_TO_PUSH' && !isNativeSeedOnlyHop(hops[index]));
1400
+ 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;
1403
+ if (outboundHops.length === 0)
1404
+ return null;
1273
1405
  if (outboundHops.length === 1) {
1274
- // Single direct outbound hop: use the existing V1-based tracking
1275
1406
  const hop = outboundHops[0];
1276
1407
  const hopHooks = (0, progress_route_hooks_1.pickWaitHooks)(hop.route);
1277
1408
  const remainingTimeout = timeout - (Date.now() - startTime);
@@ -1303,10 +1434,12 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks) {
1303
1434
  });
1304
1435
  let lastEmittedStatus;
1305
1436
  try {
1306
- const outboundDetails = yield callbacks.waitForOutboundTxFn(response.hash, {
1437
+ const outboundDetails = yield callbacks.waitForOutboundTxFn(pushTxHash, {
1307
1438
  initialWaitMs: Math.min(60000, remainingTimeout),
1308
1439
  pollingIntervalMs,
1309
1440
  timeout: remainingTimeout,
1441
+ _resolvedSubTxId: resolvedSubTxId,
1442
+ _expectedDestinationChain: hop.executionChain,
1310
1443
  progressHook: (event) => {
1311
1444
  cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
1312
1445
  hopIndex: hop.hopIndex,
@@ -1322,17 +1455,24 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks) {
1322
1455
  }
1323
1456
  },
1324
1457
  });
1458
+ // `outboundDetails.externalTxHash` from waitForOutboundTx
1459
+ // is the raw `0x`-hex form (internal canonical). For the
1460
+ // user-facing `hop.txHash`, `hop.outboundDetails`, and
1461
+ // cascade progress event, normalize to base58 for SVM so
1462
+ // 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;
1464
+ const userFacingOutboundDetails = Object.assign(Object.assign({}, outboundDetails), { externalTxHash: displayTxHash });
1325
1465
  hop.status = 'confirmed';
1326
- hop.txHash = outboundDetails.externalTxHash;
1327
- hop.outboundDetails = outboundDetails;
1328
- emitHopEvent(hopHooks.success(outboundDetails));
1466
+ hop.txHash = displayTxHash;
1467
+ hop.outboundDetails = userFacingOutboundDetails;
1468
+ emitHopEvent(hopHooks.success(userFacingOutboundDetails));
1329
1469
  emitHopComplete(hop.hopIndex);
1330
1470
  cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
1331
1471
  hopIndex: hop.hopIndex,
1332
1472
  route: hop.route,
1333
1473
  chain: hop.executionChain,
1334
1474
  status: 'confirmed',
1335
- txHash: outboundDetails.externalTxHash,
1475
+ txHash: displayTxHash,
1336
1476
  elapsed: Date.now() - startTime,
1337
1477
  });
1338
1478
  }
@@ -1364,19 +1504,17 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks) {
1364
1504
  }
1365
1505
  }
1366
1506
  else {
1367
- // Multiple outbound hops: use V2 API which returns outboundTx[]
1368
- // Emit "Awaiting External Chain" once per outbound hop before
1369
- // polling begins, using the route-specific awaiting hook.
1370
1507
  for (const outHop of outboundHops) {
1371
1508
  emitHopStart(outHop.hopIndex);
1372
1509
  emitHopEvent((0, progress_route_hooks_1.pickWaitHooks)(outHop.route).awaiting(outHop.executionChain));
1373
1510
  }
1374
1511
  const perHopLastStatus = new Map();
1375
1512
  const confirmedHops = new Set();
1376
- const allOutboundDetails = yield callbacks.waitForAllOutboundTxsFn(response.hash, outboundHops, {
1513
+ const allOutboundDetails = yield callbacks.waitForAllOutboundTxsFn(pushTxHash, outboundHops, {
1377
1514
  initialWaitMs: Math.min(60000, timeout - (Date.now() - startTime)),
1378
1515
  pollingIntervalMs,
1379
1516
  timeout: timeout - (Date.now() - startTime),
1517
+ _resolvedSubTxId: resolvedSubTxId,
1380
1518
  progressHook: (event) => {
1381
1519
  var _a, _b;
1382
1520
  cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
@@ -1390,8 +1528,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks) {
1390
1528
  const matchedHop = outboundHops.find((h) => h.hopIndex === event.hopIndex);
1391
1529
  const eventHooks = (0, progress_route_hooks_1.pickWaitHooks)(matchedHop === null || matchedHop === void 0 ? void 0 : matchedHop.route);
1392
1530
  const prev = perHopLastStatus.get(event.hopIndex);
1393
- if (event.status === 'polling' &&
1394
- prev !== 'polling') {
1531
+ if (event.status === 'polling' && prev !== 'polling') {
1395
1532
  perHopLastStatus.set(event.hopIndex, 'polling');
1396
1533
  emitHopEvent(eventHooks.polling((_b = (_a = matchedHop === null || matchedHop === void 0 ? void 0 : matchedHop.executionChain) !== null && _a !== void 0 ? _a : event.chain) !== null && _b !== void 0 ? _b : '', Date.now() - startTime));
1397
1534
  }
@@ -1412,12 +1549,12 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks) {
1412
1549
  },
1413
1550
  });
1414
1551
  if (!allOutboundDetails.success) {
1415
- const failedIdx = (_d = allOutboundDetails.failedAt) !== null && _d !== void 0 ? _d : 0;
1552
+ const failedIdx = (_b = allOutboundDetails.failedAt) !== null && _b !== void 0 ? _b : 0;
1416
1553
  const failedHop = outboundHops.find((h) => h.hopIndex === failedIdx);
1417
1554
  const failMsg = failedHop
1418
1555
  ? `Outbound failed for hop ${failedIdx} on ${failedHop.executionChain}`
1419
1556
  : `Outbound cascade failed at hop ${failedIdx}`;
1420
- emitHopEvent((0, progress_route_hooks_1.pickWaitHooks)(failedHop === null || failedHop === void 0 ? void 0 : failedHop.route).failed((_e = failedHop === null || failedHop === void 0 ? void 0 : failedHop.executionChain) !== null && _e !== void 0 ? _e : '', failMsg));
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));
1421
1558
  emitCascadeFailed(failedIdx, failMsg);
1422
1559
  return {
1423
1560
  success: false,
@@ -1426,22 +1563,228 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks) {
1426
1563
  };
1427
1564
  }
1428
1565
  }
1566
+ return null;
1567
+ });
1568
+ // Mark only root Push Chain (Route 1) hops as confirmed. Route 1
1569
+ // hops after a Route 3 leg execute in the child inbound Push tx and
1570
+ // must wait for that child UTX to reach terminal success.
1571
+ for (const hop of hopInfos) {
1572
+ if (hop.route === 'CEA_TO_PUSH' &&
1573
+ isNativeSeedOnlyHop(hops[hop.hopIndex])) {
1574
+ emitHopStart(hop.hopIndex);
1575
+ hop.status = 'confirmed';
1576
+ hop.txHash = response.hash;
1577
+ cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
1578
+ hopIndex: hop.hopIndex,
1579
+ route: hop.route,
1580
+ chain: (0, helpers_1.getPushChainForNetwork)(ctx.pushNetwork),
1581
+ status: 'confirmed',
1582
+ txHash: response.hash,
1583
+ elapsed: Date.now() - startTime,
1584
+ });
1585
+ emitHopComplete(hop.hopIndex);
1586
+ }
1587
+ }
1588
+ for (const hop of hopInfos) {
1589
+ if (hop.route === 'UOA_TO_PUSH' && !isAfterCeaToPush(hop.hopIndex)) {
1590
+ hop.status = 'confirmed';
1591
+ hop.txHash = response.hash;
1592
+ cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
1593
+ hopIndex: hop.hopIndex,
1594
+ route: hop.route,
1595
+ chain: (0, helpers_1.getPushChainForNetwork)(ctx.pushNetwork),
1596
+ status: 'confirmed',
1597
+ txHash: response.hash,
1598
+ elapsed: Date.now() - startTime,
1599
+ });
1600
+ emitHopComplete(hop.hopIndex);
1601
+ }
1429
1602
  }
1430
- // 3. Route 3 (CEA_TO_PUSH) tracking - mark as submitted
1431
- // Cascade does not poll inbound completion; we surface the hop-start
1432
- // marker so consumers see a "Starting tx N" event for the inbound,
1433
- // but skip emitHopComplete since we never observe terminal state.
1434
- const inboundHops = hopInfos.filter((h) => h.route === 'CEA_TO_PUSH');
1435
- for (const inboundHop of inboundHops) {
1436
- inboundHop.status = 'submitted';
1603
+ // 2. Track direct outbound hops (Route 2: UOA_TO_CEA) that belong
1604
+ // to the root Push UTX.
1605
+ const rootOutboundHops = hopInfos.filter((h) => h.route === 'UOA_TO_CEA' && !isAfterCeaToPush(h.hopIndex));
1606
+ const rootOutboundFailure = yield trackOutboundHops(rootOutboundHops, response.hash);
1607
+ if (rootOutboundFailure)
1608
+ return rootOutboundFailure;
1609
+ let childInbound;
1610
+ if (ceaToPushIndex >= 0) {
1611
+ const inboundHop = hopInfos[ceaToPushIndex];
1612
+ const hopHooks = (0, progress_route_hooks_1.pickWaitHooks)(inboundHop.route);
1613
+ const remainingTimeout = timeout - (Date.now() - startTime);
1614
+ if (remainingTimeout <= 0) {
1615
+ inboundHop.status = 'failed';
1616
+ emitHopEvent(hopHooks.timeout(inboundHop.executionChain, Date.now() - startTime));
1617
+ emitCascadeTimeout(inboundHop.hopIndex);
1618
+ return {
1619
+ success: false,
1620
+ hops: hopInfos,
1621
+ failedAt: inboundHop.hopIndex,
1622
+ };
1623
+ }
1437
1624
  emitHopStart(inboundHop.hopIndex);
1625
+ emitHopEvent(hopHooks.awaiting(inboundHop.executionChain));
1438
1626
  cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
1439
1627
  hopIndex: inboundHop.hopIndex,
1440
1628
  route: inboundHop.route,
1441
1629
  chain: inboundHop.executionChain,
1442
- status: 'waiting',
1630
+ status: 'polling',
1443
1631
  elapsed: Date.now() - startTime,
1444
1632
  });
1633
+ let outboundLastStatus;
1634
+ let sourceOutboundDetails;
1635
+ try {
1636
+ sourceOutboundDetails = yield callbacks.waitForOutboundTxFn(response.hash, {
1637
+ initialWaitMs: Math.min(60000, remainingTimeout),
1638
+ pollingIntervalMs,
1639
+ timeout: remainingTimeout,
1640
+ _expectedDestinationChain: inboundHop.executionChain,
1641
+ progressHook: (event) => {
1642
+ cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
1643
+ hopIndex: inboundHop.hopIndex,
1644
+ route: inboundHop.route,
1645
+ chain: inboundHop.executionChain,
1646
+ status: event.status,
1647
+ elapsed: Date.now() - startTime,
1648
+ });
1649
+ if (event.status === 'polling' &&
1650
+ outboundLastStatus !== 'polling') {
1651
+ outboundLastStatus = 'polling';
1652
+ emitHopEvent(hopHooks.polling(inboundHop.executionChain, event.elapsed));
1653
+ }
1654
+ },
1655
+ });
1656
+ }
1657
+ catch (err) {
1658
+ const errMsg = err instanceof Error ? err.message : String(err);
1659
+ const isTimeout = errMsg.startsWith('Timeout waiting for outbound transaction');
1660
+ emitHopEvent(isTimeout
1661
+ ? hopHooks.timeout(inboundHop.executionChain, remainingTimeout)
1662
+ : hopHooks.failed(inboundHop.executionChain, errMsg));
1663
+ if (isTimeout)
1664
+ emitCascadeTimeout(inboundHop.hopIndex);
1665
+ else
1666
+ emitCascadeFailed(inboundHop.hopIndex, errMsg);
1667
+ inboundHop.status = 'failed';
1668
+ 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;
1670
+ if (failedDisplayTxHash)
1671
+ inboundHop.txHash = failedDisplayTxHash;
1672
+ cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
1673
+ hopIndex: inboundHop.hopIndex,
1674
+ route: inboundHop.route,
1675
+ chain: inboundHop.executionChain,
1676
+ status: 'failed',
1677
+ txHash: failedDisplayTxHash,
1678
+ elapsed: Date.now() - startTime,
1679
+ });
1680
+ return {
1681
+ success: false,
1682
+ hops: hopInfos,
1683
+ failedAt: inboundHop.hopIndex,
1684
+ };
1685
+ }
1686
+ const displayExternalTxHash = (_e = (0, helpers_1.toExternalTxHashDisplay)(sourceOutboundDetails.destinationChain, sourceOutboundDetails.externalTxHash)) !== null && _e !== void 0 ? _e : sourceOutboundDetails.externalTxHash;
1687
+ inboundHop.outboundDetails = Object.assign(Object.assign({}, sourceOutboundDetails), { externalTxHash: displayExternalTxHash });
1688
+ inboundHop.txHash = displayExternalTxHash;
1689
+ emitHopEvent(hopHooks.success(inboundHop.outboundDetails));
1690
+ emitHopEvent(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_310_01](inboundHop.executionChain));
1691
+ let inboundLastStatus;
1692
+ const inboundRemainingTimeout = timeout - (Date.now() - startTime);
1693
+ 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, {
1695
+ initialWaitMs: Math.min(60000, inboundRemainingTimeout),
1696
+ pollingIntervalMs,
1697
+ timeout: inboundRemainingTimeout,
1698
+ progressHook: (event) => {
1699
+ if (event.status === 'polling' &&
1700
+ inboundLastStatus !== 'polling') {
1701
+ inboundLastStatus = 'polling';
1702
+ emitHopEvent(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_310_02](inboundHop.executionChain, event.elapsedMs));
1703
+ }
1704
+ },
1705
+ });
1706
+ }
1707
+ catch (err) {
1708
+ const errMsg = err instanceof Error ? err.message : String(err);
1709
+ const isTimeout = err instanceof inbound_tracker_1.InboundTimeoutError;
1710
+ emitHopEvent(isTimeout
1711
+ ? progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_399_03](inboundHop.executionChain, inboundRemainingTimeout, 'inbound')
1712
+ : progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_399_02](errMsg, 'inbound', inboundHop.executionChain));
1713
+ if (isTimeout)
1714
+ emitCascadeTimeout(inboundHop.hopIndex);
1715
+ else
1716
+ emitCascadeFailed(inboundHop.hopIndex, errMsg);
1717
+ inboundHop.status = 'failed';
1718
+ cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
1719
+ hopIndex: inboundHop.hopIndex,
1720
+ route: inboundHop.route,
1721
+ chain: inboundHop.executionChain,
1722
+ status: 'failed',
1723
+ elapsed: Date.now() - startTime,
1724
+ });
1725
+ return {
1726
+ success: false,
1727
+ hops: hopInfos,
1728
+ failedAt: inboundHop.hopIndex,
1729
+ };
1730
+ }
1731
+ if (childInbound.status !== 'confirmed') {
1732
+ const failMsg = (_g = childInbound.errorMessage) !== null && _g !== void 0 ? _g : 'inbound execution failed on Push Chain';
1733
+ inboundHop.status = 'failed';
1734
+ inboundHop.txHash =
1735
+ childInbound.pushTxHash || displayExternalTxHash;
1736
+ emitHopEvent(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_399_02](failMsg, 'inbound', inboundHop.executionChain));
1737
+ emitCascadeFailed(inboundHop.hopIndex, failMsg);
1738
+ cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
1739
+ hopIndex: inboundHop.hopIndex,
1740
+ route: inboundHop.route,
1741
+ chain: inboundHop.executionChain,
1742
+ status: 'failed',
1743
+ txHash: inboundHop.txHash,
1744
+ elapsed: Date.now() - startTime,
1745
+ });
1746
+ return {
1747
+ success: false,
1748
+ hops: hopInfos,
1749
+ failedAt: inboundHop.hopIndex,
1750
+ };
1751
+ }
1752
+ inboundHop.status = 'confirmed';
1753
+ inboundHop.txHash =
1754
+ childInbound.pushTxHash || displayExternalTxHash;
1755
+ emitHopEvent(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_399_01](inboundHop.executionChain, childInbound.pushTxHash));
1756
+ emitHopComplete(inboundHop.hopIndex);
1757
+ cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
1758
+ hopIndex: inboundHop.hopIndex,
1759
+ route: inboundHop.route,
1760
+ chain: inboundHop.executionChain,
1761
+ status: 'confirmed',
1762
+ txHash: inboundHop.txHash,
1763
+ elapsed: Date.now() - startTime,
1764
+ });
1765
+ }
1766
+ if (childInbound) {
1767
+ for (const hop of hopInfos) {
1768
+ if (hop.route === 'UOA_TO_PUSH' &&
1769
+ isAfterCeaToPush(hop.hopIndex)) {
1770
+ emitHopStart(hop.hopIndex);
1771
+ hop.status = 'confirmed';
1772
+ hop.txHash = childInbound.pushTxHash;
1773
+ cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
1774
+ hopIndex: hop.hopIndex,
1775
+ route: hop.route,
1776
+ chain: (0, helpers_1.getPushChainForNetwork)(ctx.pushNetwork),
1777
+ status: 'confirmed',
1778
+ txHash: childInbound.pushTxHash,
1779
+ elapsed: Date.now() - startTime,
1780
+ });
1781
+ emitHopComplete(hop.hopIndex);
1782
+ }
1783
+ }
1784
+ const childOutboundHops = hopInfos.filter((h) => h.route === 'UOA_TO_CEA' && isAfterCeaToPush(h.hopIndex));
1785
+ const childOutboundFailure = yield trackOutboundHops(childOutboundHops, childInbound.pushTxHash || response.hash, childInbound.childUtxId);
1786
+ if (childOutboundFailure)
1787
+ return childOutboundFailure;
1445
1788
  }
1446
1789
  emitCascadeSuccess();
1447
1790
  return { success: true, hops: hopInfos };