@pushchain/core 6.0.20 → 6.0.21

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 (111) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/package.json +1 -1
  3. package/src/lib/cache/cache.js +2 -0
  4. package/src/lib/constants/abi/index.d.ts +1 -0
  5. package/src/lib/constants/abi/index.js +8 -1
  6. package/src/lib/constants/abi/pc20.evm.d.ts +217 -0
  7. package/src/lib/constants/abi/pc20.evm.js +150 -0
  8. package/src/lib/constants/abi/prc20.evm.d.ts +172 -0
  9. package/src/lib/constants/abi/prc20.evm.js +97 -0
  10. package/src/lib/constants/abi/universalGatewayV0.json +1 -1
  11. package/src/lib/constants/chain.js +38 -8
  12. package/src/lib/constants/selectors.js +2 -2
  13. package/src/lib/constants/tokens.js +4 -3
  14. package/src/lib/formatters.js +16 -6
  15. package/src/lib/generated/uexecutor/v1/query.js +2 -4
  16. package/src/lib/generated/uexecutor/v1/types.d.ts +14 -0
  17. package/src/lib/generated/uexecutor/v1/types.js +90 -47
  18. package/src/lib/generated/uexecutor/v2/query.js +2 -4
  19. package/src/lib/generated/uexecutor/v2/types.d.ts +20 -0
  20. package/src/lib/generated/uexecutor/v2/types.js +49 -33
  21. package/src/lib/generated/v1/tx.js +30 -37
  22. package/src/lib/index.d.ts +3 -0
  23. package/src/lib/index.js +21 -3
  24. package/src/lib/internal/buffer.js +1 -2
  25. package/src/lib/orchestrator/cea-utils.js +73 -83
  26. package/src/lib/orchestrator/internals/account-manager.js +130 -139
  27. package/src/lib/orchestrator/internals/cascade.d.ts +0 -6
  28. package/src/lib/orchestrator/internals/cascade.js +1184 -1059
  29. package/src/lib/orchestrator/internals/confirmation.js +138 -148
  30. package/src/lib/orchestrator/internals/error-decoder.js +1 -1
  31. package/src/lib/orchestrator/internals/errors.js +12 -3
  32. package/src/lib/orchestrator/internals/execute-funds-only.d.ts +2 -2
  33. package/src/lib/orchestrator/internals/execute-funds-only.js +297 -259
  34. package/src/lib/orchestrator/internals/execute-funds-payload.d.ts +2 -2
  35. package/src/lib/orchestrator/internals/execute-funds-payload.js +220 -220
  36. package/src/lib/orchestrator/internals/execute-standard.d.ts +0 -8
  37. package/src/lib/orchestrator/internals/execute-standard.js +339 -342
  38. package/src/lib/orchestrator/internals/gas-calculator.js +497 -520
  39. package/src/lib/orchestrator/internals/gas-usd-sizer.js +101 -110
  40. package/src/lib/orchestrator/internals/gateway-client.js +102 -103
  41. package/src/lib/orchestrator/internals/helpers.js +30 -27
  42. package/src/lib/orchestrator/internals/inbound-tracker.js +234 -238
  43. package/src/lib/orchestrator/internals/outbound-sync.js +463 -452
  44. package/src/lib/orchestrator/internals/outbound-tracker.js +27 -34
  45. package/src/lib/orchestrator/internals/payload-builder.d.ts +2 -2
  46. package/src/lib/orchestrator/internals/payload-builder.js +119 -100
  47. package/src/lib/orchestrator/internals/pc-usd-oracle.js +121 -129
  48. package/src/lib/orchestrator/internals/pc20/address-codec.d.ts +31 -0
  49. package/src/lib/orchestrator/internals/pc20/address-codec.js +124 -0
  50. package/src/lib/orchestrator/internals/pc20/chain-namespace.d.ts +68 -0
  51. package/src/lib/orchestrator/internals/pc20/chain-namespace.js +136 -0
  52. package/src/lib/orchestrator/internals/pc20/errors.d.ts +160 -0
  53. package/src/lib/orchestrator/internals/pc20/errors.js +236 -0
  54. package/src/lib/orchestrator/internals/pc20/export.d.ts +172 -0
  55. package/src/lib/orchestrator/internals/pc20/export.js +412 -0
  56. package/src/lib/orchestrator/internals/pc20/gate.d.ts +67 -0
  57. package/src/lib/orchestrator/internals/pc20/gate.js +184 -0
  58. package/src/lib/orchestrator/internals/pc20/resolver.d.ts +187 -0
  59. package/src/lib/orchestrator/internals/pc20/resolver.js +658 -0
  60. package/src/lib/orchestrator/internals/pc20/svm.d.ts +189 -0
  61. package/src/lib/orchestrator/internals/pc20/svm.js +286 -0
  62. package/src/lib/orchestrator/internals/pc20/tracking.d.ts +46 -0
  63. package/src/lib/orchestrator/internals/pc20/tracking.js +109 -0
  64. package/src/lib/orchestrator/internals/preflight.js +1 -1
  65. package/src/lib/orchestrator/internals/progress-route-hooks.js +2 -2
  66. package/src/lib/orchestrator/internals/push-chain-tx.js +319 -329
  67. package/src/lib/orchestrator/internals/quote.js +90 -94
  68. package/src/lib/orchestrator/internals/rescue.js +76 -79
  69. package/src/lib/orchestrator/internals/response-builder.d.ts +28 -1
  70. package/src/lib/orchestrator/internals/response-builder.js +817 -701
  71. package/src/lib/orchestrator/internals/route-handlers.d.ts +7 -15
  72. package/src/lib/orchestrator/internals/route-handlers.js +1512 -1178
  73. package/src/lib/orchestrator/internals/signing.js +78 -83
  74. package/src/lib/orchestrator/internals/svm-bridge.d.ts +2 -2
  75. package/src/lib/orchestrator/internals/svm-bridge.js +161 -93
  76. package/src/lib/orchestrator/internals/svm-helpers.d.ts +15 -1
  77. package/src/lib/orchestrator/internals/svm-helpers.js +68 -24
  78. package/src/lib/orchestrator/internals/svm-rent.d.ts +19 -1
  79. package/src/lib/orchestrator/internals/svm-rent.js +163 -141
  80. package/src/lib/orchestrator/internals/tx-transformer.js +26 -23
  81. package/src/lib/orchestrator/internals/uea-manager.js +89 -86
  82. package/src/lib/orchestrator/max-pc-for-gas.js +5 -6
  83. package/src/lib/orchestrator/orchestrator.js +217 -200
  84. package/src/lib/orchestrator/orchestrator.types.d.ts +121 -1
  85. package/src/lib/orchestrator/orchestrator.types.js +19 -0
  86. package/src/lib/orchestrator/payload-builders.d.ts +2 -2
  87. package/src/lib/orchestrator/payload-builders.js +26 -24
  88. package/src/lib/orchestrator/route-detector.js +26 -9
  89. package/src/lib/orchestrator/svm-idl/ix-encoder.js +1 -2
  90. package/src/lib/orchestrator/svm-idl/normalize-address.js +2 -2
  91. package/src/lib/orchestrator/svm-idl/resolve.js +1 -2
  92. package/src/lib/price-fetch/price-fetch.js +67 -68
  93. package/src/lib/progress-hook/progress-hook.js +124 -72
  94. package/src/lib/push-chain/push-chain.js +171 -153
  95. package/src/lib/push-client/push-client.js +223 -237
  96. package/src/lib/universal/account/account.js +349 -365
  97. package/src/lib/universal/signer/signer.js +306 -294
  98. package/src/lib/universal-tx-detector/auto-detect.js +55 -60
  99. package/src/lib/universal-tx-detector/cascade.js +160 -172
  100. package/src/lib/universal-tx-detector/child-inbounds.js +67 -75
  101. package/src/lib/universal-tx-detector/classify.js +2 -3
  102. package/src/lib/universal-tx-detector/detector-svm.js +139 -147
  103. package/src/lib/universal-tx-detector/detector.js +145 -157
  104. package/src/lib/universal-tx-detector/normalize.js +7 -7
  105. package/src/lib/utils/external-tx-hash.js +1 -2
  106. package/src/lib/utils.d.ts +44 -0
  107. package/src/lib/utils.js +762 -683
  108. package/src/lib/vm-client/evm-client.js +186 -202
  109. package/src/lib/vm-client/svm-client.d.ts +1 -1
  110. package/src/lib/vm-client/svm-client.js +220 -224
  111. package/src/lib/vm-client/vm-client.types.d.ts +14 -2
@@ -1,10 +1,4 @@
1
1
  "use strict";
2
- /**
3
- * Cascade composition functions extracted from Orchestrator.
4
- *
5
- * Covers: prepareTransaction, buildHopDescriptor, classifyIntoSegments,
6
- * getSegmentType, composeCascade, createCascadedBuilder, createChainedBuilder.
7
- */
8
2
  Object.defineProperty(exports, "__esModule", { value: true });
9
3
  exports.dispatchCascadeProgressEvent = dispatchCascadeProgressEvent;
10
4
  exports.prepareTransaction = prepareTransaction;
@@ -15,11 +9,19 @@ exports.composeCascadeDetailed = composeCascadeDetailed;
15
9
  exports.composeCascade = composeCascade;
16
10
  exports.createCascadedBuilder = createCascadedBuilder;
17
11
  const tslib_1 = require("tslib");
12
+ const gate_1 = require("./pc20/gate");
13
+ /**
14
+ * Cascade composition functions extracted from Orchestrator.
15
+ *
16
+ * Covers: prepareTransaction, buildHopDescriptor, classifyIntoSegments,
17
+ * getSegmentType, composeCascade, createCascadedBuilder, createChainedBuilder.
18
+ */
18
19
  const web3_js_1 = require("@solana/web3.js");
19
20
  const viem_1 = require("viem");
20
21
  const erc20_evm_1 = require("../../constants/abi/erc20.evm");
21
22
  const chain_1 = require("../../constants/chain");
22
23
  const enums_1 = require("../../constants/enums");
24
+ const orchestrator_types_1 = require("../orchestrator.types");
23
25
  const selectors_1 = require("../../constants/selectors");
24
26
  const route_detector_1 = require("../route-detector");
25
27
  const cea_utils_1 = require("../cea-utils");
@@ -39,6 +41,7 @@ const progress_hook_types_1 = require("../../progress-hook/progress-hook.types")
39
41
  const build_payload_1 = require("../svm-idl/build-payload");
40
42
  const preflight_1 = require("./preflight");
41
43
  const svm_rent_1 = require("./svm-rent");
44
+ const svm_1 = require("./pc20/svm");
42
45
  const errors_1 = require("./errors");
43
46
  const context_2 = require("./context");
44
47
  const inbound_tracker_1 = require("./inbound-tracker");
@@ -71,277 +74,122 @@ function dispatchCascadeProgressEvent(event, primary, secondary) {
71
74
  // ============================================================================
72
75
  // prepareTransaction
73
76
  // ============================================================================
74
- function prepareTransaction(ctx, params, callbacks) {
75
- return tslib_1.__awaiter(this, void 0, void 0, function* () {
76
- var _a, _b;
77
- (0, route_detector_1.validateRouteParams)(params, {
78
- clientChain: ctx.universalSigner.account.chain,
79
- });
80
- const route = (0, route_detector_1.detectRoute)(params);
81
- // Push native EOA: block Route 1 — use sendTransaction() for direct Push calls
82
- const signerChain = ctx.universalSigner.account.chain;
83
- const isPushNativeEOA = (0, helpers_1.isPushChain)(signerChain);
84
- if (isPushNativeEOA && route === route_detector_1.TransactionRoute.UOA_TO_PUSH) {
85
- throw new Error('Push native accounts cannot use prepareTransaction for Push Chain transactions. ' +
86
- 'Use sendTransaction() instead for direct Push Chain calls.');
87
- }
88
- // Fetch UEA nonce (skip for Push native EOA — uses EVM nonce, not UEA contract nonce)
89
- let nonce;
90
- const ueaAddress = (0, uea_manager_1.computeUEAOffchain)(ctx);
91
- if (isPushNativeEOA) {
92
- nonce = BigInt(0);
77
+ async function prepareTransaction(ctx, params, callbacks) {
78
+ (0, route_detector_1.validateRouteParams)(params, {
79
+ clientChain: ctx.universalSigner.account.chain,
80
+ });
81
+ const route = (0, route_detector_1.detectRoute)(params);
82
+ // Push native EOA: block Route 1 — use sendTransaction() for direct Push calls
83
+ const signerChain = ctx.universalSigner.account.chain;
84
+ const isPushNativeEOA = (0, helpers_1.isPushChain)(signerChain);
85
+ if (isPushNativeEOA && route === route_detector_1.TransactionRoute.UOA_TO_PUSH) {
86
+ throw new Error('Push native accounts cannot use prepareTransaction for Push Chain transactions. ' +
87
+ 'Use sendTransaction() instead for direct Push Chain calls.');
88
+ }
89
+ // Fetch UEA nonce (skip for Push native EOA — uses EVM nonce, not UEA contract nonce)
90
+ let nonce;
91
+ const ueaAddress = (0, uea_manager_1.computeUEAOffchain)(ctx);
92
+ if (isPushNativeEOA) {
93
+ nonce = BigInt(0);
94
+ }
95
+ else {
96
+ const deployedHintCascade = ctx.accountStatusCache?.uea?.deployed;
97
+ if (deployedHintCascade) {
98
+ nonce = await (0, uea_manager_1.getUEANonce)(ctx, ueaAddress);
93
99
  }
94
100
  else {
95
- const deployedHintCascade = (_b = (_a = ctx.accountStatusCache) === null || _a === void 0 ? void 0 : _a.uea) === null || _b === void 0 ? void 0 : _b.deployed;
96
- if (deployedHintCascade) {
97
- nonce = yield (0, uea_manager_1.getUEANonce)(ctx, ueaAddress);
98
- }
99
- else {
100
- const status = yield (0, uea_manager_1.getUeaStatusAndNonce)(ctx);
101
- nonce = status.nonce;
102
- }
101
+ const status = await (0, uea_manager_1.getUeaStatusAndNonce)(ctx);
102
+ nonce = status.nonce;
103
103
  }
104
- // Build the payload based on route
105
- const { payload, gatewayRequest } = yield (0, route_handlers_1.buildPayloadForRoute)(ctx, params, route, nonce);
106
- const gasEstimate = params.gasLimit || selectors_1.DEFAULT_OUTBOUND_GAS_LIMIT;
107
- const deadline = params.deadline || BigInt(Math.floor(Date.now() / 1000) + 3600);
108
- // Build the HopDescriptor with all metadata needed for cascade nesting
109
- const hop = yield buildHopDescriptor(ctx, params, route, ueaAddress);
110
- const prepared = {
111
- route,
112
- payload,
113
- gatewayRequest,
114
- estimatedGas: gasEstimate,
115
- nonce,
116
- deadline,
117
- _hop: hop,
118
- };
119
- return prepared;
120
- });
104
+ }
105
+ // PC20 resolution for a prepared transaction.
106
+ //
107
+ // A prepared tx is signed now and sent later, so the registry state it was
108
+ // built against can move in between — most importantly, a first export can
109
+ // land and create a mapping that did not exist at prepare time. The
110
+ // descriptor is therefore resolved here to build the payload, and
111
+ // deliberately NOT cached as authoritative: `revalidatePreparedPC20` re-runs
112
+ // the critical reads at send time.
113
+ const preparedParams = (await (0, gate_1.gateFunds)(ctx, params));
114
+ // Build the payload based on route
115
+ const { payload, gatewayRequest } = await (0, route_handlers_1.buildPayloadForRoute)(ctx, preparedParams, route, nonce);
116
+ const gasEstimate = params.gasLimit || selectors_1.DEFAULT_OUTBOUND_GAS_LIMIT;
117
+ const deadline = params.deadline || BigInt(Math.floor(Date.now() / 1000) + 3600);
118
+ // Build the HopDescriptor with all metadata needed for cascade nesting
119
+ const hop = await buildHopDescriptor(ctx, preparedParams, route, ueaAddress);
120
+ const prepared = {
121
+ route,
122
+ payload,
123
+ gatewayRequest,
124
+ estimatedGas: gasEstimate,
125
+ nonce,
126
+ deadline,
127
+ _hop: hop,
128
+ };
129
+ return prepared;
121
130
  }
122
131
  // ============================================================================
123
132
  // buildHopDescriptor
124
133
  // ============================================================================
125
- function buildHopDescriptor(ctx, params, route, ueaAddress) {
126
- return tslib_1.__awaiter(this, void 0, void 0, function* () {
127
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s;
128
- // Pass 0 when user omits gasLimit so the contract uses the per-chain base,
129
- // except EVM Route 3 where a CEA deploy + sendUniversalTxToUEA needs more
130
- // than the generic 500k source-chain base.
131
- const defaultGasLimit = route === route_detector_1.TransactionRoute.CEA_TO_PUSH &&
132
- ((_a = params.from) === null || _a === void 0 ? void 0 : _a.chain) &&
133
- !(0, payload_builders_1.isSvmChain)(params.from.chain)
134
- ? selectors_1.DEFAULT_CEA_TO_PUSH_GAS_LIMIT
135
- : BigInt(0);
136
- const gasLimit = (_b = params.gasLimit) !== null && _b !== void 0 ? _b : defaultGasLimit;
137
- const routeStr = route;
138
- const baseDescriptor = {
139
- params,
140
- route: routeStr,
141
- gasLimit,
142
- maxPCForGas: (_c = params.maxPCForGas) !== null && _c !== void 0 ? _c : BigInt(0),
143
- ueaAddress,
144
- revertRecipient: ueaAddress,
145
- };
146
- switch (route) {
147
- case route_detector_1.TransactionRoute.UOA_TO_PUSH: {
148
- // Route 1: Build Push Chain multicalls
149
- const seedAmount = (_d = params.value) !== null && _d !== void 0 ? _d : BigInt(0);
150
- if (isValueOnlyNativeSeedToOwnUea(params, ueaAddress, {
151
- allowFunds: true,
152
- })) {
153
- return Object.assign(Object.assign({}, baseDescriptor), { pushMulticalls: [], nativeSeedOnly: true, nativeSeedAmount: seedAmount });
154
- }
155
- const executeParams = (0, helpers_1.toExecuteParams)(params);
156
- const pushMulticalls = (0, payload_builders_1.buildExecuteMulticall)({
157
- execute: executeParams,
158
- ueaAddress,
159
- });
160
- return Object.assign(Object.assign({}, baseDescriptor), { pushMulticalls });
134
+ async function buildHopDescriptor(ctx, params, route, ueaAddress) {
135
+ // Pass 0 when user omits gasLimit so the contract uses the per-chain base,
136
+ // except EVM Route 3 where a CEA deploy + sendUniversalTxToUEA needs more
137
+ // than the generic 500k source-chain base.
138
+ const defaultGasLimit = route === route_detector_1.TransactionRoute.CEA_TO_PUSH &&
139
+ params.from?.chain &&
140
+ !(0, payload_builders_1.isSvmChain)(params.from.chain)
141
+ ? selectors_1.DEFAULT_CEA_TO_PUSH_GAS_LIMIT
142
+ : BigInt(0);
143
+ const gasLimit = params.gasLimit ?? defaultGasLimit;
144
+ const routeStr = route;
145
+ const baseDescriptor = {
146
+ params,
147
+ route: routeStr,
148
+ gasLimit,
149
+ maxPCForGas: params.maxPCForGas ?? BigInt(0),
150
+ ueaAddress,
151
+ revertRecipient: ueaAddress,
152
+ };
153
+ switch (route) {
154
+ case route_detector_1.TransactionRoute.UOA_TO_PUSH: {
155
+ // Route 1: Build Push Chain multicalls
156
+ const seedAmount = params.value ?? BigInt(0);
157
+ if (isValueOnlyNativeSeedToOwnUea(params, ueaAddress, {
158
+ allowFunds: true,
159
+ })) {
160
+ return {
161
+ ...baseDescriptor,
162
+ pushMulticalls: [],
163
+ nativeSeedOnly: true,
164
+ nativeSeedAmount: seedAmount,
165
+ };
161
166
  }
162
- case route_detector_1.TransactionRoute.UOA_TO_CEA: {
163
- // Route 2: Build outbound metadata
164
- const target = params.to;
165
- const targetChain = target.chain;
166
- // Branch: SVM vs EVM
167
- if ((0, payload_builders_1.isSvmChain)(targetChain)) {
168
- // SVM path: no CEA lookup, build SVM payload via IDL resolver
169
- const { svmPayload, hasExecute: hasSvmExecute } = (0, build_payload_1.buildSvmPayloadFromParams)({
170
- data: params.data,
171
- to: target,
172
- senderUea: ueaAddress,
173
- });
174
- (0, payload_builders_1.assertSvmPayloadWithinRelayLimit)(svmPayload, `Route 2 SVM ${targetChain}`);
175
- let prc20Token = selectors_1.ZERO_ADDRESS;
176
- let burnAmount = BigInt(0);
177
- if ((_e = params.funds) === null || _e === void 0 ? void 0 : _e.amount) {
178
- const token = params.funds.token;
179
- if (token) {
180
- prc20Token = push_chain_1.PushChain.utils.tokens.getPRC20Address(token).address;
181
- burnAmount = params.funds.amount;
182
- }
183
- }
184
- else if (params.value && params.value > BigInt(0)) {
185
- prc20Token = (0, helpers_1.getNativePRC20ForChain)(targetChain, ctx.pushNetwork);
186
- burnAmount = params.value;
187
- }
188
- else if (hasSvmExecute) {
189
- // PAYLOAD-only SVM execute: still need native PRC-20 for chain namespace
190
- // + gas fees, but burn amount must be zero. Matches executeUoaToCeaSvm
191
- // (route-handlers.ts) and the EVM cascade branch below.
192
- // Using burnAmount=1 would trigger transferFrom on the PRC-20 token,
193
- // which reverts with InsufficientBalance when the UEA has no pSOL balance.
194
- prc20Token = (0, helpers_1.getNativePRC20ForChain)(targetChain, ctx.pushNetwork);
195
- burnAmount = BigInt(0);
196
- }
197
- let gasToken = selectors_1.ZERO_ADDRESS;
198
- let gasFee = BigInt(0);
199
- let gasPrice = BigInt(0);
200
- let sizing;
201
- // SVM finalize budget: compare the quoted gasFee in lamports against
202
- // the gateway finalize minimum, then re-query with a bumped gasLimit
203
- // if the default quote is too low.
204
- const svmFundsToken = burnAmount > BigInt(0)
205
- ? (_f = params.funds) === null || _f === void 0 ? void 0 : _f.token
206
- : undefined;
207
- const splMintBase58 = (svmFundsToken === null || svmFundsToken === void 0 ? void 0 : svmFundsToken.mechanism) === 'approve'
208
- ? svmFundsToken.address
209
- : undefined;
210
- let effectiveGasLimit = gasLimit;
211
- if (prc20Token !== selectors_1.ZERO_ADDRESS) {
212
- let result = yield (0, gas_calculator_1.queryOutboundGasFee)(ctx, prc20Token, effectiveGasLimit, targetChain);
213
- result = yield (0, svm_rent_1.ensureSvmFinalizeGasBudgetQuote)({
214
- ctx,
215
- ueaAddress,
216
- targetChain,
217
- prc20Token,
218
- quote: result,
219
- splMintBase58,
220
- burnAmount,
221
- pathTag: 'buildHopDescriptor',
222
- });
223
- gasToken = result.gasToken;
224
- gasFee = result.gasFee;
225
- gasPrice = result.gasPrice;
226
- sizing = result.sizing;
227
- effectiveGasLimit = result.gasLimitUsed;
228
- if (!params.gasLimit) {
229
- (0, context_1.printLog)(ctx, `buildHopDescriptor — SVM resolved effectiveGasLimit: ${effectiveGasLimit} ` +
230
- `(gasFee=${result.gasFee}, gasPrice=${result.gasPrice})`);
231
- }
232
- }
233
- return Object.assign(Object.assign({}, baseDescriptor), { targetChain, isSvmTarget: true, svmPayload,
234
- prc20Token,
235
- burnAmount,
236
- gasToken,
237
- gasFee,
238
- gasPrice, gasLimit: effectiveGasLimit, sizing });
239
- }
240
- // EVM path: Resolve CEA address + build CEA multicalls
241
- const { cea: ceaAddress } = yield (0, cea_utils_1.getCEAAddress)(ueaAddress, targetChain, (_g = ctx.rpcUrls[targetChain]) === null || _g === void 0 ? void 0 : _g[0]);
242
- // Migration path: raw MIGRATION_SELECTOR payload, no multicall wrapping
243
- if (params.migration) {
244
- const prc20Token = (0, helpers_1.getNativePRC20ForChain)(targetChain, ctx.pushNetwork);
245
- const burnAmount = BigInt(0); // Migration is logic-only — no funds. CEA rejects msg.value != 0.
246
- let gasToken = selectors_1.ZERO_ADDRESS;
247
- let gasFee = BigInt(0);
248
- let gasPrice = BigInt(0);
249
- let sizing;
250
- if (prc20Token !== selectors_1.ZERO_ADDRESS) {
251
- const result = yield (0, gas_calculator_1.queryOutboundGasFee)(ctx, prc20Token, gasLimit, targetChain);
252
- gasToken = result.gasToken;
253
- gasFee = result.gasFee;
254
- gasPrice = result.gasPrice;
255
- sizing = result.sizing;
256
- }
257
- return Object.assign(Object.assign({}, baseDescriptor), { targetChain,
258
- ceaAddress, ceaMulticalls: [], prc20Token,
259
- burnAmount,
260
- gasToken,
261
- gasFee,
262
- gasPrice, isMigration: true, sizing });
263
- }
264
- // Build CEA multicalls
265
- const ceaMulticalls = [];
266
- const hasData = (0, data_utils_1.hasExecutablePayloadData)(params.data);
267
- const fundsToken = ((_h = params.funds) === null || _h === void 0 ? void 0 : _h.amount)
268
- ? (0, route_handlers_1.resolveR2DestinationFundsToken)(params.funds.token, targetChain, ctx.pushNetwork)
269
- : undefined;
270
- const nativeFundsValue = ((_j = params.funds) === null || _j === void 0 ? void 0 : _j.amount) && (fundsToken === null || fundsToken === void 0 ? void 0 : fundsToken.mechanism) === 'native'
271
- ? params.funds.amount
272
- : BigInt(0);
273
- if (hasData) {
274
- if (Array.isArray(params.data)) {
275
- ceaMulticalls.push(...params.data);
276
- }
277
- else {
278
- // When ERC-20 funds are provided with a single payload, auto-prepend a
279
- // transfer() call so the tokens minted to the CEA are forwarded to the
280
- // target address. This mirrors the Route 1 behavior in buildExecuteMulticall.
281
- if ((_k = params.funds) === null || _k === void 0 ? void 0 : _k.amount) {
282
- if (fundsToken && fundsToken.mechanism !== 'native') {
283
- const erc20Transfer = (0, viem_1.encodeFunctionData)({
284
- abi: erc20_evm_1.ERC20_EVM,
285
- functionName: 'transfer',
286
- args: [target.address, params.funds.amount],
287
- });
288
- ceaMulticalls.push({
289
- to: fundsToken.address,
290
- value: BigInt(0),
291
- data: erc20Transfer,
292
- });
293
- }
294
- }
295
- // Single call with data. Forward native value (if any) so the target
296
- // contract receives it alongside the payload call. The vault deposits
297
- // native value to the CEA, and the multicall forwards it to the target.
298
- ceaMulticalls.push({
299
- to: target.address,
300
- value: ((_l = params.value) !== null && _l !== void 0 ? _l : BigInt(0)) + nativeFundsValue,
301
- data: params.data,
302
- });
303
- }
304
- }
305
- else {
306
- if (((_m = params.funds) === null || _m === void 0 ? void 0 : _m.amount) &&
307
- fundsToken &&
308
- fundsToken.mechanism !== 'native') {
309
- const erc20Transfer = (0, viem_1.encodeFunctionData)({
310
- abi: erc20_evm_1.ERC20_EVM,
311
- functionName: 'transfer',
312
- args: [target.address, params.funds.amount],
313
- });
314
- ceaMulticalls.push({
315
- to: fundsToken.address,
316
- value: BigInt(0),
317
- data: erc20Transfer,
318
- });
319
- }
320
- if (params.value) {
321
- // Skip multicall when sending native value to own CEA — gateway deposits directly.
322
- // Self-call with value would revert (CEA._handleMulticall rejects it).
323
- if (target.address.toLowerCase() !== ceaAddress.toLowerCase()) {
324
- ceaMulticalls.push({
325
- to: target.address,
326
- value: params.value,
327
- data: '0x',
328
- });
329
- }
330
- }
331
- else if (nativeFundsValue > BigInt(0)) {
332
- if (target.address.toLowerCase() !== ceaAddress.toLowerCase()) {
333
- ceaMulticalls.push({
334
- to: target.address,
335
- value: nativeFundsValue,
336
- data: '0x',
337
- });
338
- }
339
- }
340
- }
341
- // Determine PRC-20 token and burn amount
167
+ const executeParams = (0, gate_1.assertLegacyFunds)((0, helpers_1.toExecuteParams)(params));
168
+ const pushMulticalls = (0, payload_builders_1.buildExecuteMulticall)({
169
+ execute: executeParams,
170
+ ueaAddress,
171
+ });
172
+ return {
173
+ ...baseDescriptor,
174
+ pushMulticalls,
175
+ };
176
+ }
177
+ case route_detector_1.TransactionRoute.UOA_TO_CEA: {
178
+ // Route 2: Build outbound metadata
179
+ const target = params.to;
180
+ const targetChain = target.chain;
181
+ // Branch: SVM vs EVM
182
+ if ((0, payload_builders_1.isSvmChain)(targetChain)) {
183
+ // SVM path: no CEA lookup, build SVM payload via IDL resolver
184
+ const { svmPayload, hasExecute: hasSvmExecute } = (0, build_payload_1.buildSvmPayloadFromParams)({
185
+ data: params.data,
186
+ to: target,
187
+ senderUea: ueaAddress,
188
+ });
189
+ (0, payload_builders_1.assertSvmPayloadWithinRelayLimit)(svmPayload, `Route 2 SVM ${targetChain}`);
342
190
  let prc20Token = selectors_1.ZERO_ADDRESS;
343
191
  let burnAmount = BigInt(0);
344
- if ((_o = params.funds) === null || _o === void 0 ? void 0 : _o.amount) {
192
+ if (params.funds?.amount) {
345
193
  const token = params.funds.token;
346
194
  if (token) {
347
195
  prc20Token = push_chain_1.PushChain.utils.tokens.getPRC20Address(token).address;
@@ -352,160 +200,371 @@ function buildHopDescriptor(ctx, params, route, ueaAddress) {
352
200
  prc20Token = (0, helpers_1.getNativePRC20ForChain)(targetChain, ctx.pushNetwork);
353
201
  burnAmount = params.value;
354
202
  }
355
- else if ((0, data_utils_1.hasExecutablePayloadData)(params.data)) {
356
- // PAYLOAD-only (no value transfer): still need native PRC-20 token for
357
- // chain namespace lookup + gas fees, but burn amount must be zero.
358
- // Matches the direct executeUoaToCea path (route-handlers.ts).
203
+ else if (hasSvmExecute) {
204
+ // PAYLOAD-only SVM execute: still need native PRC-20 for chain namespace
205
+ // + gas fees, but burn amount must be zero. Matches executeUoaToCeaSvm
206
+ // (route-handlers.ts) and the EVM cascade branch below.
359
207
  // Using burnAmount=1 would trigger transferFrom on the PRC-20 token,
360
- // which reverts when the UEA has no PRC-20 balance for that chain.
208
+ // which reverts with InsufficientBalance when the UEA has no pSOL balance.
361
209
  prc20Token = (0, helpers_1.getNativePRC20ForChain)(targetChain, ctx.pushNetwork);
362
210
  burnAmount = BigInt(0);
363
211
  }
364
- // Query gas fee
212
+ let gasToken = selectors_1.ZERO_ADDRESS;
213
+ let gasFee = BigInt(0);
214
+ let gasPrice = BigInt(0);
215
+ let sizing;
216
+ // SVM finalize budget: compare the quoted gasFee in lamports against
217
+ // the gateway finalize minimum, then re-query with a bumped gasLimit
218
+ // if the default quote is too low.
219
+ const svmFundsToken = burnAmount > BigInt(0)
220
+ ? params.funds?.token
221
+ : undefined;
222
+ const splMintBase58 = svmFundsToken?.mechanism === 'approve'
223
+ ? svmFundsToken.address
224
+ : undefined;
225
+ let effectiveGasLimit = gasLimit;
226
+ if (prc20Token !== selectors_1.ZERO_ADDRESS) {
227
+ let result = await (0, gas_calculator_1.queryOutboundGasFee)(ctx, prc20Token, effectiveGasLimit, targetChain);
228
+ result = await (0, svm_rent_1.ensureSvmFinalizeGasBudgetQuote)({
229
+ ctx,
230
+ ueaAddress,
231
+ targetChain,
232
+ prc20Token,
233
+ quote: result,
234
+ splMintBase58,
235
+ burnAmount,
236
+ pathTag: 'buildHopDescriptor',
237
+ });
238
+ gasToken = result.gasToken;
239
+ gasFee = result.gasFee;
240
+ gasPrice = result.gasPrice;
241
+ sizing = result.sizing;
242
+ effectiveGasLimit = result.gasLimitUsed;
243
+ if (!params.gasLimit) {
244
+ (0, context_1.printLog)(ctx, `buildHopDescriptor — SVM resolved effectiveGasLimit: ${effectiveGasLimit} ` +
245
+ `(gasFee=${result.gasFee}, gasPrice=${result.gasPrice})`);
246
+ }
247
+ }
248
+ return {
249
+ ...baseDescriptor,
250
+ targetChain,
251
+ isSvmTarget: true,
252
+ svmPayload,
253
+ prc20Token,
254
+ burnAmount,
255
+ gasToken,
256
+ gasFee,
257
+ gasPrice,
258
+ gasLimit: effectiveGasLimit,
259
+ sizing,
260
+ };
261
+ }
262
+ // EVM path: Resolve CEA address + build CEA multicalls
263
+ const { cea: ceaAddress } = await (0, cea_utils_1.getCEAAddress)(ueaAddress, targetChain, ctx.rpcUrls[targetChain]?.[0]);
264
+ // Migration path: raw MIGRATION_SELECTOR payload, no multicall wrapping
265
+ if (params.migration) {
266
+ const prc20Token = (0, helpers_1.getNativePRC20ForChain)(targetChain, ctx.pushNetwork);
267
+ const burnAmount = BigInt(0); // Migration is logic-only — no funds. CEA rejects msg.value != 0.
365
268
  let gasToken = selectors_1.ZERO_ADDRESS;
366
269
  let gasFee = BigInt(0);
367
270
  let gasPrice = BigInt(0);
368
271
  let sizing;
369
272
  if (prc20Token !== selectors_1.ZERO_ADDRESS) {
370
- const result = yield (0, gas_calculator_1.queryOutboundGasFee)(ctx, prc20Token, gasLimit, targetChain);
273
+ const result = await (0, gas_calculator_1.queryOutboundGasFee)(ctx, prc20Token, gasLimit, targetChain);
371
274
  gasToken = result.gasToken;
372
275
  gasFee = result.gasFee;
373
276
  gasPrice = result.gasPrice;
374
277
  sizing = result.sizing;
375
278
  }
376
- return Object.assign(Object.assign({}, baseDescriptor), { targetChain,
279
+ return {
280
+ ...baseDescriptor,
281
+ targetChain,
377
282
  ceaAddress,
378
- ceaMulticalls,
283
+ ceaMulticalls: [],
379
284
  prc20Token,
380
285
  burnAmount,
381
286
  gasToken,
382
287
  gasFee,
383
288
  gasPrice,
384
- sizing });
289
+ isMigration: true,
290
+ sizing,
291
+ };
385
292
  }
386
- case route_detector_1.TransactionRoute.CEA_TO_PUSH: {
387
- // Route 3: Build CEA multicalls for sendUniversalTxFromCEA
388
- const sourceChain = params.from.chain;
389
- const seedAmount = (_p = params.value) !== null && _p !== void 0 ? _p : BigInt(0);
390
- if (isValueOnlyNativeSeedToOwnUea(params, ueaAddress)) {
391
- return Object.assign(Object.assign({}, baseDescriptor), { sourceChain, burnAmount: BigInt(0), gasToken: selectors_1.ZERO_ADDRESS, gasFee: BigInt(0), nativeSeedOnly: true, nativeSeedAmount: seedAmount });
293
+ // Build CEA multicalls
294
+ const ceaMulticalls = [];
295
+ const hasData = (0, data_utils_1.hasExecutablePayloadData)(params.data);
296
+ const fundsToken = params.funds?.amount
297
+ ? (0, route_handlers_1.resolveR2DestinationFundsToken)(params.funds.token, targetChain, ctx.pushNetwork)
298
+ : undefined;
299
+ const nativeFundsValue = params.funds?.amount && fundsToken?.mechanism === 'native'
300
+ ? params.funds.amount
301
+ : BigInt(0);
302
+ if (hasData) {
303
+ if (Array.isArray(params.data)) {
304
+ ceaMulticalls.push(...params.data);
392
305
  }
393
- // SVM chains use PDA-based CEA, not factory-deployed CEA
394
- if ((0, payload_builders_1.isSvmChain)(sourceChain)) {
395
- const lockerContract = chain_1.CHAIN_INFO[sourceChain].lockerContract;
396
- if (!lockerContract) {
397
- throw new Error(`No SVM gateway program configured for chain ${sourceChain}`);
398
- }
399
- const programPk = new web3_js_1.PublicKey(lockerContract);
400
- const gatewayProgramHex = ('0x' +
401
- Buffer.from(programPk.toBytes()).toString('hex'));
402
- // Derive CEA PDA
403
- const ueaBytes = Buffer.from(ueaAddress.slice(2), 'hex');
404
- const [ceaPda] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from('push_identity'), ueaBytes], programPk);
405
- const ceaPdaHex = ('0x' +
406
- Buffer.from(ceaPda.toBytes()).toString('hex'));
407
- let amount = BigInt(0);
408
- let prc20Token;
409
- if (((_q = params.funds) === null || _q === void 0 ? void 0 : _q.amount) && params.funds.amount > BigInt(0)) {
410
- amount = params.funds.amount;
411
- const token = params.funds.token;
412
- if (token && token.address) {
413
- prc20Token = push_chain_1.PushChain.utils.tokens.getPRC20Address(token).address;
414
- }
415
- else {
416
- prc20Token = (0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork);
306
+ else {
307
+ // When ERC-20 funds are provided with a single payload, auto-prepend a
308
+ // transfer() call so the tokens minted to the CEA are forwarded to the
309
+ // target address. This mirrors the Route 1 behavior in buildExecuteMulticall.
310
+ if (params.funds?.amount) {
311
+ if (fundsToken && fundsToken.mechanism !== 'native') {
312
+ const erc20Transfer = (0, viem_1.encodeFunctionData)({
313
+ abi: erc20_evm_1.ERC20_EVM,
314
+ functionName: 'transfer',
315
+ args: [target.address, params.funds.amount],
316
+ });
317
+ ceaMulticalls.push({
318
+ to: fundsToken.address,
319
+ value: BigInt(0),
320
+ data: erc20Transfer,
321
+ });
417
322
  }
418
323
  }
419
- else if (params.value && params.value > BigInt(0)) {
420
- amount = params.value;
421
- prc20Token = (0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork);
422
- }
423
- else {
424
- // Payload-only Route 3 SVM
425
- prc20Token = (0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork);
324
+ // Single call with data. Forward native value (if any) so the target
325
+ // contract receives it alongside the payload call. The vault deposits
326
+ // native value to the CEA, and the multicall forwards it to the target.
327
+ ceaMulticalls.push({
328
+ to: target.address,
329
+ value: (params.value ?? BigInt(0)) + nativeFundsValue,
330
+ data: params.data,
331
+ });
332
+ }
333
+ }
334
+ else {
335
+ if (params.funds?.amount &&
336
+ fundsToken &&
337
+ fundsToken.mechanism !== 'native') {
338
+ const erc20Transfer = (0, viem_1.encodeFunctionData)({
339
+ abi: erc20_evm_1.ERC20_EVM,
340
+ functionName: 'transfer',
341
+ args: [target.address, params.funds.amount],
342
+ });
343
+ ceaMulticalls.push({
344
+ to: fundsToken.address,
345
+ value: BigInt(0),
346
+ data: erc20Transfer,
347
+ });
348
+ }
349
+ if (params.value) {
350
+ // Skip multicall when sending native value to own CEA — gateway deposits directly.
351
+ // Self-call with value would revert (CEA._handleMulticall rejects it).
352
+ if (target.address.toLowerCase() !== ceaAddress.toLowerCase()) {
353
+ ceaMulticalls.push({
354
+ to: target.address,
355
+ value: params.value,
356
+ data: '0x',
357
+ });
426
358
  }
427
- let gasToken = selectors_1.ZERO_ADDRESS;
428
- let gasFee = BigInt(0);
429
- let gasPrice = BigInt(0);
430
- let sizing;
431
- let effectiveGasLimit = gasLimit;
432
- if (prc20Token !== selectors_1.ZERO_ADDRESS) {
433
- let result = yield (0, gas_calculator_1.queryOutboundGasFee)(ctx, prc20Token, effectiveGasLimit, sourceChain);
434
- result = yield (0, svm_rent_1.ensureSvmFinalizeGasBudgetQuote)({
435
- ctx,
436
- ueaAddress,
437
- targetChain: sourceChain,
438
- prc20Token,
439
- quote: result,
440
- splMintBase58: undefined,
441
- burnAmount: BigInt(0),
442
- pathTag: 'buildHopDescriptor',
359
+ }
360
+ else if (nativeFundsValue > BigInt(0)) {
361
+ if (target.address.toLowerCase() !== ceaAddress.toLowerCase()) {
362
+ ceaMulticalls.push({
363
+ to: target.address,
364
+ value: nativeFundsValue,
365
+ data: '0x',
443
366
  });
444
- gasToken = result.gasToken;
445
- gasFee = result.gasFee;
446
- gasPrice = result.gasPrice;
447
- sizing = result.sizing;
448
- effectiveGasLimit = result.gasLimitUsed;
449
367
  }
450
- return Object.assign(Object.assign({}, baseDescriptor), { sourceChain, ceaAddress: ceaPdaHex, isSvmTarget: true, prc20Token,
451
- // Route 3 drains pre-existing funds from the external CEA. The
452
- // Push-side outbound is a payload relay, so no PRC-20 is burned.
453
- burnAmount: BigInt(0), gasToken,
454
- gasFee,
455
- gasPrice, gasLimit: effectiveGasLimit, sizing });
456
368
  }
457
- const { cea: ceaAddress } = yield (0, cea_utils_1.getCEAAddress)(ueaAddress, sourceChain, (_r = ctx.rpcUrls[sourceChain]) === null || _r === void 0 ? void 0 : _r[0]);
458
- // CEA auto-deploys on-chain: Vault.finalizeUniversalTx calls CEAFactory.deployCEA()
459
- // if CEA doesn't exist yet. No SDK-side blocking needed.
460
- // Determine token/amount for the inbound
461
- let tokenAddress = selectors_1.ZERO_ADDRESS;
369
+ }
370
+ // Determine PRC-20 token and burn amount
371
+ let prc20Token = selectors_1.ZERO_ADDRESS;
372
+ let burnAmount = BigInt(0);
373
+ if (params.funds?.amount) {
374
+ const token = params.funds.token;
375
+ if (token) {
376
+ prc20Token = push_chain_1.PushChain.utils.tokens.getPRC20Address(token).address;
377
+ burnAmount = params.funds.amount;
378
+ }
379
+ }
380
+ else if (params.value && params.value > BigInt(0)) {
381
+ prc20Token = (0, helpers_1.getNativePRC20ForChain)(targetChain, ctx.pushNetwork);
382
+ burnAmount = params.value;
383
+ }
384
+ else if ((0, data_utils_1.hasExecutablePayloadData)(params.data)) {
385
+ // PAYLOAD-only (no value transfer): still need native PRC-20 token for
386
+ // chain namespace lookup + gas fees, but burn amount must be zero.
387
+ // Matches the direct executeUoaToCea path (route-handlers.ts).
388
+ // Using burnAmount=1 would trigger transferFrom on the PRC-20 token,
389
+ // which reverts when the UEA has no PRC-20 balance for that chain.
390
+ prc20Token = (0, helpers_1.getNativePRC20ForChain)(targetChain, ctx.pushNetwork);
391
+ burnAmount = BigInt(0);
392
+ }
393
+ // Query gas fee
394
+ let gasToken = selectors_1.ZERO_ADDRESS;
395
+ let gasFee = BigInt(0);
396
+ let gasPrice = BigInt(0);
397
+ let sizing;
398
+ if (prc20Token !== selectors_1.ZERO_ADDRESS) {
399
+ const result = await (0, gas_calculator_1.queryOutboundGasFee)(ctx, prc20Token, gasLimit, targetChain);
400
+ gasToken = result.gasToken;
401
+ gasFee = result.gasFee;
402
+ gasPrice = result.gasPrice;
403
+ sizing = result.sizing;
404
+ }
405
+ return {
406
+ ...baseDescriptor,
407
+ targetChain,
408
+ ceaAddress,
409
+ ceaMulticalls,
410
+ prc20Token,
411
+ burnAmount,
412
+ gasToken,
413
+ gasFee,
414
+ gasPrice,
415
+ sizing,
416
+ };
417
+ }
418
+ case route_detector_1.TransactionRoute.CEA_TO_PUSH: {
419
+ // Route 3: Build CEA multicalls for sendUniversalTxFromCEA
420
+ const sourceChain = params.from.chain;
421
+ const seedAmount = params.value ?? BigInt(0);
422
+ if (isValueOnlyNativeSeedToOwnUea(params, ueaAddress)) {
423
+ return {
424
+ ...baseDescriptor,
425
+ sourceChain,
426
+ burnAmount: BigInt(0),
427
+ gasToken: selectors_1.ZERO_ADDRESS,
428
+ gasFee: BigInt(0),
429
+ nativeSeedOnly: true,
430
+ nativeSeedAmount: seedAmount,
431
+ };
432
+ }
433
+ // SVM chains use PDA-based CEA, not factory-deployed CEA
434
+ if ((0, payload_builders_1.isSvmChain)(sourceChain)) {
435
+ const lockerContract = chain_1.CHAIN_INFO[sourceChain].lockerContract;
436
+ if (!lockerContract) {
437
+ throw new Error(`No SVM gateway program configured for chain ${sourceChain}`);
438
+ }
439
+ const programPk = new web3_js_1.PublicKey(lockerContract);
440
+ const gatewayProgramHex = ('0x' +
441
+ Buffer.from(programPk.toBytes()).toString('hex'));
442
+ // Derive CEA PDA
443
+ const ueaBytes = Buffer.from(ueaAddress.slice(2), 'hex');
444
+ const [ceaPda] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from('push_identity'), ueaBytes], programPk);
445
+ const ceaPdaHex = ('0x' +
446
+ Buffer.from(ceaPda.toBytes()).toString('hex'));
462
447
  let amount = BigInt(0);
463
- let nativeValue = BigInt(0);
464
- if ((_s = params.funds) === null || _s === void 0 ? void 0 : _s.amount) {
448
+ let prc20Token;
449
+ if (params.funds?.amount && params.funds.amount > BigInt(0)) {
450
+ amount = params.funds.amount;
465
451
  const token = params.funds.token;
466
- if (token) {
467
- if (token.mechanism === 'native') {
468
- amount = params.funds.amount;
469
- nativeValue = params.funds.amount;
470
- }
471
- else {
472
- tokenAddress = token.address;
473
- amount = params.funds.amount;
474
- }
452
+ const pc20 = params._pc20;
453
+ if (pc20?.direction === 'import') {
454
+ // PC20 CEA burns use the native PRC-20 only for the outbound
455
+ // relay's chain namespace and gas. The wrapper amount is carried
456
+ // inside the Solana self-call and must not be burned on Push.
457
+ prc20Token = (0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork);
458
+ }
459
+ else if (token && token.address) {
460
+ prc20Token = push_chain_1.PushChain.utils.tokens.getPRC20Address(token).address;
461
+ }
462
+ else {
463
+ prc20Token = (0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork);
475
464
  }
476
465
  }
477
466
  else if (params.value && params.value > BigInt(0)) {
478
467
  amount = params.value;
479
- nativeValue = params.value;
468
+ prc20Token = (0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork);
469
+ }
470
+ else {
471
+ // Payload-only Route 3 SVM
472
+ prc20Token = (0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork);
480
473
  }
481
- // The PRC-20 for the outbound wrapper (Route 2 to source chain)
482
- const prc20Token = (0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork);
483
474
  let gasToken = selectors_1.ZERO_ADDRESS;
484
475
  let gasFee = BigInt(0);
485
476
  let gasPrice = BigInt(0);
486
477
  let sizing;
478
+ let effectiveGasLimit = gasLimit;
487
479
  if (prc20Token !== selectors_1.ZERO_ADDRESS) {
488
- const result = yield (0, gas_calculator_1.queryOutboundGasFee)(ctx, prc20Token, gasLimit, sourceChain);
480
+ let result = await (0, gas_calculator_1.queryOutboundGasFee)(ctx, prc20Token, effectiveGasLimit, sourceChain);
481
+ result = await (0, svm_rent_1.ensureSvmFinalizeGasBudgetQuote)({
482
+ ctx,
483
+ ueaAddress,
484
+ targetChain: sourceChain,
485
+ prc20Token,
486
+ quote: result,
487
+ splMintBase58: undefined,
488
+ burnAmount: BigInt(0),
489
+ pathTag: 'buildHopDescriptor',
490
+ });
489
491
  gasToken = result.gasToken;
490
492
  gasFee = result.gasFee;
491
493
  gasPrice = result.gasPrice;
492
494
  sizing = result.sizing;
495
+ effectiveGasLimit = result.gasLimitUsed;
493
496
  }
494
- return Object.assign(Object.assign({}, baseDescriptor), { sourceChain,
495
- ceaAddress,
496
- prc20Token,
497
- // Route 3 is payload-only on Push: the CEA-side sendUniversalTxToUEA
498
- // carries `amount`. Burning here would require the UEA to pre-hold the
499
- // token it is trying to receive from the external CEA.
500
- burnAmount: BigInt(0), gasToken,
497
+ return {
498
+ ...baseDescriptor,
499
+ sourceChain,
500
+ ceaAddress: ceaPdaHex,
501
+ isSvmTarget: true,
502
+ prc20Token,
503
+ // Route 3 drains pre-existing funds from the external CEA. The
504
+ // Push-side outbound is a payload relay, so no PRC-20 is burned.
505
+ burnAmount: BigInt(0),
506
+ gasToken,
501
507
  gasFee,
502
508
  gasPrice,
503
- sizing });
509
+ gasLimit: effectiveGasLimit,
510
+ sizing,
511
+ };
512
+ }
513
+ const { cea: ceaAddress } = await (0, cea_utils_1.getCEAAddress)(ueaAddress, sourceChain, ctx.rpcUrls[sourceChain]?.[0]);
514
+ // CEA auto-deploys on-chain: Vault.finalizeUniversalTx calls CEAFactory.deployCEA()
515
+ // if CEA doesn't exist yet. No SDK-side blocking needed.
516
+ // Determine token/amount for the inbound
517
+ let tokenAddress = selectors_1.ZERO_ADDRESS;
518
+ let amount = BigInt(0);
519
+ let nativeValue = BigInt(0);
520
+ if (params.funds?.amount) {
521
+ const token = params.funds.token;
522
+ if (token) {
523
+ if (token.mechanism === 'native') {
524
+ amount = params.funds.amount;
525
+ nativeValue = params.funds.amount;
526
+ }
527
+ else {
528
+ tokenAddress = token.address;
529
+ amount = params.funds.amount;
530
+ }
531
+ }
532
+ }
533
+ else if (params.value && params.value > BigInt(0)) {
534
+ amount = params.value;
535
+ nativeValue = params.value;
504
536
  }
505
- default:
506
- return baseDescriptor;
537
+ // The PRC-20 for the outbound wrapper (Route 2 to source chain)
538
+ const prc20Token = (0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork);
539
+ let gasToken = selectors_1.ZERO_ADDRESS;
540
+ let gasFee = BigInt(0);
541
+ let gasPrice = BigInt(0);
542
+ let sizing;
543
+ if (prc20Token !== selectors_1.ZERO_ADDRESS) {
544
+ const result = await (0, gas_calculator_1.queryOutboundGasFee)(ctx, prc20Token, gasLimit, sourceChain);
545
+ gasToken = result.gasToken;
546
+ gasFee = result.gasFee;
547
+ gasPrice = result.gasPrice;
548
+ sizing = result.sizing;
549
+ }
550
+ return {
551
+ ...baseDescriptor,
552
+ sourceChain,
553
+ ceaAddress,
554
+ prc20Token,
555
+ // Route 3 is payload-only on Push: the CEA-side sendUniversalTxToUEA
556
+ // carries `amount`. Burning here would require the UEA to pre-hold the
557
+ // token it is trying to receive from the external CEA.
558
+ burnAmount: BigInt(0),
559
+ gasToken,
560
+ gasFee,
561
+ gasPrice,
562
+ sizing,
563
+ };
507
564
  }
508
- });
565
+ default:
566
+ return baseDescriptor;
567
+ }
509
568
  }
510
569
  // ============================================================================
511
570
  // classifyIntoSegments
@@ -622,15 +681,15 @@ function categoryRank(c) {
622
681
  return c === 'C' ? 2 : c === 'B' ? 1 : 0;
623
682
  }
624
683
  function mergeMaxPCForGas(current, next) {
625
- const a = current !== null && current !== void 0 ? current : BigInt(0);
626
- const b = next !== null && next !== void 0 ? next : BigInt(0);
684
+ const a = current ?? BigInt(0);
685
+ const b = next ?? BigInt(0);
627
686
  if (a === BigInt(0) || b === BigInt(0))
628
687
  return BigInt(0);
629
688
  return a + b;
630
689
  }
631
690
  function mergeGasPrice(current, next) {
632
- const a = current !== null && current !== void 0 ? current : BigInt(0);
633
- const b = next !== null && next !== void 0 ? next : BigInt(0);
691
+ const a = current ?? BigInt(0);
692
+ const b = next ?? BigInt(0);
634
693
  return a > b ? a : b;
635
694
  }
636
695
  // ============================================================================
@@ -649,12 +708,11 @@ function getSegmentType(route) {
649
708
  }
650
709
  }
651
710
  function sumMulticallNativeValue(multicalls) {
652
- 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));
711
+ return (multicalls ?? []).reduce((sum, mc) => sum + (mc.value ?? BigInt(0)), BigInt(0));
653
712
  }
654
713
  function isValueOnlyNativeSeedToOwnUea(params, ueaAddress, options) {
655
- var _a, _b;
656
- return (((_a = params.value) !== null && _a !== void 0 ? _a : BigInt(0)) > BigInt(0) &&
657
- ((options === null || options === void 0 ? void 0 : options.allowFunds) === true || !((_b = params.funds) === null || _b === void 0 ? void 0 : _b.amount)) &&
714
+ return ((params.value ?? BigInt(0)) > BigInt(0) &&
715
+ (options?.allowFunds === true || !params.funds?.amount) &&
658
716
  (0, data_utils_1.isEmptyPayloadData)(params.data) &&
659
717
  typeof params.to === 'string' &&
660
718
  params.to.toLowerCase() === ueaAddress.toLowerCase());
@@ -668,30 +726,42 @@ function getRootFundsDeposit(hops) {
668
726
  if (hop.route !== 'UOA_TO_PUSH')
669
727
  break;
670
728
  const funds = hop.params.funds;
671
- if (!(funds === null || funds === void 0 ? void 0 : funds.amount) || funds.amount <= BigInt(0))
729
+ if (!funds?.amount || funds.amount <= BigInt(0))
672
730
  continue;
673
731
  if (!rootFunds) {
674
- rootFunds = Object.assign({}, funds);
732
+ rootFunds = { ...funds };
675
733
  continue;
676
734
  }
735
+ // Token identity across hops. Compared on the fields both union members
736
+ // share (address) plus, for MoveableTokens, symbol/mechanism. A PC20
737
+ // reference is identified by chain+address alone — it has no symbol or
738
+ // mechanism to compare, and its chain is what distinguishes two wrappers
739
+ // that happen to share an address on different chains.
677
740
  const currentToken = rootFunds.token;
678
741
  const nextToken = funds.token;
679
742
  const sameToken = currentToken === nextToken ||
680
743
  (!!currentToken &&
681
744
  !!nextToken &&
682
- currentToken.address.toLowerCase() ===
683
- nextToken.address.toLowerCase() &&
684
- currentToken.symbol === nextToken.symbol &&
685
- currentToken.mechanism === nextToken.mechanism);
745
+ currentToken.address.toLowerCase() === nextToken.address.toLowerCase() &&
746
+ (0, orchestrator_types_1.isPC20Reference)(currentToken) === (0, orchestrator_types_1.isPC20Reference)(nextToken) &&
747
+ ((0, orchestrator_types_1.isPC20Reference)(currentToken) && (0, orchestrator_types_1.isPC20Reference)(nextToken)
748
+ ? currentToken.chain === nextToken.chain
749
+ : currentToken.symbol ===
750
+ nextToken.symbol &&
751
+ currentToken.mechanism ===
752
+ nextToken.mechanism));
686
753
  if (!sameToken) {
687
754
  throw new Error('Cascade can only bridge one origin token in root Push execution hops. Split mixed-token deposits into separate transactions.');
688
755
  }
689
- rootFunds = Object.assign(Object.assign({}, rootFunds), { amount: rootFunds.amount + funds.amount });
756
+ rootFunds = {
757
+ ...rootFunds,
758
+ amount: rootFunds.amount + funds.amount,
759
+ };
690
760
  }
691
761
  return rootFunds;
692
762
  }
693
763
  function isNativeSeedOnlyHop(hop) {
694
- return (hop === null || hop === void 0 ? void 0 : hop.nativeSeedOnly) === true;
764
+ return hop?.nativeSeedOnly === true;
695
765
  }
696
766
  function isNativeSeedOnlySegment(segment) {
697
767
  return (segment.type === 'INBOUND_FROM_CEA' &&
@@ -702,7 +772,6 @@ function segmentNeedsOutboundGas(segment) {
702
772
  return segment.type !== 'PUSH_EXECUTION' && !isNativeSeedOnlySegment(segment);
703
773
  }
704
774
  function buildRootOutboundIndexByHop(segments, hops) {
705
- var _a;
706
775
  const hopIndexByDescriptor = new Map();
707
776
  hops.forEach((hop, index) => hopIndexByDescriptor.set(hop, index));
708
777
  const nextIndexByChain = new Map();
@@ -718,7 +787,7 @@ function buildRootOutboundIndexByHop(segments, hops) {
718
787
  }
719
788
  if (!chain)
720
789
  continue;
721
- const outboundIndex = (_a = nextIndexByChain.get(chain)) !== null && _a !== void 0 ? _a : 0;
790
+ const outboundIndex = nextIndexByChain.get(chain) ?? 0;
722
791
  for (const hop of segment.hops) {
723
792
  const hopIndex = hopIndexByDescriptor.get(hop);
724
793
  if (hopIndex !== undefined) {
@@ -730,16 +799,15 @@ function buildRootOutboundIndexByHop(segments, hops) {
730
799
  return result;
731
800
  }
732
801
  function sumNativeSeedAmount(hops) {
733
- return hops.reduce((sum, hop) => { var _a; return sum + ((_a = hop.nativeSeedAmount) !== null && _a !== void 0 ? _a : BigInt(0)); }, BigInt(0));
802
+ return hops.reduce((sum, hop) => sum + (hop.nativeSeedAmount ?? BigInt(0)), BigInt(0));
734
803
  }
735
804
  function getCascadeNativeSeed(segments) {
736
- var _a, _b, _c;
737
805
  let total = BigInt(0);
738
806
  for (const segment of segments) {
739
807
  for (const hop of segment.hops) {
740
- const value = (_a = hop.params.value) !== null && _a !== void 0 ? _a : BigInt(0);
808
+ const value = hop.params.value ?? BigInt(0);
741
809
  if (isNativeSeedOnlyHop(hop)) {
742
- total += (_b = hop.nativeSeedAmount) !== null && _b !== void 0 ? _b : value;
810
+ total += hop.nativeSeedAmount ?? value;
743
811
  continue;
744
812
  }
745
813
  // In a CEA->Push hop that also supplies `funds`, `value` is native PC
@@ -747,7 +815,7 @@ function getCascadeNativeSeed(segments) {
747
815
  // later Push-side cascade calls and must be counted for sizing fresh UEAs.
748
816
  if (segment.type === 'INBOUND_FROM_CEA' &&
749
817
  value > BigInt(0) &&
750
- ((_c = hop.params.funds) === null || _c === void 0 ? void 0 : _c.amount)) {
818
+ hop.params.funds?.amount) {
751
819
  total += value;
752
820
  }
753
821
  }
@@ -755,7 +823,6 @@ function getCascadeNativeSeed(segments) {
755
823
  return total;
756
824
  }
757
825
  function getInboundFundingForBurnToken(segments, beforeSegmentIndex, burnToken, pushNetwork) {
758
- var _a;
759
826
  let total = BigInt(0);
760
827
  const normalizedBurnToken = burnToken.toLowerCase();
761
828
  for (const segment of segments.slice(0, beforeSegmentIndex)) {
@@ -765,7 +832,7 @@ function getInboundFundingForBurnToken(segments, beforeSegmentIndex, burnToken,
765
832
  const params = hop.params;
766
833
  let fundedToken;
767
834
  let fundedAmount = BigInt(0);
768
- if ((_a = params.funds) === null || _a === void 0 ? void 0 : _a.amount) {
835
+ if (params.funds?.amount) {
769
836
  const token = params.funds.token;
770
837
  if (token) {
771
838
  fundedToken = push_chain_1.PushChain.utils.tokens.getPRC20Address(token).address;
@@ -779,630 +846,673 @@ function getInboundFundingForBurnToken(segments, beforeSegmentIndex, burnToken,
779
846
  fundedToken = (0, helpers_1.getNativePRC20ForChain)(hop.sourceChain, pushNetwork);
780
847
  fundedAmount = params.value;
781
848
  }
782
- if ((fundedToken === null || fundedToken === void 0 ? void 0 : fundedToken.toLowerCase()) === normalizedBurnToken) {
849
+ if (fundedToken?.toLowerCase() === normalizedBurnToken) {
783
850
  total += fundedAmount;
784
851
  }
785
852
  }
786
853
  }
787
854
  return total;
788
855
  }
789
- function composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce, options) {
790
- return tslib_1.__awaiter(this, void 0, void 0, function* () {
791
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u;
792
- let accumulatedPushMulticalls = [];
793
- let requiredNativeValue = BigInt(0);
794
- const gatewayPcAddress = (0, helpers_1.getUniversalGatewayPCAddress)();
795
- const currentUeaBalance = ueaBalance !== null && ueaBalance !== void 0 ? ueaBalance : BigInt(0);
796
- const cascadeNativeSeed = getCascadeNativeSeed(segments);
797
- const effectiveUeaBalance = currentUeaBalance + cascadeNativeSeed;
798
- const enforceGasCheck = (options === null || options === void 0 ? void 0 : options.enforceGasCheck) === true ||
799
- segments.some((segment) => segment.hops.some((hop) => { var _a; return ((_a = hop.params.options) === null || _a === void 0 ? void 0 : _a.enforceGasCheck) === true; }));
800
- if (cascadeNativeSeed > BigInt(0)) {
801
- (0, context_1.printLog)(ctx, `composeCascade — counting cascade native seed ${cascadeNativeSeed.toString()} for cascade sizing (current UEA balance ${currentUeaBalance.toString()})`);
856
+ async function composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce, options) {
857
+ let accumulatedPushMulticalls = [];
858
+ let requiredNativeValue = BigInt(0);
859
+ const gatewayPcAddress = (0, helpers_1.getUniversalGatewayPCAddress)();
860
+ const currentUeaBalance = ueaBalance ?? BigInt(0);
861
+ const cascadeNativeSeed = getCascadeNativeSeed(segments);
862
+ const effectiveUeaBalance = currentUeaBalance + cascadeNativeSeed;
863
+ const enforceGasCheck = options?.enforceGasCheck === true ||
864
+ segments.some((segment) => segment.hops.some((hop) => hop.params.options?.enforceGasCheck === true));
865
+ if (cascadeNativeSeed > BigInt(0)) {
866
+ (0, context_1.printLog)(ctx, `composeCascade — counting cascade native seed ${cascadeNativeSeed.toString()} for cascade sizing (current UEA balance ${currentUeaBalance.toString()})`);
867
+ }
868
+ // Compute per-outbound nativeValueForGas from UEA balance
869
+ // Each outbound segment needs native value for the gas swap on the destination chain.
870
+ // The contract refunds excess, so over-allocating is safe.
871
+ const numOutbounds = segments.filter(segmentNeedsOutboundGas).length;
872
+ const CASCADE_GAS_RESERVE = BigInt(3e18); // 3 PC reserve for gas costs
873
+ // Cascade pre-flight: catches the zero-balance silent-segment-zero failure
874
+ // mode. When `ueaBalance <= CASCADE_GAS_RESERVE`, `perOutboundNativeValue`
875
+ // is left undefined and downstream segments silently allocate value=0,
876
+ // producing under-funded swaps that revert inside Uniswap. Warn by default
877
+ // and throw only when enforceGasCheck=true.
878
+ if (numOutbounds > 0) {
879
+ (0, preflight_1.runPreflight)({
880
+ ctx,
881
+ ueaAddress,
882
+ ueaBalance: effectiveUeaBalance,
883
+ // Required = at least one wei of allocatable PC after reserve. The
884
+ // per-segment min-swap check below catches finer-grained shortfalls.
885
+ requiredValue: BigInt(1),
886
+ gasReserve: CASCADE_GAS_RESERVE,
887
+ pathTag: 'CASCADE',
888
+ enforceGasCheck,
889
+ });
890
+ }
891
+ let perOutboundNativeValue;
892
+ if (numOutbounds > 0 && effectiveUeaBalance > CASCADE_GAS_RESERVE) {
893
+ perOutboundNativeValue =
894
+ (effectiveUeaBalance - CASCADE_GAS_RESERVE) / BigInt(numOutbounds);
895
+ }
896
+ let universalCoreAddressForSwap;
897
+ const getUniversalCoreAddressForSwap = async () => {
898
+ if (!universalCoreAddressForSwap) {
899
+ universalCoreAddressForSwap =
900
+ await ctx.pushClient.readContract({
901
+ address: gatewayPcAddress,
902
+ abi: abi_1.UNIVERSAL_GATEWAY_PC,
903
+ functionName: 'universalCore',
904
+ args: [],
905
+ });
802
906
  }
803
- // Compute per-outbound nativeValueForGas from UEA balance
804
- // Each outbound segment needs native value for the gas swap on the destination chain.
805
- // The contract refunds excess, so over-allocating is safe.
806
- const numOutbounds = segments.filter(segmentNeedsOutboundGas).length;
807
- const CASCADE_GAS_RESERVE = BigInt(3e18); // 3 PC reserve for gas costs
808
- // Cascade pre-flight: catches the zero-balance silent-segment-zero failure
809
- // mode. When `ueaBalance <= CASCADE_GAS_RESERVE`, `perOutboundNativeValue`
810
- // is left undefined and downstream segments silently allocate value=0,
811
- // producing under-funded swaps that revert inside Uniswap. Warn by default
812
- // and throw only when enforceGasCheck=true.
813
- if (numOutbounds > 0) {
814
- (0, preflight_1.runPreflight)({
815
- ctx,
816
- ueaAddress,
817
- ueaBalance: effectiveUeaBalance,
818
- // Required = at least one wei of allocatable PC after reserve. The
819
- // per-segment min-swap check below catches finer-grained shortfalls.
820
- requiredValue: BigInt(1),
821
- gasReserve: CASCADE_GAS_RESERVE,
822
- pathTag: 'CASCADE',
823
- enforceGasCheck,
824
- });
907
+ return universalCoreAddressForSwap;
908
+ };
909
+ // Pool-price-based native-value override for SVM outbounds.
910
+ // The flat split above works for EVM gas tokens (pBNB/pETH — cheap per unit),
911
+ // but pSOL has a much higher WPC/pSOL pool price, so a flat balance split can
912
+ // underfund the gas swap and make Uniswap revert with "STF". Mirror the
913
+ // direct SVM route handlers' pool-price read for every SVM outbound-bearing
914
+ // segment, including Route 3 SVM inbound round trips.
915
+ const svmSegments = segments.filter((s) => ((s.type === 'OUTBOUND_TO_CEA' && s.hops[0]?.isSvmTarget === true) ||
916
+ (s.type === 'INBOUND_FROM_CEA' &&
917
+ !isNativeSeedOnlySegment(s) &&
918
+ (0, payload_builders_1.isSvmChain)(s.sourceChain))) &&
919
+ s.gasToken &&
920
+ s.gasFee &&
921
+ s.gasFee > BigInt(0));
922
+ const svmNativeValueBySegment = new Map();
923
+ if (svmSegments.length > 0 && effectiveUeaBalance > CASCADE_GAS_RESERVE) {
924
+ const universalCoreAddress = await getUniversalCoreAddressForSwap();
925
+ // Compute per-SVM budget = ueaBalance − (PC needed for EVM outbounds) − 1 PC safety
926
+ // (outer-tx gas + refund headroom). Pass a huge accountBalance to the estimator so
927
+ // its internal cap is effectively disabled; we cap externally to the per-SVM budget.
928
+ const numEvmOutbounds = numOutbounds - svmSegments.length;
929
+ const evmReservation = perOutboundNativeValue && numEvmOutbounds > 0
930
+ ? perOutboundNativeValue * BigInt(numEvmOutbounds)
931
+ : BigInt(0);
932
+ const outerSafety = BigInt(1e18); // 1 PC for outer-tx gas
933
+ const svmTotal = effectiveUeaBalance > evmReservation + outerSafety
934
+ ? effectiveUeaBalance - evmReservation - outerSafety
935
+ : BigInt(0);
936
+ const perSvmCap = svmTotal / BigInt(svmSegments.length);
937
+ const UNCAPPED_BALANCE = BigInt('1000000000000000000000000000000'); // effectively disable estimator's internal cap
938
+ for (const seg of svmSegments) {
939
+ // Cascade-level segment index (position in `segments`, not in `svmSegments`)
940
+ // so the error's `segmentIndex` lines up with what the caller sees.
941
+ const cascadeIdx = segments.indexOf(seg);
942
+ const estimated = await (0, gas_calculator_1.estimateNativeValueForSwap)(ctx, universalCoreAddress, seg.gasToken, seg.gasFee, UNCAPPED_BALANCE);
943
+ // SVM warn-threshold telemetry (no truncation — pre-flight handles drain protection).
944
+ (0, preflight_1.maybeFireSvmWarnThreshold)(ctx, estimated, seg.gasToken, 'CASCADE');
945
+ const flat = perOutboundNativeValue ?? BigInt(0);
946
+ let value = estimated > flat ? estimated : flat;
947
+ if (perSvmCap > BigInt(0) && value > perSvmCap) {
948
+ const cappedValue = perSvmCap >= estimated ? perSvmCap : estimated;
949
+ if (cappedValue !== perSvmCap) {
950
+ (0, context_1.printLog)(ctx, `composeCascade — SVM outbound to ${seg.targetChain ?? seg.sourceChain}: perSvmCap=${perSvmCap} is below live pool quote=${estimated}; using quote to avoid underfunded gas swap`);
951
+ }
952
+ value = cappedValue;
953
+ }
954
+ // Upward-allocation ceiling (mirrors R3 SVM Fix #4 in route-handlers.ts:1690).
955
+ // When UEA balance is huge, the flat split or perSvmCap can produce a value
956
+ // far above what the swap actually needs. Submitting that into a Uniswap
957
+ // pool moves the price for other users. Cap at max(200 PC, 5×estimated):
958
+ // - fair pool (estimated ≈ 100 PC): cap = 500 PC → caps voluntary excess
959
+ // - skewed pool (estimated > 200 PC): cap = 5×estimated → still allows
960
+ // pool-skew margin; min-swap check below still fires if value < estimated
961
+ // The contract still refunds excess via swapAndBurnGas, so this is purely
962
+ // a guard against submitting wasteful msg.value to fair-priced pools.
963
+ const UPWARD_BASE_CEILING_CASCADE_SVM = BigInt(200) * BigInt(1e18);
964
+ const upwardCeilingCascadeSvm = estimated * BigInt(5) > UPWARD_BASE_CEILING_CASCADE_SVM
965
+ ? estimated * BigInt(5)
966
+ : UPWARD_BASE_CEILING_CASCADE_SVM;
967
+ if (value > upwardCeilingCascadeSvm) {
968
+ (0, context_1.printLog)(ctx, `composeCascade — SVM outbound to ${seg.targetChain ?? seg.sourceChain}: capping value from ${value} to upwardCeiling=${upwardCeilingCascadeSvm} (pool quote=${estimated})`);
969
+ value = upwardCeilingCascadeSvm;
970
+ }
971
+ (0, context_1.printLog)(ctx, `composeCascade — SVM outbound to ${seg.targetChain ?? seg.sourceChain}: gasFee=${seg.gasFee}, estimatedWpc=${estimated}, flat=${flat}, perSvmCap=${perSvmCap}, upwardCeiling=${upwardCeilingCascadeSvm}, chosen=${value}`);
972
+ // Per-segment minimum-viable-swap check: if the chosen `value` is below
973
+ // the live pool quote (`estimated`), the cap squeezed the budget below
974
+ // what the swap actually needs and the segment will revert inside
975
+ // Uniswap with STF. Throw cleanly with `segmentIndex` so the caller
976
+ // knows which hop's pool is misbehaving.
977
+ if (estimated > BigInt(0) && value < estimated) {
978
+ const shortfall = estimated - value;
979
+ (0, context_2.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_003_03, estimated, value, false, ueaAddress, 'CASCADE', {
980
+ kind: 'NATIVE',
981
+ segmentIndex: cascadeIdx,
982
+ enforceGasCheck,
983
+ shortfall,
984
+ });
985
+ if (!enforceGasCheck) {
986
+ (0, context_1.printLog)(ctx, `composeCascade — SVM outbound segment ${cascadeIdx} has native shortfall ${shortfall.toString()}; proceeding because enforceGasCheck=false`);
987
+ svmNativeValueBySegment.set(seg, value);
988
+ continue;
989
+ }
990
+ (0, context_2.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_003_04, estimated, value, shortfall, ueaAddress, 'CASCADE', { kind: 'NATIVE', segmentIndex: cascadeIdx });
991
+ throw new errors_1.InsufficientUEABalanceError({
992
+ required: estimated,
993
+ available: value,
994
+ shortfall,
995
+ ueaAddress,
996
+ pathTag: 'CASCADE',
997
+ reason: 'NATIVE',
998
+ segmentIndex: cascadeIdx,
999
+ });
1000
+ }
1001
+ svmNativeValueBySegment.set(seg, value);
825
1002
  }
826
- let perOutboundNativeValue;
827
- if (numOutbounds > 0 && effectiveUeaBalance > CASCADE_GAS_RESERVE) {
828
- perOutboundNativeValue =
829
- (effectiveUeaBalance - CASCADE_GAS_RESERVE) / BigInt(numOutbounds);
1003
+ }
1004
+ // Every EVM gas-bearing segment should follow the direct-route path: use the
1005
+ // live WPC/gasToken quote instead of blindly spending the flat balance split.
1006
+ // This includes Route 3 wrappers, which also call sendUniversalTxOutbound on
1007
+ // Push Chain before the source CEA can return. A fixed ceiling below the live
1008
+ // exact-output requirement deterministically reverts inside Uniswap with
1009
+ // `STF`; the caller-facing maxPCForGas field is the explicit spend ceiling.
1010
+ const evmSegments = segments.filter((s) => segmentNeedsOutboundGas(s) &&
1011
+ ((s.type === 'OUTBOUND_TO_CEA' && s.hops[0]?.isSvmTarget !== true) ||
1012
+ (s.type === 'INBOUND_FROM_CEA' &&
1013
+ !(0, payload_builders_1.isSvmChain)(s.sourceChain))) &&
1014
+ s.gasToken &&
1015
+ s.gasFee &&
1016
+ s.gasFee > BigInt(0));
1017
+ const evmNativeValueBySegment = new Map();
1018
+ if (evmSegments.length > 0 && effectiveUeaBalance > CASCADE_GAS_RESERVE) {
1019
+ const universalCoreAddress = await getUniversalCoreAddressForSwap();
1020
+ const UNCAPPED_BALANCE = BigInt('1000000000000000000000000000000');
1021
+ for (const seg of evmSegments) {
1022
+ const value = await (0, gas_calculator_1.estimateNativeValueForSwap)(ctx, universalCoreAddress, seg.gasToken, seg.gasFee, UNCAPPED_BALANCE);
1023
+ (0, context_1.printLog)(ctx, `composeCascade — EVM outbound to ${seg.targetChain ?? seg.sourceChain}: gasFee=${seg.gasFee}, quotedNativeValue=${value}, flat=${perOutboundNativeValue ?? BigInt(0)}`);
1024
+ evmNativeValueBySegment.set(seg, value);
830
1025
  }
831
- let universalCoreAddressForSwap;
832
- const getUniversalCoreAddressForSwap = () => tslib_1.__awaiter(this, void 0, void 0, function* () {
833
- if (!universalCoreAddressForSwap) {
834
- universalCoreAddressForSwap =
835
- yield ctx.pushClient.readContract({
836
- address: gatewayPcAddress,
837
- abi: abi_1.UNIVERSAL_GATEWAY_PC,
838
- functionName: 'universalCore',
839
- args: [],
840
- });
1026
+ }
1027
+ for (let segIdxLoop = segments.length - 1; segIdxLoop >= 0; segIdxLoop--) {
1028
+ const segment = segments[segIdxLoop];
1029
+ const gasPriceForOutboundRequest = (chainForLog) => {
1030
+ const quotedGasPrice = segment.gasPrice ?? BigInt(0);
1031
+ const isInboundFromCea = segment.type === 'INBOUND_FROM_CEA';
1032
+ // If this outbound is being accumulated into an earlier Route 3
1033
+ // inbound payload, it may execute minutes after the quote. Keep
1034
+ // immediate outbounds pinned to the quote, but let delayed nested
1035
+ // outbounds resolve the live base gas price at execution time. Route 3
1036
+ // source-chain outbounds have the same quote-staleness risk because the
1037
+ // source CEA leg can execute after the Push Chain gateway quote changes.
1038
+ const executesAfterInbound = segments
1039
+ .slice(0, segIdxLoop)
1040
+ .some((s) => s.type === 'INBOUND_FROM_CEA');
1041
+ const shouldUseLiveGasPrice = isInboundFromCea || executesAfterInbound;
1042
+ if (shouldUseLiveGasPrice && quotedGasPrice > BigInt(0)) {
1043
+ (0, context_1.printLog)(ctx, `composeCascade — ${isInboundFromCea ? 'Route 3' : 'delayed'} outbound to ${chainForLog ?? 'unknown chain'}: using gasPrice=0 instead of quoted ${quotedGasPrice} so UGPC resolves the live base price at execution time`);
841
1044
  }
842
- return universalCoreAddressForSwap;
843
- });
844
- // Pool-price-based native-value override for SVM outbounds.
845
- // The flat split above works for EVM gas tokens (pBNB/pETH — cheap per unit),
846
- // but pSOL has a much higher WPC/pSOL pool price, so a flat balance split can
847
- // underfund the gas swap and make Uniswap revert with "STF". Mirror the
848
- // direct SVM route handlers' pool-price read for every SVM outbound-bearing
849
- // segment, including Route 3 SVM inbound round trips.
850
- const svmSegments = segments.filter((s) => {
851
- var _a;
852
- return ((s.type === 'OUTBOUND_TO_CEA' && ((_a = s.hops[0]) === null || _a === void 0 ? void 0 : _a.isSvmTarget) === true) ||
853
- (s.type === 'INBOUND_FROM_CEA' &&
854
- !isNativeSeedOnlySegment(s) &&
855
- (0, payload_builders_1.isSvmChain)(s.sourceChain))) &&
856
- s.gasToken &&
857
- s.gasFee &&
858
- s.gasFee > BigInt(0);
859
- });
860
- const svmNativeValueBySegment = new Map();
861
- if (svmSegments.length > 0 && effectiveUeaBalance > CASCADE_GAS_RESERVE) {
862
- const universalCoreAddress = yield getUniversalCoreAddressForSwap();
863
- // Compute per-SVM budget = ueaBalance − (PC needed for EVM outbounds) − 1 PC safety
864
- // (outer-tx gas + refund headroom). Pass a huge accountBalance to the estimator so
865
- // its internal cap is effectively disabled; we cap externally to the per-SVM budget.
866
- const numEvmOutbounds = numOutbounds - svmSegments.length;
867
- const evmReservation = perOutboundNativeValue && numEvmOutbounds > 0
868
- ? perOutboundNativeValue * BigInt(numEvmOutbounds)
869
- : BigInt(0);
870
- const outerSafety = BigInt(1e18); // 1 PC for outer-tx gas
871
- const svmTotal = effectiveUeaBalance > evmReservation + outerSafety
872
- ? effectiveUeaBalance - evmReservation - outerSafety
873
- : BigInt(0);
874
- const perSvmCap = svmTotal / BigInt(svmSegments.length);
875
- const UNCAPPED_BALANCE = BigInt('1000000000000000000000000000000'); // effectively disable estimator's internal cap
876
- for (const seg of svmSegments) {
877
- // Cascade-level segment index (position in `segments`, not in `svmSegments`)
878
- // so the error's `segmentIndex` lines up with what the caller sees.
879
- const cascadeIdx = segments.indexOf(seg);
880
- const estimated = yield (0, gas_calculator_1.estimateNativeValueForSwap)(ctx, universalCoreAddress, seg.gasToken, seg.gasFee, UNCAPPED_BALANCE);
881
- // SVM warn-threshold telemetry (no truncation — pre-flight handles drain protection).
882
- (0, preflight_1.maybeFireSvmWarnThreshold)(ctx, estimated, seg.gasToken, 'CASCADE');
883
- const flat = perOutboundNativeValue !== null && perOutboundNativeValue !== void 0 ? perOutboundNativeValue : BigInt(0);
884
- let value = estimated > flat ? estimated : flat;
885
- if (perSvmCap > BigInt(0) && value > perSvmCap) {
886
- const cappedValue = perSvmCap >= estimated ? perSvmCap : estimated;
887
- if (cappedValue !== perSvmCap) {
888
- (0, context_1.printLog)(ctx, `composeCascade — SVM outbound to ${(_a = seg.targetChain) !== null && _a !== void 0 ? _a : seg.sourceChain}: perSvmCap=${perSvmCap} is below live pool quote=${estimated}; using quote to avoid underfunded gas swap`);
889
- }
890
- value = cappedValue;
1045
+ return shouldUseLiveGasPrice ? BigInt(0) : quotedGasPrice;
1046
+ };
1047
+ switch (segment.type) {
1048
+ case 'PUSH_EXECUTION': {
1049
+ // Prepend Push Chain multicalls to accumulated
1050
+ const seedAmount = sumNativeSeedAmount(segment.hops);
1051
+ if (seedAmount > requiredNativeValue) {
1052
+ requiredNativeValue = seedAmount;
1053
+ }
1054
+ requiredNativeValue += sumMulticallNativeValue(segment.mergedPushMulticalls);
1055
+ accumulatedPushMulticalls = [
1056
+ ...(segment.mergedPushMulticalls || []),
1057
+ ...accumulatedPushMulticalls,
1058
+ ];
1059
+ break;
1060
+ }
1061
+ case 'OUTBOUND_TO_CEA': {
1062
+ const firstHop = segment.hops[0];
1063
+ const isSvmSegment = firstHop?.isSvmTarget === true;
1064
+ let outboundPayload;
1065
+ let targetForOutbound;
1066
+ const isMigration = firstHop?.isMigration === true;
1067
+ if (isSvmSegment) {
1068
+ // SVM: use the pre-built SVM payload from the hop descriptor
1069
+ outboundPayload = firstHop.svmPayload ?? '0x';
1070
+ // For SVM, to.address IS the target (program for execute, recipient for withdraw)
1071
+ const svmTarget = firstHop.params.to;
1072
+ targetForOutbound = svmTarget.address;
891
1073
  }
892
- // Upward-allocation ceiling (mirrors R3 SVM Fix #4 in route-handlers.ts:1690).
893
- // When UEA balance is huge, the flat split or perSvmCap can produce a value
894
- // far above what the swap actually needs. Submitting that into a Uniswap
895
- // pool moves the price for other users. Cap at max(200 PC, 5×estimated):
896
- // - fair pool (estimated ≈ 100 PC): cap = 500 PC → caps voluntary excess
897
- // - skewed pool (estimated > 200 PC): cap = 5×estimated → still allows
898
- // pool-skew margin; min-swap check below still fires if value < estimated
899
- // The contract still refunds excess via swapAndBurnGas, so this is purely
900
- // a guard against submitting wasteful msg.value to fair-priced pools.
901
- const UPWARD_BASE_CEILING_CASCADE_SVM = BigInt(200) * BigInt(1e18);
902
- const upwardCeilingCascadeSvm = estimated * BigInt(5) > UPWARD_BASE_CEILING_CASCADE_SVM
903
- ? estimated * BigInt(5)
904
- : UPWARD_BASE_CEILING_CASCADE_SVM;
905
- if (value > upwardCeilingCascadeSvm) {
906
- (0, context_1.printLog)(ctx, `composeCascade — SVM outbound to ${(_b = seg.targetChain) !== null && _b !== void 0 ? _b : seg.sourceChain}: capping value from ${value} to upwardCeiling=${upwardCeilingCascadeSvm} (pool quote=${estimated})`);
907
- value = upwardCeilingCascadeSvm;
1074
+ else if (isMigration) {
1075
+ // Migration: use raw 4-byte MIGRATION_SELECTOR, no multicall wrapping
1076
+ outboundPayload = (0, payload_builders_1.buildMigrationPayload)();
1077
+ targetForOutbound = firstHop?.ceaAddress || ueaAddress;
908
1078
  }
909
- (0, context_1.printLog)(ctx, `composeCascade — SVM outbound to ${(_c = seg.targetChain) !== null && _c !== void 0 ? _c : seg.sourceChain}: gasFee=${seg.gasFee}, estimatedWpc=${estimated}, flat=${flat}, perSvmCap=${perSvmCap}, upwardCeiling=${upwardCeilingCascadeSvm}, chosen=${value}`);
910
- // Per-segment minimum-viable-swap check: if the chosen `value` is below
911
- // the live pool quote (`estimated`), the cap squeezed the budget below
912
- // what the swap actually needs and the segment will revert inside
913
- // Uniswap with STF. Throw cleanly with `segmentIndex` so the caller
914
- // knows which hop's pool is misbehaving.
915
- if (estimated > BigInt(0) && value < estimated) {
916
- const shortfall = estimated - value;
917
- (0, context_2.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_003_03, estimated, value, false, ueaAddress, 'CASCADE', {
918
- kind: 'NATIVE',
919
- segmentIndex: cascadeIdx,
1079
+ else {
1080
+ // EVM: build CEA payload from merged multicalls
1081
+ outboundPayload = (0, payload_builders_1.buildCeaMulticallPayload)(segment.mergedCeaMulticalls || []);
1082
+ // Get CEA address from the first hop
1083
+ targetForOutbound =
1084
+ outboundPayload === '0x'
1085
+ ? selectors_1.ZERO_ADDRESS
1086
+ : firstHop?.ceaAddress || ueaAddress;
1087
+ (0, payload_builders_1.assertCeaFundsParkingInvariant)(targetForOutbound, outboundPayload);
1088
+ }
1089
+ // Per-segment PRC-20 burn-balance pre-check. Fires whenever
1090
+ // burnAmount > 0 even when burn token equals gas token — the gateway's
1091
+ // transferFrom happens BEFORE the gas swap, so user must pre-hold the
1092
+ // burn amount.
1093
+ const segBurnAmount = segment.totalBurnAmount || BigInt(0);
1094
+ const segBurnToken = segment.prc20Token;
1095
+ if (segBurnAmount > BigInt(0) && segBurnToken && !isMigration) {
1096
+ const onHand = await ctx.pushClient.readContract({
1097
+ address: segBurnToken,
1098
+ abi: erc20_evm_1.ERC20_EVM,
1099
+ functionName: 'balanceOf',
1100
+ args: [ueaAddress],
1101
+ });
1102
+ const sufficient = onHand >= segBurnAmount;
1103
+ (0, context_2.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_003_03, segBurnAmount, onHand, sufficient, ueaAddress, 'CASCADE', {
1104
+ kind: 'PRC20',
1105
+ burnToken: segBurnToken,
1106
+ segmentIndex: segIdxLoop,
920
1107
  enforceGasCheck,
921
- shortfall,
1108
+ shortfall: sufficient ? BigInt(0) : segBurnAmount - onHand,
922
1109
  });
923
- if (!enforceGasCheck) {
924
- (0, context_1.printLog)(ctx, `composeCascade — SVM outbound segment ${cascadeIdx} has native shortfall ${shortfall.toString()}; proceeding because enforceGasCheck=false`);
925
- svmNativeValueBySegment.set(seg, value);
926
- continue;
1110
+ const priorPushExecutionMayFundBurn = segments
1111
+ .slice(0, segIdxLoop)
1112
+ .some((s) => s.type === 'PUSH_EXECUTION');
1113
+ const priorInboundFundingForBurn = getInboundFundingForBurnToken(segments, segIdxLoop, segBurnToken, ctx.pushNetwork);
1114
+ const priorInboundMayFundBurn = onHand + priorInboundFundingForBurn >= segBurnAmount;
1115
+ if (!sufficient &&
1116
+ !priorPushExecutionMayFundBurn &&
1117
+ !priorInboundMayFundBurn) {
1118
+ const projectedOnHand = onHand + priorInboundFundingForBurn;
1119
+ const shortfall = segBurnAmount - projectedOnHand;
1120
+ if (!enforceGasCheck) {
1121
+ (0, context_1.printLog)(ctx, `composeCascade — PRC-20 shortfall ${shortfall.toString()} for segment ${segIdxLoop}; proceeding because enforceGasCheck=false`);
1122
+ }
1123
+ else {
1124
+ (0, context_2.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_003_04, segBurnAmount, projectedOnHand, shortfall, ueaAddress, 'CASCADE', {
1125
+ kind: 'PRC20',
1126
+ burnToken: segBurnToken,
1127
+ segmentIndex: segIdxLoop,
1128
+ });
1129
+ throw new errors_1.InsufficientUEABalanceError({
1130
+ required: segBurnAmount,
1131
+ available: projectedOnHand,
1132
+ shortfall,
1133
+ ueaAddress,
1134
+ pathTag: 'CASCADE',
1135
+ reason: 'PRC20',
1136
+ burnToken: segBurnToken,
1137
+ segmentIndex: segIdxLoop,
1138
+ });
1139
+ }
1140
+ }
1141
+ else if (!sufficient && priorPushExecutionMayFundBurn) {
1142
+ (0, context_1.printLog)(ctx, `composeCascade — skipping PRC-20 preflight failure for segment ${segIdxLoop}; earlier Push execution may fund ${segBurnToken} before this outbound runs`);
1143
+ }
1144
+ else if (!sufficient && priorInboundMayFundBurn) {
1145
+ (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`);
927
1146
  }
928
- (0, context_2.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_003_04, estimated, value, shortfall, ueaAddress, 'CASCADE', { kind: 'NATIVE', segmentIndex: cascadeIdx });
929
- throw new errors_1.InsufficientUEABalanceError({
930
- required: estimated,
931
- available: value,
932
- shortfall,
933
- ueaAddress,
934
- pathTag: 'CASCADE',
935
- reason: 'NATIVE',
936
- segmentIndex: cascadeIdx,
937
- });
938
1147
  }
939
- svmNativeValueBySegment.set(seg, value);
940
- }
941
- }
942
- // EVM cascade outbounds should follow the direct R2 path: use the live
943
- // WPC/gasToken quote instead of blindly spending the flat balance split.
944
- // Over-allocating into thin pETH/pBNB pools can revert with Uniswap "STF"
945
- // before the gateway has a chance to refund unused PC.
946
- const evmSegments = segments.filter((s) => {
947
- var _a;
948
- return s.type === 'OUTBOUND_TO_CEA' &&
949
- ((_a = s.hops[0]) === null || _a === void 0 ? void 0 : _a.isSvmTarget) !== true &&
950
- s.gasToken &&
951
- s.gasFee &&
952
- s.gasFee > BigInt(0);
953
- });
954
- const evmNativeValueBySegment = new Map();
955
- if (evmSegments.length > 0 && effectiveUeaBalance > CASCADE_GAS_RESERVE) {
956
- const universalCoreAddress = yield getUniversalCoreAddressForSwap();
957
- const ONE_PC = BigInt('1000000000000000000');
958
- const EVM_NATIVE_VALUE_SAFETY_CAP_CASCADE = BigInt(200) * ONE_PC;
959
- const UNCAPPED_BALANCE = BigInt('1000000000000000000000000000000');
960
- for (const seg of evmSegments) {
961
- let value = yield (0, gas_calculator_1.estimateNativeValueForSwap)(ctx, universalCoreAddress, seg.gasToken, seg.gasFee, UNCAPPED_BALANCE);
962
- if (value > EVM_NATIVE_VALUE_SAFETY_CAP_CASCADE) {
963
- (0, context_1.printLog)(ctx, `composeCascade — EVM outbound to ${seg.targetChain}: capping nativeValueForGas at 200 PC ceiling (was ${value.toString()})`);
964
- value = EVM_NATIVE_VALUE_SAFETY_CAP_CASCADE;
1148
+ // Build outbound request
1149
+ const outboundReq = (0, payload_builders_1.buildOutboundRequest)(targetForOutbound, segment.prc20Token || selectors_1.ZERO_ADDRESS, segment.totalBurnAmount || BigInt(0), segment.gasLimit ?? BigInt(0), outboundPayload, ueaAddress, segment.maxPCForGas ?? BigInt(0), gasPriceForOutboundRequest(firstHop?.targetChain));
1150
+ // Build approval + outbound multicalls
1151
+ const segGasFee = segment.gasFee || BigInt(0);
1152
+ const svmOverride = svmNativeValueBySegment.get(segment);
1153
+ // When UEA balance < CASCADE_GAS_RESERVE, perOutboundNativeValue is
1154
+ // undefined. Mirror the drained-UEA branch in executeUoaToCea
1155
+ // (route-handlers.ts): send the entire available balance divided
1156
+ // across outbound segments. The contract refunds excess via
1157
+ // swapAndBurnGas, so overshooting is safe; undershooting causes STF.
1158
+ let evmFallbackValue;
1159
+ if (effectiveUeaBalance > BigInt(0)) {
1160
+ evmFallbackValue =
1161
+ effectiveUeaBalance / BigInt(Math.max(numOutbounds, 1));
965
1162
  }
966
- (0, context_1.printLog)(ctx, `composeCascade — EVM outbound to ${seg.targetChain}: gasFee=${seg.gasFee}, quotedNativeValue=${value}, flat=${perOutboundNativeValue !== null && perOutboundNativeValue !== void 0 ? perOutboundNativeValue : BigInt(0)}`);
967
- evmNativeValueBySegment.set(seg, value);
968
- }
969
- }
970
- for (let segIdxLoop = segments.length - 1; segIdxLoop >= 0; segIdxLoop--) {
971
- const segment = segments[segIdxLoop];
972
- const gasPriceForOutboundRequest = (chainForLog) => {
973
- var _a;
974
- const quotedGasPrice = (_a = segment.gasPrice) !== null && _a !== void 0 ? _a : BigInt(0);
975
- const isInboundFromCea = segment.type === 'INBOUND_FROM_CEA';
976
- // If this outbound is being accumulated into an earlier Route 3
977
- // inbound payload, it may execute minutes after the quote. Keep
978
- // immediate outbounds pinned to the quote, but let delayed nested
979
- // outbounds resolve the live base gas price at execution time. Route 3
980
- // source-chain outbounds have the same quote-staleness risk because the
981
- // source CEA leg can execute after the Push Chain gateway quote changes.
982
- const executesAfterInbound = segments
983
- .slice(0, segIdxLoop)
984
- .some((s) => s.type === 'INBOUND_FROM_CEA');
985
- const shouldUseLiveGasPrice = isInboundFromCea || executesAfterInbound;
986
- if (shouldUseLiveGasPrice && quotedGasPrice > BigInt(0)) {
987
- (0, context_1.printLog)(ctx, `composeCascade — ${isInboundFromCea ? 'Route 3' : 'delayed'} outbound to ${chainForLog !== null && chainForLog !== void 0 ? chainForLog : 'unknown chain'}: using gasPrice=0 instead of quoted ${quotedGasPrice} so UGPC resolves the live base price at execution time`);
1163
+ else {
1164
+ evmFallbackValue = segGasFee * BigInt(1000000);
988
1165
  }
989
- return shouldUseLiveGasPrice ? BigInt(0) : quotedGasPrice;
990
- };
991
- switch (segment.type) {
992
- case 'PUSH_EXECUTION': {
993
- // Prepend Push Chain multicalls to accumulated
1166
+ const nativeValueForGas = svmOverride ??
1167
+ evmNativeValueBySegment.get(segment) ??
1168
+ perOutboundNativeValue ??
1169
+ evmFallbackValue;
1170
+ // SDK 5.2 Case C: when the segment's sizing is category C, compose
1171
+ // R2/OUTBOUND_TO_CEA segments no longer apply Case A/B/C sizing
1172
+ // (scoped to R1 + R3 only). segment.sizing may still be populated
1173
+ // from the hop descriptor merge, but it is not consulted here —
1174
+ // nativeValueForGas is driven by pool-quote on the single-hop path
1175
+ // and by the cascade-level per-outbound allocation above.
1176
+ const outboundMulticalls = (0, payload_builders_1.buildOutboundApprovalAndCall)({
1177
+ prc20Token: segment.prc20Token || selectors_1.ZERO_ADDRESS,
1178
+ gasToken: segment.gasToken || selectors_1.ZERO_ADDRESS,
1179
+ burnAmount: segment.totalBurnAmount || BigInt(0),
1180
+ gasFee: segGasFee,
1181
+ nativeValueForGas,
1182
+ gatewayPcAddress,
1183
+ outboundRequest: outboundReq,
1184
+ });
1185
+ requiredNativeValue += sumMulticallNativeValue(outboundMulticalls);
1186
+ // Prepend to accumulated
1187
+ accumulatedPushMulticalls = [
1188
+ ...outboundMulticalls,
1189
+ ...accumulatedPushMulticalls,
1190
+ ];
1191
+ break;
1192
+ }
1193
+ case 'INBOUND_FROM_CEA': {
1194
+ // The accumulated multicalls = what runs on Push Chain AFTER inbound arrives.
1195
+ // Wrap in UniversalPayload struct with correct UEA nonce for the relay.
1196
+ if (isNativeSeedOnlySegment(segment)) {
994
1197
  const seedAmount = sumNativeSeedAmount(segment.hops);
995
1198
  if (seedAmount > requiredNativeValue) {
996
1199
  requiredNativeValue = seedAmount;
997
1200
  }
998
- requiredNativeValue += sumMulticallNativeValue(segment.mergedPushMulticalls);
999
- accumulatedPushMulticalls = [
1000
- ...(segment.mergedPushMulticalls || []),
1001
- ...accumulatedPushMulticalls,
1002
- ];
1201
+ (0, context_1.printLog)(ctx, `composeCascade — value-only CEA_TO_PUSH seed ${seedAmount.toString()} folded into root Push execution; no source-chain outbound needed`);
1003
1202
  break;
1004
1203
  }
1005
- case 'OUTBOUND_TO_CEA': {
1006
- const firstHop = segment.hops[0];
1007
- const isSvmSegment = (firstHop === null || firstHop === void 0 ? void 0 : firstHop.isSvmTarget) === true;
1008
- let outboundPayload;
1009
- let targetForOutbound;
1010
- const isMigration = (firstHop === null || firstHop === void 0 ? void 0 : firstHop.isMigration) === true;
1011
- if (isSvmSegment) {
1012
- // SVM: use the pre-built SVM payload from the hop descriptor
1013
- outboundPayload = (_d = firstHop.svmPayload) !== null && _d !== void 0 ? _d : '0x';
1014
- // For SVM, to.address IS the target (program for execute, recipient for withdraw)
1015
- const svmTarget = firstHop.params.to;
1016
- targetForOutbound = svmTarget.address;
1017
- }
1018
- else if (isMigration) {
1019
- // Migration: use raw 4-byte MIGRATION_SELECTOR, no multicall wrapping
1020
- outboundPayload = (0, payload_builders_1.buildMigrationPayload)();
1021
- targetForOutbound = (firstHop === null || firstHop === void 0 ? void 0 : firstHop.ceaAddress) || ueaAddress;
1022
- }
1023
- else {
1024
- // EVM: build CEA payload from merged multicalls
1025
- outboundPayload = (0, payload_builders_1.buildCeaMulticallPayload)(segment.mergedCeaMulticalls || []);
1026
- // Get CEA address from the first hop
1027
- targetForOutbound =
1028
- outboundPayload === '0x'
1029
- ? selectors_1.ZERO_ADDRESS
1030
- : (firstHop === null || firstHop === void 0 ? void 0 : firstHop.ceaAddress) || ueaAddress;
1031
- (0, payload_builders_1.assertCeaFundsParkingInvariant)(targetForOutbound, outboundPayload);
1032
- }
1033
- // Per-segment PRC-20 burn-balance pre-check. Fires whenever
1034
- // burnAmount > 0 even when burn token equals gas token — the gateway's
1035
- // transferFrom happens BEFORE the gas swap, so user must pre-hold the
1036
- // burn amount.
1037
- const segBurnAmount = segment.totalBurnAmount || BigInt(0);
1038
- const segBurnToken = segment.prc20Token;
1039
- if (segBurnAmount > BigInt(0) && segBurnToken && !isMigration) {
1040
- const onHand = yield ctx.pushClient.readContract({
1041
- address: segBurnToken,
1042
- abi: erc20_evm_1.ERC20_EVM,
1043
- functionName: 'balanceOf',
1044
- args: [ueaAddress],
1045
- });
1046
- const sufficient = onHand >= segBurnAmount;
1047
- (0, context_2.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_003_03, segBurnAmount, onHand, sufficient, ueaAddress, 'CASCADE', {
1048
- kind: 'PRC20',
1049
- burnToken: segBurnToken,
1050
- segmentIndex: segIdxLoop,
1051
- enforceGasCheck,
1052
- shortfall: sufficient ? BigInt(0) : segBurnAmount - onHand,
1204
+ // Build push multicalls from EVERY merged hop's own data in original
1205
+ // order (e.g. counter.increment() + value transfer). Multi-hop R3
1206
+ // segments (same sourceChain) bundle all target ops into the single
1207
+ // UEA multicall so no nested outbound is needed.
1208
+ const mergedHopMulticalls = [];
1209
+ for (const h of segment.hops) {
1210
+ const pc20Forward = (0, gate_1.isPC20ImportWithFunds)(h.params);
1211
+ if ((0, data_utils_1.hasExecutablePayloadData)(h?.params?.data) ||
1212
+ h?.params?.value ||
1213
+ pc20Forward) {
1214
+ const hopCalls = (0, payload_builders_1.buildExecuteMulticall)({
1215
+ execute: (pc20Forward
1216
+ ? (0, gate_1.assertLegacyFunds)((0, helpers_1.toExecuteParams)(h.params))
1217
+ : {
1218
+ to: h.params.to,
1219
+ value: h.params.value,
1220
+ data: h.params.data,
1221
+ }),
1222
+ ueaAddress,
1223
+ allowSelfValueCall: true,
1053
1224
  });
1054
- const priorPushExecutionMayFundBurn = segments
1055
- .slice(0, segIdxLoop)
1056
- .some((s) => s.type === 'PUSH_EXECUTION');
1057
- const priorInboundFundingForBurn = getInboundFundingForBurnToken(segments, segIdxLoop, segBurnToken, ctx.pushNetwork);
1058
- const priorInboundMayFundBurn = onHand + priorInboundFundingForBurn >= segBurnAmount;
1059
- if (!sufficient &&
1060
- !priorPushExecutionMayFundBurn &&
1061
- !priorInboundMayFundBurn) {
1062
- const projectedOnHand = onHand + priorInboundFundingForBurn;
1063
- const shortfall = segBurnAmount - projectedOnHand;
1064
- if (!enforceGasCheck) {
1065
- (0, context_1.printLog)(ctx, `composeCascade — PRC-20 shortfall ${shortfall.toString()} for segment ${segIdxLoop}; proceeding because enforceGasCheck=false`);
1066
- }
1067
- else {
1068
- (0, context_2.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_003_04, segBurnAmount, projectedOnHand, shortfall, ueaAddress, 'CASCADE', {
1069
- kind: 'PRC20',
1070
- burnToken: segBurnToken,
1071
- segmentIndex: segIdxLoop,
1072
- });
1073
- throw new errors_1.InsufficientUEABalanceError({
1074
- required: segBurnAmount,
1075
- available: projectedOnHand,
1076
- shortfall,
1077
- ueaAddress,
1078
- pathTag: 'CASCADE',
1079
- reason: 'PRC20',
1080
- burnToken: segBurnToken,
1081
- segmentIndex: segIdxLoop,
1082
- });
1083
- }
1084
- }
1085
- else if (!sufficient && priorPushExecutionMayFundBurn) {
1086
- (0, context_1.printLog)(ctx, `composeCascade — skipping PRC-20 preflight failure for segment ${segIdxLoop}; earlier Push execution may fund ${segBurnToken} before this outbound runs`);
1087
- }
1088
- else if (!sufficient && priorInboundMayFundBurn) {
1089
- (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`);
1090
- }
1225
+ mergedHopMulticalls.push(...hopCalls);
1091
1226
  }
1092
- // Build outbound request
1093
- 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), gasPriceForOutboundRequest(firstHop === null || firstHop === void 0 ? void 0 : firstHop.targetChain));
1094
- // Build approval + outbound multicalls
1095
- const segGasFee = segment.gasFee || BigInt(0);
1096
- const svmOverride = svmNativeValueBySegment.get(segment);
1097
- // When UEA balance < CASCADE_GAS_RESERVE, perOutboundNativeValue is
1098
- // undefined. Mirror the drained-UEA branch in executeUoaToCea
1099
- // (route-handlers.ts): send the entire available balance divided
1100
- // across outbound segments. The contract refunds excess via
1101
- // swapAndBurnGas, so overshooting is safe; undershooting causes STF.
1102
- let evmFallbackValue;
1103
- if (effectiveUeaBalance > BigInt(0)) {
1104
- evmFallbackValue =
1105
- effectiveUeaBalance / BigInt(Math.max(numOutbounds, 1));
1106
- }
1107
- else {
1108
- evmFallbackValue = segGasFee * BigInt(1000000);
1109
- }
1110
- const nativeValueForGas = (_h = (_g = svmOverride !== null && svmOverride !== void 0 ? svmOverride : evmNativeValueBySegment.get(segment)) !== null && _g !== void 0 ? _g : perOutboundNativeValue) !== null && _h !== void 0 ? _h : evmFallbackValue;
1111
- // SDK 5.2 Case C: when the segment's sizing is category C, compose
1112
- // R2/OUTBOUND_TO_CEA segments no longer apply Case A/B/C sizing
1113
- // (scoped to R1 + R3 only). segment.sizing may still be populated
1114
- // from the hop descriptor merge, but it is not consulted here —
1115
- // nativeValueForGas is driven by pool-quote on the single-hop path
1116
- // and by the cascade-level per-outbound allocation above.
1117
- const outboundMulticalls = (0, payload_builders_1.buildOutboundApprovalAndCall)({
1118
- prc20Token: segment.prc20Token || selectors_1.ZERO_ADDRESS,
1119
- gasToken: segment.gasToken || selectors_1.ZERO_ADDRESS,
1120
- burnAmount: segment.totalBurnAmount || BigInt(0),
1121
- gasFee: segGasFee,
1122
- nativeValueForGas,
1123
- gatewayPcAddress,
1124
- outboundRequest: outboundReq,
1125
- });
1126
- requiredNativeValue += sumMulticallNativeValue(outboundMulticalls);
1127
- // Prepend to accumulated
1227
+ }
1228
+ if (mergedHopMulticalls.length > 0) {
1229
+ requiredNativeValue += sumMulticallNativeValue(mergedHopMulticalls);
1128
1230
  accumulatedPushMulticalls = [
1129
- ...outboundMulticalls,
1231
+ ...mergedHopMulticalls,
1130
1232
  ...accumulatedPushMulticalls,
1131
1233
  ];
1132
- break;
1133
1234
  }
1134
- case 'INBOUND_FROM_CEA': {
1135
- // The accumulated multicalls = what runs on Push Chain AFTER inbound arrives.
1136
- // Wrap in UniversalPayload struct with correct UEA nonce for the relay.
1137
- if (isNativeSeedOnlySegment(segment)) {
1138
- const seedAmount = sumNativeSeedAmount(segment.hops);
1139
- if (seedAmount > requiredNativeValue) {
1140
- requiredNativeValue = seedAmount;
1141
- }
1142
- (0, context_1.printLog)(ctx, `composeCascade — value-only CEA_TO_PUSH seed ${seedAmount.toString()} folded into root Push execution; no source-chain outbound needed`);
1143
- break;
1235
+ // Primary hop carries the sourceChain / CEA address (shared across merged hops).
1236
+ const hop = segment.hops[0];
1237
+ const sourceChain = hop.sourceChain;
1238
+ const ceaAddress = hop.ceaAddress || ueaAddress;
1239
+ let intermediatePayload = '0x';
1240
+ if (accumulatedPushMulticalls.length > 0) {
1241
+ const multicallPayload = (0, payload_builder_1.buildMulticallPayloadData)(ctx, ueaAddress, accumulatedPushMulticalls);
1242
+ // +1: the outbound tx consumes one nonce via execute()
1243
+ const nonce = (ueaNonce ?? BigInt(0)) + BigInt(1);
1244
+ intermediatePayload = (0, payload_builders_1.isSvmChain)(sourceChain)
1245
+ ? (0, payload_builders_1.buildInboundUniversalPayloadSvm)(multicallPayload, { nonce })
1246
+ : (0, payload_builders_1.buildInboundUniversalPayload)(multicallPayload, { nonce });
1247
+ }
1248
+ for (const segmentHop of segment.hops) {
1249
+ (0, gate_1.assertPC20ImportHasPayload)(segmentHop.params, intermediatePayload !== '0x', `Cascaded Route 3 ${sourceChain}`);
1250
+ }
1251
+ // SVM chains: build SVM CPI payload instead of EVM CEA multicall
1252
+ if ((0, payload_builders_1.isSvmChain)(sourceChain)) {
1253
+ const lockerContract = chain_1.CHAIN_INFO[sourceChain].lockerContract;
1254
+ if (!lockerContract) {
1255
+ throw new Error(`No SVM gateway program configured for chain ${sourceChain}`);
1144
1256
  }
1145
- // Build push multicalls from EVERY merged hop's own data in original
1146
- // order (e.g. counter.increment() + value transfer). Multi-hop R3
1147
- // segments (same sourceChain) bundle all target ops into the single
1148
- // UEA multicall so no nested outbound is needed.
1149
- const mergedHopMulticalls = [];
1150
- for (const h of segment.hops) {
1151
- if ((0, data_utils_1.hasExecutablePayloadData)((_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)) {
1152
- const hopCalls = (0, payload_builders_1.buildExecuteMulticall)({
1153
- execute: {
1154
- to: h.params.to,
1155
- value: h.params.value,
1156
- data: h.params.data,
1157
- },
1158
- ueaAddress,
1159
- allowSelfValueCall: true,
1160
- });
1161
- mergedHopMulticalls.push(...hopCalls);
1257
+ const programPk = new web3_js_1.PublicKey(lockerContract);
1258
+ const gatewayProgramHex = ('0x' +
1259
+ Buffer.from(programPk.toBytes()).toString('hex'));
1260
+ let drainAmount = BigInt(0);
1261
+ let tokenMintHex;
1262
+ const params = hop.params;
1263
+ if (params.funds?.amount && params.funds.amount > BigInt(0)) {
1264
+ drainAmount = params.funds.amount;
1265
+ const token = params.funds.token;
1266
+ if (token && token.address) {
1267
+ const mintPk = new web3_js_1.PublicKey(token.address);
1268
+ tokenMintHex = ('0x' +
1269
+ Buffer.from(mintPk.toBytes()).toString('hex'));
1162
1270
  }
1163
1271
  }
1164
- if (mergedHopMulticalls.length > 0) {
1165
- requiredNativeValue += sumMulticallNativeValue(mergedHopMulticalls);
1166
- accumulatedPushMulticalls = [
1167
- ...mergedHopMulticalls,
1168
- ...accumulatedPushMulticalls,
1169
- ];
1272
+ else if (params.value && params.value > BigInt(0)) {
1273
+ drainAmount = params.value;
1170
1274
  }
1171
- // Primary hop carries the sourceChain / CEA address (shared across merged hops).
1172
- const hop = segment.hops[0];
1173
- const sourceChain = hop.sourceChain;
1174
- const ceaAddress = hop.ceaAddress || ueaAddress;
1175
- let intermediatePayload = '0x';
1176
- if (accumulatedPushMulticalls.length > 0) {
1177
- const multicallPayload = (0, payload_builder_1.buildMulticallPayloadData)(ctx, ueaAddress, accumulatedPushMulticalls);
1178
- // +1: the outbound tx consumes one nonce via execute()
1179
- const nonce = (ueaNonce !== null && ueaNonce !== void 0 ? ueaNonce : BigInt(0)) + BigInt(1);
1180
- intermediatePayload = (0, payload_builders_1.isSvmChain)(sourceChain)
1181
- ? (0, payload_builders_1.buildInboundUniversalPayloadSvm)(multicallPayload, { nonce })
1182
- : (0, payload_builders_1.buildInboundUniversalPayload)(multicallPayload, { nonce });
1275
+ // Derive CEA PDA as revert recipient
1276
+ const ueaBytes = Buffer.from(ueaAddress.slice(2), 'hex');
1277
+ const [ceaPda] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from('push_identity'), ueaBytes], programPk);
1278
+ const ceaPdaHex = ('0x' +
1279
+ Buffer.from(ceaPda.toBytes()).toString('hex'));
1280
+ // Build SVM payload with intermediate Push Chain payload embedded.
1281
+ // PC20 burns use the gateway's dedicated send_universal_tx shape;
1282
+ // SPL/native drains keep send_universal_tx_to_uea.
1283
+ const extraPayload = intermediatePayload !== '0x'
1284
+ ? new Uint8Array(Buffer.from(intermediatePayload.slice(2), 'hex'))
1285
+ : undefined;
1286
+ const pc20 = params._pc20;
1287
+ let svmPayload;
1288
+ if ((0, gate_1.isPC20ImportWithFunds)(params) && pc20) {
1289
+ const mintPk = new web3_js_1.PublicKey(pc20.originAddress);
1290
+ const fields = (0, svm_1.buildPC20SvmCeaBurnPayload)({
1291
+ gatewayProgram: programPk,
1292
+ mint: mintPk,
1293
+ ceaAuthority: ceaPda,
1294
+ ceaAta: (0, svm_rent_1.deriveAtaPubkey)(ceaPda, mintPk),
1295
+ ueaAddress,
1296
+ amount: drainAmount,
1297
+ pushPayload: extraPayload ?? new Uint8Array(0),
1298
+ });
1299
+ svmPayload = (0, payload_builders_1.encodeSvmExecutePayload)(fields);
1183
1300
  }
1184
- // SVM chains: build SVM CPI payload instead of EVM CEA multicall
1185
- if ((0, payload_builders_1.isSvmChain)(sourceChain)) {
1186
- const lockerContract = chain_1.CHAIN_INFO[sourceChain].lockerContract;
1187
- if (!lockerContract) {
1188
- throw new Error(`No SVM gateway program configured for chain ${sourceChain}`);
1189
- }
1190
- const programPk = new web3_js_1.PublicKey(lockerContract);
1191
- const gatewayProgramHex = ('0x' +
1192
- Buffer.from(programPk.toBytes()).toString('hex'));
1193
- let drainAmount = BigInt(0);
1194
- let tokenMintHex;
1195
- const params = hop.params;
1196
- if (((_l = params.funds) === null || _l === void 0 ? void 0 : _l.amount) && params.funds.amount > BigInt(0)) {
1197
- drainAmount = params.funds.amount;
1198
- const token = params.funds.token;
1199
- if (token && token.address) {
1200
- const mintPk = new web3_js_1.PublicKey(token.address);
1201
- tokenMintHex = ('0x' +
1202
- Buffer.from(mintPk.toBytes()).toString('hex'));
1203
- }
1204
- }
1205
- else if (params.value && params.value > BigInt(0)) {
1206
- drainAmount = params.value;
1207
- }
1208
- // Derive CEA PDA as revert recipient
1209
- const ueaBytes = Buffer.from(ueaAddress.slice(2), 'hex');
1210
- const [ceaPda] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from('push_identity'), ueaBytes], programPk);
1211
- const ceaPdaHex = ('0x' +
1212
- Buffer.from(ceaPda.toBytes()).toString('hex'));
1213
- // Build SVM payload with intermediate Push Chain payload embedded
1214
- const svmPayload = (0, payload_builders_1.encodeSvmCeaToUeaPayload)({
1301
+ else {
1302
+ svmPayload = (0, payload_builders_1.encodeSvmCeaToUeaPayload)({
1215
1303
  gatewayProgramHex,
1216
1304
  drainAmount,
1217
1305
  tokenMintHex,
1218
1306
  revertRecipientHex: ceaPdaHex,
1219
- extraPayload: intermediatePayload !== '0x'
1220
- ? new Uint8Array(Buffer.from(intermediatePayload.slice(2), 'hex'))
1221
- : undefined,
1307
+ extraPayload,
1222
1308
  });
1223
- (0, payload_builders_1.assertSvmPayloadWithinRelayLimit)(svmPayload, `Route 3 SVM cascade ${sourceChain}`);
1224
- const svmBurnAmount = segment.totalBurnAmount || BigInt(0);
1225
- const outboundReq = (0, payload_builders_1.buildOutboundRequest)(gatewayProgramHex, segment.prc20Token ||
1226
- (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), gasPriceForOutboundRequest(sourceChain));
1227
- const inboundGasFee = segment.gasFee || BigInt(0);
1228
- let svmInboundFallback;
1229
- if (effectiveUeaBalance > BigInt(0)) {
1230
- svmInboundFallback =
1231
- effectiveUeaBalance / BigInt(Math.max(numOutbounds, 1));
1232
- }
1233
- else {
1234
- svmInboundFallback = inboundGasFee * BigInt(1000000);
1235
- }
1236
- let svmInboundNativeValue = (_q = (_p = svmNativeValueBySegment.get(segment)) !== null && _p !== void 0 ? _p : perOutboundNativeValue) !== null && _q !== void 0 ? _q : svmInboundFallback;
1237
- // SDK 5.2 R3-style Case C bump: when the inbound segment's sizing
1238
- // is C, top up the swap budget by the overflow. No bridge-swap —
1239
- // R3 has no destination funds delivery.
1240
- if (((_r = segment.sizing) === null || _r === void 0 ? void 0 : _r.category) === 'C' &&
1241
- segment.sizing.overflowNativePc > BigInt(0)) {
1242
- const bumped = svmInboundNativeValue + segment.sizing.overflowNativePc;
1243
- (0, context_1.printLog)(ctx, `composeCascade — INBOUND_FROM_CEA (SVM) Case C: bumping nativeValueForGas from ${svmInboundNativeValue} to ${bumped} (overflow=${segment.sizing.overflowNativePc})`);
1244
- svmInboundNativeValue = bumped;
1245
- }
1246
- const outboundMulticalls = (0, payload_builders_1.buildOutboundApprovalAndCall)({
1247
- prc20Token: segment.prc20Token ||
1248
- (0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork),
1249
- gasToken: segment.gasToken || selectors_1.ZERO_ADDRESS,
1250
- burnAmount: svmBurnAmount,
1251
- gasFee: inboundGasFee,
1252
- nativeValueForGas: svmInboundNativeValue,
1253
- gatewayPcAddress,
1254
- outboundRequest: outboundReq,
1255
- });
1256
- requiredNativeValue += sumMulticallNativeValue(outboundMulticalls);
1257
- accumulatedPushMulticalls = [...outboundMulticalls];
1258
- break;
1259
- }
1260
- // EVM path: Build CEA multicall: sendUniversalTxToUEA(payload).
1261
- // CEA handles ERC20 gateway approval internally.
1262
- const ceaMulticalls = [];
1263
- // Add primary hop's own CEA operations if any
1264
- // (e.g., approve + swap before bridging back)
1265
- if (hop.ceaMulticalls && hop.ceaMulticalls.length > 0) {
1266
- ceaMulticalls.push(...hop.ceaMulticalls);
1267
- }
1268
- // Aggregate token/amount across all merged hops so the single
1269
- // sendUniversalTxToUEA call carries the combined forward amount.
1270
- // For multi-hop R3 segments (same sourceChain), ERC20 funds across
1271
- // hops must resolve to the same token — otherwise this path can't
1272
- // express it in one call and the caller should split manually.
1273
- let tokenAddress = selectors_1.ZERO_ADDRESS;
1274
- let amount = BigInt(0);
1275
- for (const mergedHop of segment.hops) {
1276
- const hParams = mergedHop.params;
1277
- if ((_s = hParams.funds) === null || _s === void 0 ? void 0 : _s.amount) {
1278
- const token = hParams.funds.token;
1279
- if (token) {
1280
- if (token.mechanism === 'native') {
1281
- amount += hParams.funds.amount;
1282
- }
1283
- else {
1284
- const addr = token.address;
1285
- if (tokenAddress !== selectors_1.ZERO_ADDRESS &&
1286
- tokenAddress.toLowerCase() !== addr.toLowerCase()) {
1287
- throw new Error(`composeCascade: merged R3 hops target different ERC20 tokens (${tokenAddress} vs ${addr}); split into separate executeTransactions batches`);
1288
- }
1289
- tokenAddress = addr;
1290
- amount += hParams.funds.amount;
1291
- }
1292
- }
1293
- }
1294
- else if (hParams.value && hParams.value > BigInt(0)) {
1295
- amount += hParams.value;
1296
- }
1297
1309
  }
1298
- // Build sendUniversalTxToUEA self-call on CEA. CEA self-calls must
1299
- // always use value=0; native funds are spent from the CEA balance.
1300
- const sendCall = (0, payload_builders_1.buildSendUniversalTxToUEA)(ceaAddress, tokenAddress, amount, intermediatePayload, ceaAddress);
1301
- ceaMulticalls.push(sendCall);
1302
- // Wrap CEA multicall in outbound from Push Chain
1303
- const ceaPayload = (0, payload_builders_1.buildCeaMulticallPayload)(ceaMulticalls);
1304
- // The nested outbound back to the source chain needs a non-zero
1305
- // gasLimit — UGPC.sendUniversalTxOutbound reverts with
1306
- // "Missing or invalid parameters" on 0. When the hop didn't supply
1307
- // one (typical for a pure-R3 leg composed inside a cascade), fall
1308
- // back to the EVM Route 3 default.
1309
- const effectiveGasLimit = segment.gasLimit && segment.gasLimit > BigInt(0)
1310
- ? segment.gasLimit
1311
- : selectors_1.DEFAULT_CEA_TO_PUSH_GAS_LIMIT;
1312
- const outboundReq = (0, payload_builders_1.buildOutboundRequest)(ceaAddress, segment.prc20Token ||
1313
- (0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork), segment.totalBurnAmount || BigInt(0), effectiveGasLimit, ceaPayload, ueaAddress, (_t = segment.maxPCForGas) !== null && _t !== void 0 ? _t : BigInt(0), gasPriceForOutboundRequest(sourceChain));
1310
+ (0, payload_builders_1.assertSvmPayloadWithinRelayLimit)(svmPayload, `Route 3 SVM cascade ${sourceChain}`);
1311
+ const svmBurnAmount = segment.totalBurnAmount || BigInt(0);
1312
+ const outboundReq = (0, payload_builders_1.buildOutboundRequest)(gatewayProgramHex, segment.prc20Token ||
1313
+ (0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork), svmBurnAmount, segment.gasLimit ?? BigInt(0), svmPayload, ueaAddress, segment.maxPCForGas ?? BigInt(0), gasPriceForOutboundRequest(sourceChain));
1314
1314
  const inboundGasFee = segment.gasFee || BigInt(0);
1315
- let evmInboundFallback;
1315
+ let svmInboundFallback;
1316
1316
  if (effectiveUeaBalance > BigInt(0)) {
1317
- evmInboundFallback =
1317
+ svmInboundFallback =
1318
1318
  effectiveUeaBalance / BigInt(Math.max(numOutbounds, 1));
1319
1319
  }
1320
1320
  else {
1321
- evmInboundFallback = inboundGasFee * BigInt(1000000);
1321
+ svmInboundFallback = inboundGasFee * BigInt(1000000);
1322
1322
  }
1323
- let evmInboundNativeValue = perOutboundNativeValue !== null && perOutboundNativeValue !== void 0 ? perOutboundNativeValue : evmInboundFallback;
1323
+ let svmInboundNativeValue = svmNativeValueBySegment.get(segment) ??
1324
+ perOutboundNativeValue ??
1325
+ svmInboundFallback;
1324
1326
  // SDK 5.2 R3-style Case C bump: when the inbound segment's sizing
1325
1327
  // is C, top up the swap budget by the overflow. No bridge-swap —
1326
1328
  // R3 has no destination funds delivery.
1327
- if (((_u = segment.sizing) === null || _u === void 0 ? void 0 : _u.category) === 'C' &&
1329
+ if (segment.sizing?.category === 'C' &&
1328
1330
  segment.sizing.overflowNativePc > BigInt(0)) {
1329
- const bumped = evmInboundNativeValue + segment.sizing.overflowNativePc;
1330
- (0, context_1.printLog)(ctx, `composeCascade — INBOUND_FROM_CEA (EVM) Case C: bumping nativeValueForGas from ${evmInboundNativeValue} to ${bumped} (overflow=${segment.sizing.overflowNativePc})`);
1331
- evmInboundNativeValue = bumped;
1331
+ const bumped = svmInboundNativeValue + segment.sizing.overflowNativePc;
1332
+ (0, context_1.printLog)(ctx, `composeCascade — INBOUND_FROM_CEA (SVM) Case C: bumping nativeValueForGas from ${svmInboundNativeValue} to ${bumped} (overflow=${segment.sizing.overflowNativePc})`);
1333
+ svmInboundNativeValue = bumped;
1332
1334
  }
1333
1335
  const outboundMulticalls = (0, payload_builders_1.buildOutboundApprovalAndCall)({
1334
1336
  prc20Token: segment.prc20Token ||
1335
1337
  (0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork),
1336
1338
  gasToken: segment.gasToken || selectors_1.ZERO_ADDRESS,
1337
- burnAmount: segment.totalBurnAmount || BigInt(0),
1339
+ burnAmount: svmBurnAmount,
1338
1340
  gasFee: inboundGasFee,
1339
- nativeValueForGas: evmInboundNativeValue,
1341
+ nativeValueForGas: svmInboundNativeValue,
1340
1342
  gatewayPcAddress,
1341
1343
  outboundRequest: outboundReq,
1342
1344
  });
1343
1345
  requiredNativeValue += sumMulticallNativeValue(outboundMulticalls);
1344
- // Reset accumulated -- everything is now inside this outbound
1345
1346
  accumulatedPushMulticalls = [...outboundMulticalls];
1346
1347
  break;
1347
1348
  }
1349
+ // EVM path: Build CEA multicall: sendUniversalTxToUEA(payload).
1350
+ // CEA handles ERC20 gateway approval internally.
1351
+ const ceaMulticalls = [];
1352
+ // Add primary hop's own CEA operations if any
1353
+ // (e.g., approve + swap before bridging back)
1354
+ if (hop.ceaMulticalls && hop.ceaMulticalls.length > 0) {
1355
+ ceaMulticalls.push(...hop.ceaMulticalls);
1356
+ }
1357
+ // Aggregate token/amount across all merged hops so the single
1358
+ // sendUniversalTxToUEA call carries the combined forward amount.
1359
+ // For multi-hop R3 segments (same sourceChain), ERC20 funds across
1360
+ // hops must resolve to the same token — otherwise this path can't
1361
+ // express it in one call and the caller should split manually.
1362
+ let tokenAddress = selectors_1.ZERO_ADDRESS;
1363
+ let amount = BigInt(0);
1364
+ for (const mergedHop of segment.hops) {
1365
+ const hParams = mergedHop.params;
1366
+ if (hParams.funds?.amount) {
1367
+ const token = hParams.funds.token;
1368
+ if (token) {
1369
+ if (token.mechanism === 'native') {
1370
+ amount += hParams.funds.amount;
1371
+ }
1372
+ else {
1373
+ const addr = token.address;
1374
+ if (tokenAddress !== selectors_1.ZERO_ADDRESS &&
1375
+ tokenAddress.toLowerCase() !== addr.toLowerCase()) {
1376
+ throw new Error(`composeCascade: merged R3 hops target different ERC20 tokens (${tokenAddress} vs ${addr}); split into separate executeTransactions batches`);
1377
+ }
1378
+ tokenAddress = addr;
1379
+ amount += hParams.funds.amount;
1380
+ }
1381
+ }
1382
+ }
1383
+ else if (hParams.value && hParams.value > BigInt(0)) {
1384
+ amount += hParams.value;
1385
+ }
1386
+ }
1387
+ // Build sendUniversalTxToUEA self-call on CEA. CEA self-calls must
1388
+ // always use value=0; native funds are spent from the CEA balance.
1389
+ const sendCall = (0, payload_builders_1.buildSendUniversalTxToUEA)(ceaAddress, tokenAddress, amount, intermediatePayload, ceaAddress);
1390
+ ceaMulticalls.push(sendCall);
1391
+ // Wrap CEA multicall in outbound from Push Chain
1392
+ const ceaPayload = (0, payload_builders_1.buildCeaMulticallPayload)(ceaMulticalls);
1393
+ // The nested outbound back to the source chain needs a non-zero
1394
+ // gasLimit — UGPC.sendUniversalTxOutbound reverts with
1395
+ // "Missing or invalid parameters" on 0. When the hop didn't supply
1396
+ // one (typical for a pure-R3 leg composed inside a cascade), fall
1397
+ // back to the EVM Route 3 default.
1398
+ const effectiveGasLimit = segment.gasLimit && segment.gasLimit > BigInt(0)
1399
+ ? segment.gasLimit
1400
+ : selectors_1.DEFAULT_CEA_TO_PUSH_GAS_LIMIT;
1401
+ const outboundReq = (0, payload_builders_1.buildOutboundRequest)(ceaAddress, segment.prc20Token ||
1402
+ (0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork), segment.totalBurnAmount || BigInt(0), effectiveGasLimit, ceaPayload, ueaAddress, segment.maxPCForGas ?? BigInt(0), gasPriceForOutboundRequest(sourceChain));
1403
+ const inboundGasFee = segment.gasFee || BigInt(0);
1404
+ let evmInboundFallback;
1405
+ if (effectiveUeaBalance > BigInt(0)) {
1406
+ evmInboundFallback =
1407
+ effectiveUeaBalance / BigInt(Math.max(numOutbounds, 1));
1408
+ }
1409
+ else {
1410
+ evmInboundFallback = inboundGasFee * BigInt(1000000);
1411
+ }
1412
+ let evmInboundNativeValue = evmNativeValueBySegment.get(segment) ??
1413
+ perOutboundNativeValue ??
1414
+ evmInboundFallback;
1415
+ // SDK 5.2 R3-style Case C bump: when the inbound segment's sizing
1416
+ // is C, top up the swap budget by the overflow. No bridge-swap —
1417
+ // R3 has no destination funds delivery.
1418
+ if (segment.sizing?.category === 'C' &&
1419
+ segment.sizing.overflowNativePc > BigInt(0)) {
1420
+ const bumped = evmInboundNativeValue + segment.sizing.overflowNativePc;
1421
+ (0, context_1.printLog)(ctx, `composeCascade — INBOUND_FROM_CEA (EVM) Case C: bumping nativeValueForGas from ${evmInboundNativeValue} to ${bumped} (overflow=${segment.sizing.overflowNativePc})`);
1422
+ evmInboundNativeValue = bumped;
1423
+ }
1424
+ const outboundMulticalls = (0, payload_builders_1.buildOutboundApprovalAndCall)({
1425
+ prc20Token: segment.prc20Token ||
1426
+ (0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork),
1427
+ gasToken: segment.gasToken || selectors_1.ZERO_ADDRESS,
1428
+ burnAmount: segment.totalBurnAmount || BigInt(0),
1429
+ gasFee: inboundGasFee,
1430
+ nativeValueForGas: evmInboundNativeValue,
1431
+ gatewayPcAddress,
1432
+ outboundRequest: outboundReq,
1433
+ });
1434
+ requiredNativeValue += sumMulticallNativeValue(outboundMulticalls);
1435
+ // Reset accumulated -- everything is now inside this outbound
1436
+ accumulatedPushMulticalls = [...outboundMulticalls];
1437
+ break;
1348
1438
  }
1349
1439
  }
1350
- return {
1351
- multicalls: accumulatedPushMulticalls,
1352
- requiredNativeValue,
1353
- };
1354
- });
1440
+ }
1441
+ if (requiredNativeValue > BigInt(0)) {
1442
+ (0, preflight_1.runPreflight)({
1443
+ ctx,
1444
+ ueaAddress,
1445
+ ueaBalance: effectiveUeaBalance,
1446
+ requiredValue: requiredNativeValue,
1447
+ gasReserve: CASCADE_GAS_RESERVE,
1448
+ pathTag: 'CASCADE',
1449
+ enforceGasCheck,
1450
+ });
1451
+ }
1452
+ return {
1453
+ multicalls: accumulatedPushMulticalls,
1454
+ requiredNativeValue,
1455
+ };
1355
1456
  }
1356
- function composeCascade(ctx, segments, ueaAddress, ueaBalance, ueaNonce) {
1357
- return tslib_1.__awaiter(this, void 0, void 0, function* () {
1358
- const result = yield composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce);
1359
- return result.multicalls;
1360
- });
1457
+ async function composeCascade(ctx, segments, ueaAddress, ueaBalance, ueaNonce) {
1458
+ const result = await composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonce);
1459
+ return result.multicalls;
1361
1460
  }
1362
1461
  // ============================================================================
1363
1462
  // createCascadedBuilder
1364
1463
  // ============================================================================
1365
1464
  function txHashFromProgressEvent(event) {
1366
- var _a, _b, _c, _d, _e, _f;
1367
1465
  const response = event.response;
1368
- return ((_f = (_c = (_b = (_a = response === null || response === void 0 ? void 0 : response.txHash) !== null && _a !== void 0 ? _a : response === null || response === void 0 ? void 0 : response.pushTxHash) !== null && _b !== void 0 ? _b : response === null || response === void 0 ? void 0 : response.rootTxHash) !== null && _c !== void 0 ? _c : (_e = (_d = response === null || response === void 0 ? void 0 : response.response) === null || _d === void 0 ? void 0 : _d[0]) === null || _e === void 0 ? void 0 : _e.hash) !== null && _f !== void 0 ? _f : '');
1466
+ return (response?.txHash ??
1467
+ response?.pushTxHash ??
1468
+ response?.rootTxHash ??
1469
+ response?.response?.[0]?.hash ??
1470
+ '');
1369
1471
  }
1370
- function withMultihopRootProgressMapping(ctx, run) {
1371
- return tslib_1.__awaiter(this, void 0, void 0, function* () {
1372
- const originalHook = ctx.progressHook;
1373
- if (!originalHook)
1374
- return run();
1375
- ctx.progressHook = (event) => {
1376
- if (event.id === progress_hook_types_1.PROGRESS_HOOK.SEND_TX_199_01) {
1377
- originalHook(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_199_99](txHashFromProgressEvent(event)));
1378
- return;
1379
- }
1380
- originalHook(event);
1381
- };
1382
- try {
1383
- return yield run();
1472
+ async function withMultihopRootProgressMapping(ctx, run) {
1473
+ const originalHook = ctx.progressHook;
1474
+ if (!originalHook)
1475
+ return run();
1476
+ ctx.progressHook = (event) => {
1477
+ if (event.id === progress_hook_types_1.PROGRESS_HOOK.SEND_TX_199_01) {
1478
+ originalHook(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_199_99](txHashFromProgressEvent(event)));
1479
+ return;
1384
1480
  }
1385
- finally {
1386
- ctx.progressHook = originalHook;
1387
- }
1388
- });
1481
+ originalHook(event);
1482
+ };
1483
+ try {
1484
+ return await run();
1485
+ }
1486
+ finally {
1487
+ ctx.progressHook = originalHook;
1488
+ }
1389
1489
  }
1390
1490
  function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, options) {
1391
1491
  return {
1392
- send: () => tslib_1.__awaiter(this, void 0, void 0, function* () {
1393
- var _a;
1492
+ send: async () => {
1394
1493
  const ueaAddress = (0, uea_manager_1.computeUEAOffchain)(ctx);
1395
1494
  // Extract HopDescriptors
1396
1495
  const hops = preparedTxs.map((tx) => tx._hop);
1397
- const enforceGasCheck = (options === null || options === void 0 ? void 0 : options.enforceGasCheck) === true ||
1398
- hops.some((hop) => { var _a; return ((_a = hop.params.options) === null || _a === void 0 ? void 0 : _a.enforceGasCheck) === true; });
1496
+ // Prepared PC20 imports pin registry-derived identity into their
1497
+ // payload. Re-check that binding immediately before any route is
1498
+ // executed or composed; a stale descriptor must never reach broadcast.
1499
+ await Promise.all(hops.map((hop) => (0, gate_1.revalidatePreparedPC20)(ctx, hop.params)));
1500
+ const enforceGasCheck = options?.enforceGasCheck === true ||
1501
+ hops.some((hop) => hop.params.options?.enforceGasCheck === true);
1399
1502
  const withCascadeOptions = (params) => enforceGasCheck
1400
- ? Object.assign(Object.assign({}, params), { options: Object.assign(Object.assign({}, params.options), { enforceGasCheck: true }) }) : params;
1503
+ ? {
1504
+ ...params,
1505
+ options: {
1506
+ ...params.options,
1507
+ enforceGasCheck: true,
1508
+ },
1509
+ }
1510
+ : params;
1401
1511
  // Classify into segments
1402
1512
  const segments = classifyIntoSegments(hops);
1403
1513
  // Check if this is a single-hop Route 1 (no composition needed)
1404
1514
  if (preparedTxs.length === 1 && preparedTxs[0].route === 'UOA_TO_PUSH') {
1405
- const response = yield callbacks.executeFn(withCascadeOptions(hops[0].params));
1515
+ const response = await callbacks.executeFn(withCascadeOptions(hops[0].params));
1406
1516
  const singleRoute1Result = {
1407
1517
  initialTxHash: response.hash,
1408
1518
  initialTxResponse: response,
@@ -1417,29 +1527,27 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
1417
1527
  ],
1418
1528
  hopCount: 1,
1419
1529
  finalTxHash: response.hash,
1420
- waitForAll: () => tslib_1.__awaiter(this, void 0, void 0, function* () {
1421
- return ({
1422
- success: true,
1423
- hops: [
1424
- {
1425
- hopIndex: 0,
1426
- route: hops[0].route,
1427
- executionChain: (0, helpers_1.getPushChainForNetwork)(ctx.pushNetwork),
1428
- status: 'confirmed',
1429
- txHash: response.hash,
1430
- },
1431
- ],
1432
- finalTxHash: response.hash,
1433
- finalTxResponse: singleRoute1Result,
1434
- });
1530
+ waitForAll: async () => ({
1531
+ success: true,
1532
+ hops: [
1533
+ {
1534
+ hopIndex: 0,
1535
+ route: hops[0].route,
1536
+ executionChain: (0, helpers_1.getPushChainForNetwork)(ctx.pushNetwork),
1537
+ status: 'confirmed',
1538
+ txHash: response.hash,
1539
+ },
1540
+ ],
1541
+ finalTxHash: response.hash,
1542
+ finalTxResponse: singleRoute1Result,
1435
1543
  }),
1436
- wait: (opts) => tslib_1.__awaiter(this, void 0, void 0, function* () { return singleRoute1Result.waitForAll(opts); }),
1544
+ wait: async (opts) => singleRoute1Result.waitForAll(opts),
1437
1545
  };
1438
1546
  return singleRoute1Result;
1439
1547
  }
1440
1548
  // Check if single-hop Route 2 (just execute directly)
1441
1549
  if (preparedTxs.length === 1 && preparedTxs[0].route === 'UOA_TO_CEA') {
1442
- const response = yield callbacks.executeFn(withCascadeOptions(hops[0].params));
1550
+ const response = await callbacks.executeFn(withCascadeOptions(hops[0].params));
1443
1551
  const targetChain = hops[0].targetChain || (0, helpers_1.getPushChainForNetwork)(ctx.pushNetwork);
1444
1552
  const singleRoute2Result = {
1445
1553
  initialTxHash: response.hash,
@@ -1455,39 +1563,37 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
1455
1563
  ],
1456
1564
  hopCount: 1,
1457
1565
  finalTxHash: response.hash,
1458
- waitForAll: () => tslib_1.__awaiter(this, void 0, void 0, function* () {
1459
- return ({
1460
- success: true,
1461
- hops: [
1462
- {
1463
- hopIndex: 0,
1464
- route: hops[0].route,
1465
- executionChain: targetChain,
1466
- status: 'confirmed',
1467
- txHash: response.hash,
1468
- },
1469
- ],
1470
- finalTxHash: response.hash,
1471
- finalTxResponse: singleRoute2Result,
1472
- });
1566
+ waitForAll: async () => ({
1567
+ success: true,
1568
+ hops: [
1569
+ {
1570
+ hopIndex: 0,
1571
+ route: hops[0].route,
1572
+ executionChain: targetChain,
1573
+ status: 'confirmed',
1574
+ txHash: response.hash,
1575
+ },
1576
+ ],
1577
+ finalTxHash: response.hash,
1578
+ finalTxResponse: singleRoute2Result,
1473
1579
  }),
1474
- wait: (opts) => tslib_1.__awaiter(this, void 0, void 0, function* () { return singleRoute2Result.waitForAll(opts); }),
1580
+ wait: async (opts) => singleRoute2Result.waitForAll(opts),
1475
1581
  };
1476
1582
  return singleRoute2Result;
1477
1583
  }
1478
1584
  // Multi-hop: compose cascade bottom-to-top
1479
1585
  // Fetch UEA balance + nonce so composeCascade can allocate native value and build inbound payloads
1480
- const ueaBalance = yield ctx.pushClient.getBalance(ueaAddress);
1481
- const ueaCodeCascade = yield ctx.pushClient.publicClient.getCode({
1586
+ const ueaBalance = await ctx.pushClient.getBalance(ueaAddress);
1587
+ const ueaCodeCascade = await ctx.pushClient.publicClient.getCode({
1482
1588
  address: ueaAddress,
1483
1589
  });
1484
1590
  const ueaNonceCascade = ueaCodeCascade !== undefined
1485
- ? yield (0, uea_manager_1.getUEANonce)(ctx, ueaAddress)
1591
+ ? await (0, uea_manager_1.getUEANonce)(ctx, ueaAddress)
1486
1592
  : BigInt(0);
1487
1593
  let composedMulticalls;
1488
1594
  let cascadeRequiredNativeValue = BigInt(0);
1489
1595
  try {
1490
- const composed = yield composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonceCascade, { enforceGasCheck });
1596
+ const composed = await composeCascadeDetailed(ctx, segments, ueaAddress, ueaBalance, ueaNonceCascade, { enforceGasCheck });
1491
1597
  composedMulticalls = composed.multicalls;
1492
1598
  cascadeRequiredNativeValue = composed.requiredNativeValue;
1493
1599
  }
@@ -1498,7 +1604,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
1498
1604
  // terminal "cascade failed" frame instead of the stream stopping
1499
1605
  // silently after 203-04.
1500
1606
  if (err instanceof errors_1.InsufficientUEABalanceError) {
1501
- const failedHopIndex = (_a = err.segmentIndex) !== null && _a !== void 0 ? _a : 0;
1607
+ const failedHopIndex = err.segmentIndex ?? 0;
1502
1608
  (0, context_2.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_999_02, failedHopIndex + 1, preparedTxs.length, err.message);
1503
1609
  }
1504
1610
  throw err;
@@ -1513,7 +1619,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
1513
1619
  if (rootFundsDeposit) {
1514
1620
  executeParams.funds = rootFundsDeposit;
1515
1621
  }
1516
- const response = yield withMultihopRootProgressMapping(ctx, () => callbacks.executeFn(executeParams));
1622
+ const response = await withMultihopRootProgressMapping(ctx, () => callbacks.executeFn(executeParams));
1517
1623
  // Build hop info for tracking
1518
1624
  const hopInfos = hops.map((hop, index) => ({
1519
1625
  hopIndex: index,
@@ -1533,8 +1639,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
1533
1639
  initialTxResponse: response,
1534
1640
  hops: hopInfos,
1535
1641
  hopCount: hops.length,
1536
- waitForAll: (opts) => tslib_1.__awaiter(this, void 0, void 0, function* () {
1537
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
1642
+ waitForAll: async (opts) => {
1538
1643
  const { pollingIntervalMs = 10000, timeout = 300000, progressHook: cascadeProgressHook, eventHook = defaultEventHook, } = opts || {};
1539
1644
  const startTime = Date.now();
1540
1645
  // Per-hop progress emission. Fans out cascade markers to BOTH the
@@ -1555,11 +1660,10 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
1555
1660
  // but guard explicitly in case someone wires a 1-element prepared list.
1556
1661
  const totalHops = hopInfos.length;
1557
1662
  const isMulti = totalHops > 1;
1558
- const chainLabels = hops.map((h) => {
1559
- var _a, _b;
1560
- return String((_b = (_a = h.targetChain) !== null && _a !== void 0 ? _a : h.sourceChain) !== null && _b !== void 0 ? _b : (0, helpers_1.getPushChainForNetwork)(ctx.pushNetwork));
1561
- });
1562
- const signerOriginChain = String((_a = ctx.universalSigner.account.chain) !== null && _a !== void 0 ? _a : 'origin');
1663
+ const chainLabels = hops.map((h) => String(h.targetChain ??
1664
+ h.sourceChain ??
1665
+ (0, helpers_1.getPushChainForNetwork)(ctx.pushNetwork)));
1666
+ const signerOriginChain = String(ctx.universalSigner.account.chain ?? 'origin');
1563
1667
  const emitHopStart = (hopIndex0) => {
1564
1668
  if (!isMulti)
1565
1669
  return;
@@ -1576,12 +1680,10 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
1576
1680
  return; // skip after final hop — 999-01 takes over
1577
1681
  emitHopEvent(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_002_99_99](n, totalHops));
1578
1682
  };
1579
- const currentFinalTxHash = () => {
1580
- var _a, _b;
1581
- return (_b = (_a = [...hopInfos]
1582
- .reverse()
1583
- .find((hop) => hop.status === 'confirmed' && hop.txHash)) === null || _a === void 0 ? void 0 : _a.txHash) !== null && _b !== void 0 ? _b : response.hash;
1584
- };
1683
+ const currentFinalTxHash = () => [...hopInfos]
1684
+ .reverse()
1685
+ .find((hop) => hop.status === 'confirmed' && hop.txHash)
1686
+ ?.txHash ?? response.hash;
1585
1687
  const emitCascadeSuccess = () => {
1586
1688
  if (!isMulti)
1587
1689
  return;
@@ -1598,13 +1700,12 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
1598
1700
  emitHopEvent(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_999_03](failedIdx0 + 1, totalHops, txHash, pushTxHash));
1599
1701
  };
1600
1702
  const emitRouteIntermediateComplete = (hop, txHash) => {
1601
- var _a;
1602
1703
  if (!isMulti)
1603
1704
  return;
1604
1705
  const n = hop.hopIndex + 1;
1605
1706
  if (n >= totalHops)
1606
1707
  return; // final terminal is 999-01
1607
- const resolvedTxHash = (_a = txHash !== null && txHash !== void 0 ? txHash : hop.txHash) !== null && _a !== void 0 ? _a : response.hash;
1708
+ const resolvedTxHash = txHash ?? hop.txHash ?? response.hash;
1608
1709
  if (hop.route === 'UOA_TO_PUSH') {
1609
1710
  emitHopEvent(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_199_99](resolvedTxHash));
1610
1711
  return;
@@ -1620,7 +1721,13 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
1620
1721
  const buildCompletionResult = (success, failedAt) => {
1621
1722
  const finalTxHash = currentFinalTxHash();
1622
1723
  cascadeResponse.finalTxHash = finalTxHash;
1623
- return Object.assign({ success, hops: hopInfos, finalTxHash, finalTxResponse: cascadeResponse }, (failedAt !== undefined ? { failedAt } : {}));
1724
+ return {
1725
+ success,
1726
+ hops: hopInfos,
1727
+ finalTxHash,
1728
+ finalTxResponse: cascadeResponse,
1729
+ ...(failedAt !== undefined ? { failedAt } : {}),
1730
+ };
1624
1731
  };
1625
1732
  if (isMulti) {
1626
1733
  emitHopEvent(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_001](totalHops, chainLabels));
@@ -1631,33 +1738,32 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
1631
1738
  // its "start" coincides with submitting the composed Push tx. For
1632
1739
  // cascades that begin with R2/R3, the per-outbound/inbound branches
1633
1740
  // below own the emitHopStart for hop 0 to avoid a duplicate marker.
1634
- if (((_b = hopInfos[0]) === null || _b === void 0 ? void 0 : _b.route) === 'UOA_TO_PUSH') {
1741
+ if (hopInfos[0]?.route === 'UOA_TO_PUSH') {
1635
1742
  emitHopStart(0);
1636
1743
  }
1637
- cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
1744
+ cascadeProgressHook?.({
1638
1745
  hopIndex: 0,
1639
- route: ((_c = hopInfos[0]) === null || _c === void 0 ? void 0 : _c.route) || 'UOA_TO_PUSH',
1746
+ route: hopInfos[0]?.route || 'UOA_TO_PUSH',
1640
1747
  chain: (0, helpers_1.getPushChainForNetwork)(ctx.pushNetwork),
1641
1748
  status: 'waiting',
1642
1749
  elapsed: Date.now() - startTime,
1643
1750
  });
1644
- yield response.wait();
1751
+ await response.wait();
1645
1752
  const ceaToPushIndex = hopInfos.findIndex((h, index) => h.route === 'CEA_TO_PUSH' && !isNativeSeedOnlyHop(hops[index]));
1646
1753
  const isAfterCeaToPush = (index) => ceaToPushIndex >= 0 && index > ceaToPushIndex;
1647
1754
  const rootOutboundIndexByHop = buildRootOutboundIndexByHop(segments, hops);
1648
- const trackOutboundHops = (outboundHops, pushTxHash, resolvedSubTxId, outboundIndexByHop, trackingOptions) => tslib_1.__awaiter(this, void 0, void 0, function* () {
1649
- var _a, _b, _c, _d, _e;
1755
+ const trackOutboundHops = async (outboundHops, pushTxHash, resolvedSubTxId, outboundIndexByHop, trackingOptions) => {
1650
1756
  if (outboundHops.length === 0)
1651
1757
  return null;
1652
- const shouldDeferHopCompletion = (hop) => (trackingOptions === null || trackingOptions === void 0 ? void 0 : trackingOptions.deferCeaToPushCompletion) === true &&
1653
- (hop === null || hop === void 0 ? void 0 : hop.route) === 'CEA_TO_PUSH';
1758
+ const shouldDeferHopCompletion = (hop) => trackingOptions?.deferCeaToPushCompletion === true &&
1759
+ hop?.route === 'CEA_TO_PUSH';
1654
1760
  if (outboundHops.length === 1) {
1655
1761
  const hop = outboundHops[0];
1656
1762
  const hopHooks = (0, progress_route_hooks_1.pickWaitHooks)(hop.route);
1657
1763
  const remainingTimeout = timeout - (Date.now() - startTime);
1658
1764
  if (remainingTimeout <= 0) {
1659
1765
  hop.status = 'failed';
1660
- cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
1766
+ cascadeProgressHook?.({
1661
1767
  hopIndex: hop.hopIndex,
1662
1768
  route: hop.route,
1663
1769
  chain: hop.executionChain,
@@ -1670,7 +1776,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
1670
1776
  }
1671
1777
  emitHopStart(hop.hopIndex);
1672
1778
  emitHopEvent(hopHooks.awaiting(hop.executionChain, pushTxHash));
1673
- cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
1779
+ cascadeProgressHook?.({
1674
1780
  hopIndex: hop.hopIndex,
1675
1781
  route: hop.route,
1676
1782
  chain: hop.executionChain,
@@ -1679,12 +1785,19 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
1679
1785
  });
1680
1786
  let lastEmittedStatus;
1681
1787
  try {
1682
- const outboundDetails = yield callbacks.waitForOutboundTxFn(pushTxHash, Object.assign(Object.assign({ initialWaitMs: Math.min(60000, remainingTimeout), pollingIntervalMs, timeout: remainingTimeout, _resolvedSubTxId: resolvedSubTxId, _expectedDestinationChain: hop.executionChain }, (outboundIndexByHop
1683
- ? {
1684
- _outboundIndex: (_a = outboundIndexByHop.get(hop.hopIndex)) !== null && _a !== void 0 ? _a : 0,
1685
- }
1686
- : {})), { progressHook: (event) => {
1687
- cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
1788
+ const outboundDetails = await callbacks.waitForOutboundTxFn(pushTxHash, {
1789
+ initialWaitMs: Math.min(60000, remainingTimeout),
1790
+ pollingIntervalMs,
1791
+ timeout: remainingTimeout,
1792
+ _resolvedSubTxId: resolvedSubTxId,
1793
+ _expectedDestinationChain: hop.executionChain,
1794
+ ...(outboundIndexByHop
1795
+ ? {
1796
+ _outboundIndex: outboundIndexByHop.get(hop.hopIndex) ?? 0,
1797
+ }
1798
+ : {}),
1799
+ progressHook: (event) => {
1800
+ cascadeProgressHook?.({
1688
1801
  hopIndex: hop.hopIndex,
1689
1802
  route: hop.route,
1690
1803
  chain: hop.executionChain,
@@ -1696,14 +1809,18 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
1696
1809
  lastEmittedStatus = 'polling';
1697
1810
  emitHopEvent(hopHooks.polling(hop.executionChain, event.elapsed, pushTxHash));
1698
1811
  }
1699
- } }));
1812
+ },
1813
+ });
1700
1814
  // `outboundDetails.externalTxHash` from waitForOutboundTx
1701
1815
  // is the raw `0x`-hex form (internal canonical). For the
1702
1816
  // user-facing `hop.txHash`, `hop.outboundDetails`, and
1703
1817
  // cascade progress event, normalize to base58 for SVM so
1704
1818
  // consumers can paste it straight into a Solana explorer.
1705
- const displayTxHash = (_b = (0, helpers_1.toExternalTxHashDisplay)(outboundDetails.destinationChain, outboundDetails.externalTxHash)) !== null && _b !== void 0 ? _b : outboundDetails.externalTxHash;
1706
- const userFacingOutboundDetails = Object.assign(Object.assign({}, outboundDetails), { externalTxHash: displayTxHash });
1819
+ const displayTxHash = (0, helpers_1.toExternalTxHashDisplay)(outboundDetails.destinationChain, outboundDetails.externalTxHash) ?? outboundDetails.externalTxHash;
1820
+ const userFacingOutboundDetails = {
1821
+ ...outboundDetails,
1822
+ externalTxHash: displayTxHash,
1823
+ };
1707
1824
  const deferHopCompletion = shouldDeferHopCompletion(hop);
1708
1825
  hop.status = deferHopCompletion ? 'pending' : 'confirmed';
1709
1826
  hop.txHash = displayTxHash;
@@ -1717,7 +1834,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
1717
1834
  }
1718
1835
  emitHopComplete(hop.hopIndex);
1719
1836
  }
1720
- cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
1837
+ cascadeProgressHook?.({
1721
1838
  hopIndex: hop.hopIndex,
1722
1839
  route: hop.route,
1723
1840
  chain: hop.executionChain,
@@ -1729,8 +1846,8 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
1729
1846
  catch (err) {
1730
1847
  const errMsg = err instanceof Error ? err.message : String(err);
1731
1848
  const isTimeout = errMsg.startsWith('Timeout waiting for outbound transaction');
1732
- const failedExternalTxHash = err === null || err === void 0 ? void 0 : err.externalTxHash;
1733
- const failedDisplayTxHash = (_c = (0, helpers_1.toExternalTxHashDisplay)(hop.executionChain, failedExternalTxHash)) !== null && _c !== void 0 ? _c : failedExternalTxHash;
1849
+ const failedExternalTxHash = err?.externalTxHash;
1850
+ const failedDisplayTxHash = (0, helpers_1.toExternalTxHashDisplay)(hop.executionChain, failedExternalTxHash) ?? failedExternalTxHash;
1734
1851
  emitHopEvent(isTimeout
1735
1852
  ? hopHooks.timeout(hop.executionChain, remainingTimeout, pushTxHash)
1736
1853
  : hopHooks.failed(hop.executionChain, errMsg, {
@@ -1746,7 +1863,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
1746
1863
  if (failedDisplayTxHash)
1747
1864
  hop.txHash = failedDisplayTxHash;
1748
1865
  hop.status = 'failed';
1749
- cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
1866
+ cascadeProgressHook?.({
1750
1867
  hopIndex: hop.hopIndex,
1751
1868
  route: hop.route,
1752
1869
  chain: hop.executionChain,
@@ -1764,17 +1881,22 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
1764
1881
  }
1765
1882
  const perHopLastStatus = new Map();
1766
1883
  const confirmedHops = new Set();
1767
- const allOutboundDetails = yield callbacks.waitForAllOutboundTxsFn(pushTxHash, outboundHops, Object.assign(Object.assign({ initialWaitMs: Math.min(60000, timeout - (Date.now() - startTime)), pollingIntervalMs, timeout: timeout - (Date.now() - startTime), _resolvedSubTxId: resolvedSubTxId }, (outboundIndexByHop
1768
- ? { _outboundIndexByHop: outboundIndexByHop }
1769
- : {})), { progressHook: (event) => {
1770
- var _a, _b;
1884
+ const allOutboundDetails = await callbacks.waitForAllOutboundTxsFn(pushTxHash, outboundHops, {
1885
+ initialWaitMs: Math.min(60000, timeout - (Date.now() - startTime)),
1886
+ pollingIntervalMs,
1887
+ timeout: timeout - (Date.now() - startTime),
1888
+ _resolvedSubTxId: resolvedSubTxId,
1889
+ ...(outboundIndexByHop
1890
+ ? { _outboundIndexByHop: outboundIndexByHop }
1891
+ : {}),
1892
+ progressHook: (event) => {
1771
1893
  const matchedHop = outboundHops.find((h) => h.hopIndex === event.hopIndex);
1772
- const eventHooks = (0, progress_route_hooks_1.pickWaitHooks)(matchedHop === null || matchedHop === void 0 ? void 0 : matchedHop.route);
1894
+ const eventHooks = (0, progress_route_hooks_1.pickWaitHooks)(matchedHop?.route);
1773
1895
  const deferHopCompletion = shouldDeferHopCompletion(matchedHop);
1774
1896
  const progressStatus = deferHopCompletion && event.status === 'confirmed'
1775
1897
  ? 'found'
1776
1898
  : event.status;
1777
- cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
1899
+ cascadeProgressHook?.({
1778
1900
  hopIndex: event.hopIndex,
1779
1901
  route: event.route,
1780
1902
  chain: event.chain,
@@ -1785,7 +1907,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
1785
1907
  const prev = perHopLastStatus.get(event.hopIndex);
1786
1908
  if (event.status === 'polling' && prev !== 'polling') {
1787
1909
  perHopLastStatus.set(event.hopIndex, 'polling');
1788
- 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, pushTxHash));
1910
+ emitHopEvent(eventHooks.polling(matchedHop?.executionChain ?? event.chain ?? '', Date.now() - startTime, pushTxHash));
1789
1911
  }
1790
1912
  if (event.status === 'confirmed' &&
1791
1913
  event.txHash &&
@@ -1795,7 +1917,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
1795
1917
  // outbound-sync.ts:348-355). Auto-promotion paths
1796
1918
  // without a txHash are skipped by the `event.txHash`
1797
1919
  // guard above, so we only reach here with full details.
1798
- if (matchedHop === null || matchedHop === void 0 ? void 0 : matchedHop.outboundDetails) {
1920
+ if (matchedHop?.outboundDetails) {
1799
1921
  confirmedHops.add(event.hopIndex);
1800
1922
  if (!deferHopCompletion) {
1801
1923
  if (isMulti) {
@@ -1808,21 +1930,22 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
1808
1930
  }
1809
1931
  }
1810
1932
  }
1811
- } }));
1933
+ },
1934
+ });
1812
1935
  if (!allOutboundDetails.success) {
1813
- const failedIdx = (_d = allOutboundDetails.failedAt) !== null && _d !== void 0 ? _d : 0;
1936
+ const failedIdx = allOutboundDetails.failedAt ?? 0;
1814
1937
  const failedHop = outboundHops.find((h) => h.hopIndex === failedIdx);
1815
1938
  const failMsg = failedHop
1816
1939
  ? `Outbound failed for hop ${failedIdx} on ${failedHop.executionChain}`
1817
1940
  : `Outbound cascade failed at hop ${failedIdx}`;
1818
- 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, {
1941
+ emitHopEvent((0, progress_route_hooks_1.pickWaitHooks)(failedHop?.route).failed(failedHop?.executionChain ?? '', failMsg, {
1819
1942
  pushTxHash,
1820
- txHash: failedHop === null || failedHop === void 0 ? void 0 : failedHop.txHash,
1943
+ txHash: failedHop?.txHash,
1821
1944
  }));
1822
- emitCascadeFailed(failedIdx, failMsg, failedHop === null || failedHop === void 0 ? void 0 : failedHop.txHash, pushTxHash);
1945
+ emitCascadeFailed(failedIdx, failMsg, failedHop?.txHash, pushTxHash);
1823
1946
  return buildCompletionResult(false, failedIdx);
1824
1947
  }
1825
- if (trackingOptions === null || trackingOptions === void 0 ? void 0 : trackingOptions.deferCeaToPushCompletion) {
1948
+ if (trackingOptions?.deferCeaToPushCompletion) {
1826
1949
  for (const outHop of outboundHops) {
1827
1950
  if (shouldDeferHopCompletion(outHop) &&
1828
1951
  outHop.outboundDetails) {
@@ -1832,7 +1955,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
1832
1955
  }
1833
1956
  }
1834
1957
  return null;
1835
- });
1958
+ };
1836
1959
  // Mark only root Push Chain (Route 1) hops as confirmed. Route 1
1837
1960
  // hops after a Route 3 leg execute in the child inbound Push tx and
1838
1961
  // must wait for that child UTX to reach terminal success.
@@ -1842,7 +1965,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
1842
1965
  emitHopStart(hop.hopIndex);
1843
1966
  hop.status = 'confirmed';
1844
1967
  hop.txHash = response.hash;
1845
- cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
1968
+ cascadeProgressHook?.({
1846
1969
  hopIndex: hop.hopIndex,
1847
1970
  route: hop.route,
1848
1971
  chain: (0, helpers_1.getPushChainForNetwork)(ctx.pushNetwork),
@@ -1858,7 +1981,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
1858
1981
  !isAfterCeaToPush(hop.hopIndex)) {
1859
1982
  hop.status = 'confirmed';
1860
1983
  hop.txHash = response.hash;
1861
- cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
1984
+ cascadeProgressHook?.({
1862
1985
  hopIndex: hop.hopIndex,
1863
1986
  route: hop.route,
1864
1987
  chain: (0, helpers_1.getPushChainForNetwork)(ctx.pushNetwork),
@@ -1871,14 +1994,11 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
1871
1994
  }
1872
1995
  // 2. Track direct outbound hops (Route 2: UOA_TO_CEA) that belong
1873
1996
  // to the root Push UTX.
1874
- const rootOutboundHops = hopInfos.filter((h) => {
1875
- var _a;
1876
- return (h.route === 'UOA_TO_CEA' && !isAfterCeaToPush(h.hopIndex)) ||
1877
- (h.hopIndex === ceaToPushIndex &&
1878
- h.route === 'CEA_TO_PUSH' &&
1879
- ((_a = chain_1.CHAIN_INFO[h.executionChain]) === null || _a === void 0 ? void 0 : _a.vm) === enums_1.VM.EVM);
1880
- });
1881
- const rootOutboundFailure = yield trackOutboundHops(rootOutboundHops, response.hash, undefined, rootOutboundIndexByHop, { deferCeaToPushCompletion: true });
1997
+ const rootOutboundHops = hopInfos.filter((h) => (h.route === 'UOA_TO_CEA' && !isAfterCeaToPush(h.hopIndex)) ||
1998
+ (h.hopIndex === ceaToPushIndex &&
1999
+ h.route === 'CEA_TO_PUSH' &&
2000
+ chain_1.CHAIN_INFO[h.executionChain]?.vm === enums_1.VM.EVM));
2001
+ const rootOutboundFailure = await trackOutboundHops(rootOutboundHops, response.hash, undefined, rootOutboundIndexByHop, { deferCeaToPushCompletion: true });
1882
2002
  if (rootOutboundFailure)
1883
2003
  return rootOutboundFailure;
1884
2004
  let childInbound;
@@ -1893,12 +2013,12 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
1893
2013
  return buildCompletionResult(false, inboundHop.hopIndex);
1894
2014
  }
1895
2015
  const hasTrackedSourceOutbound = Boolean(inboundHop.outboundDetails) &&
1896
- ((_d = chain_1.CHAIN_INFO[inboundHop.executionChain]) === null || _d === void 0 ? void 0 : _d.vm) === enums_1.VM.EVM;
2016
+ chain_1.CHAIN_INFO[inboundHop.executionChain]?.vm === enums_1.VM.EVM;
1897
2017
  if (!hasTrackedSourceOutbound) {
1898
2018
  emitHopStart(inboundHop.hopIndex);
1899
2019
  emitHopEvent(hopHooks.awaiting(inboundHop.executionChain, response.hash));
1900
2020
  }
1901
- cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
2021
+ cascadeProgressHook?.({
1902
2022
  hopIndex: inboundHop.hopIndex,
1903
2023
  route: inboundHop.route,
1904
2024
  chain: inboundHop.executionChain,
@@ -1914,14 +2034,14 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
1914
2034
  inboundHop.outboundDetails;
1915
2035
  }
1916
2036
  else {
1917
- sourceOutboundDetails = yield callbacks.waitForOutboundTxFn(response.hash, {
2037
+ sourceOutboundDetails = await callbacks.waitForOutboundTxFn(response.hash, {
1918
2038
  initialWaitMs: Math.min(60000, remainingTimeout),
1919
2039
  pollingIntervalMs,
1920
2040
  timeout: remainingTimeout,
1921
2041
  _expectedDestinationChain: inboundHop.executionChain,
1922
- _outboundIndex: (_e = rootOutboundIndexByHop.get(inboundHop.hopIndex)) !== null && _e !== void 0 ? _e : 0,
2042
+ _outboundIndex: rootOutboundIndexByHop.get(inboundHop.hopIndex) ?? 0,
1923
2043
  progressHook: (event) => {
1924
- cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
2044
+ cascadeProgressHook?.({
1925
2045
  hopIndex: inboundHop.hopIndex,
1926
2046
  route: inboundHop.route,
1927
2047
  chain: inboundHop.executionChain,
@@ -1940,8 +2060,8 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
1940
2060
  catch (err) {
1941
2061
  const errMsg = err instanceof Error ? err.message : String(err);
1942
2062
  const isTimeout = errMsg.startsWith('Timeout waiting for outbound transaction');
1943
- const failedExternalTxHash = err === null || err === void 0 ? void 0 : err.externalTxHash;
1944
- const failedDisplayTxHash = (_f = (0, helpers_1.toExternalTxHashDisplay)(inboundHop.executionChain, failedExternalTxHash)) !== null && _f !== void 0 ? _f : failedExternalTxHash;
2063
+ const failedExternalTxHash = err?.externalTxHash;
2064
+ const failedDisplayTxHash = (0, helpers_1.toExternalTxHashDisplay)(inboundHop.executionChain, failedExternalTxHash) ?? failedExternalTxHash;
1945
2065
  emitHopEvent(isTimeout
1946
2066
  ? hopHooks.timeout(inboundHop.executionChain, remainingTimeout, response.hash)
1947
2067
  : hopHooks.failed(inboundHop.executionChain, errMsg, {
@@ -1955,7 +2075,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
1955
2075
  inboundHop.status = 'failed';
1956
2076
  if (failedDisplayTxHash)
1957
2077
  inboundHop.txHash = failedDisplayTxHash;
1958
- cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
2078
+ cascadeProgressHook?.({
1959
2079
  hopIndex: inboundHop.hopIndex,
1960
2080
  route: inboundHop.route,
1961
2081
  chain: inboundHop.executionChain,
@@ -1965,15 +2085,19 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
1965
2085
  });
1966
2086
  return buildCompletionResult(false, inboundHop.hopIndex);
1967
2087
  }
1968
- const displayExternalTxHash = (_g = (0, helpers_1.toExternalTxHashDisplay)(sourceOutboundDetails.destinationChain, sourceOutboundDetails.externalTxHash)) !== null && _g !== void 0 ? _g : sourceOutboundDetails.externalTxHash;
1969
- inboundHop.outboundDetails = Object.assign(Object.assign({}, sourceOutboundDetails), { externalTxHash: displayExternalTxHash });
2088
+ const displayExternalTxHash = (0, helpers_1.toExternalTxHashDisplay)(sourceOutboundDetails.destinationChain, sourceOutboundDetails.externalTxHash) ?? sourceOutboundDetails.externalTxHash;
2089
+ inboundHop.outboundDetails = {
2090
+ ...sourceOutboundDetails,
2091
+ externalTxHash: displayExternalTxHash,
2092
+ };
1970
2093
  inboundHop.txHash = displayExternalTxHash;
1971
2094
  emitHopEvent(hopHooks.success(inboundHop.outboundDetails));
1972
2095
  emitHopEvent(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_310_01](inboundHop.executionChain, response.hash));
1973
2096
  let inboundLastStatus;
1974
2097
  const inboundRemainingTimeout = timeout - (Date.now() - startTime);
1975
2098
  try {
1976
- childInbound = yield ((_h = callbacks.waitForInboundPushTxFn) !== null && _h !== void 0 ? _h : ((externalTxHash, sourceChain, opts) => (0, inbound_tracker_1.waitForInboundPushTx)(ctx, externalTxHash, sourceChain, opts)))(sourceOutboundDetails.externalTxHash, inboundHop.executionChain, {
2099
+ childInbound = await (callbacks.waitForInboundPushTxFn ??
2100
+ ((externalTxHash, sourceChain, opts) => (0, inbound_tracker_1.waitForInboundPushTx)(ctx, externalTxHash, sourceChain, opts)))(sourceOutboundDetails.externalTxHash, inboundHop.executionChain, {
1977
2101
  initialWaitMs: Math.min(60000, inboundRemainingTimeout),
1978
2102
  pollingIntervalMs,
1979
2103
  timeout: inboundRemainingTimeout,
@@ -1997,7 +2121,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
1997
2121
  else
1998
2122
  emitCascadeFailed(inboundHop.hopIndex, errMsg, undefined, response.hash);
1999
2123
  inboundHop.status = 'failed';
2000
- cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
2124
+ cascadeProgressHook?.({
2001
2125
  hopIndex: inboundHop.hopIndex,
2002
2126
  route: inboundHop.route,
2003
2127
  chain: inboundHop.executionChain,
@@ -2007,13 +2131,14 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
2007
2131
  return buildCompletionResult(false, inboundHop.hopIndex);
2008
2132
  }
2009
2133
  if (childInbound.status !== 'confirmed') {
2010
- const failMsg = (_j = childInbound.errorMessage) !== null && _j !== void 0 ? _j : 'inbound execution failed on Push Chain';
2134
+ const failMsg = childInbound.errorMessage ??
2135
+ 'inbound execution failed on Push Chain';
2011
2136
  inboundHop.status = 'failed';
2012
2137
  inboundHop.txHash =
2013
2138
  childInbound.pushTxHash || displayExternalTxHash;
2014
2139
  emitHopEvent(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_399_02](failMsg, 'inbound', inboundHop.executionChain, undefined, response.hash, childInbound.pushTxHash || undefined));
2015
2140
  emitCascadeFailed(inboundHop.hopIndex, failMsg, childInbound.pushTxHash || undefined, response.hash);
2016
- cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
2141
+ cascadeProgressHook?.({
2017
2142
  hopIndex: inboundHop.hopIndex,
2018
2143
  route: inboundHop.route,
2019
2144
  chain: inboundHop.executionChain,
@@ -2033,7 +2158,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
2033
2158
  emitHopEvent(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_399_01](inboundHop.executionChain, childInbound.pushTxHash));
2034
2159
  }
2035
2160
  emitHopComplete(inboundHop.hopIndex);
2036
- cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
2161
+ cascadeProgressHook?.({
2037
2162
  hopIndex: inboundHop.hopIndex,
2038
2163
  route: inboundHop.route,
2039
2164
  chain: inboundHop.executionChain,
@@ -2049,7 +2174,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
2049
2174
  emitHopStart(hop.hopIndex);
2050
2175
  hop.status = 'confirmed';
2051
2176
  hop.txHash = childInbound.pushTxHash;
2052
- cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
2177
+ cascadeProgressHook?.({
2053
2178
  hopIndex: hop.hopIndex,
2054
2179
  route: hop.route,
2055
2180
  chain: (0, helpers_1.getPushChainForNetwork)(ctx.pushNetwork),
@@ -2062,7 +2187,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
2062
2187
  }
2063
2188
  }
2064
2189
  const childOutboundHops = hopInfos.filter((h) => h.route === 'UOA_TO_CEA' && isAfterCeaToPush(h.hopIndex));
2065
- const childOutboundFailure = yield trackOutboundHops(childOutboundHops, childInbound.pushTxHash || response.hash, childInbound.childUtxId);
2190
+ const childOutboundFailure = await trackOutboundHops(childOutboundHops, childInbound.pushTxHash || response.hash, childInbound.childUtxId);
2066
2191
  if (childOutboundFailure)
2067
2192
  return childOutboundFailure;
2068
2193
  }
@@ -2073,7 +2198,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
2073
2198
  const failedIdx = hopInfos.findIndex((h) => h.status !== 'confirmed');
2074
2199
  const errMsg = err instanceof Error ? err.message : String(err);
2075
2200
  const failedAt = failedIdx >= 0 ? failedIdx : 0;
2076
- const failedTxHash = (_k = hopInfos[failedAt]) === null || _k === void 0 ? void 0 : _k.txHash;
2201
+ const failedTxHash = hopInfos[failedAt]?.txHash;
2077
2202
  const isTimeout = errMsg.startsWith('Timeout');
2078
2203
  if (isTimeout) {
2079
2204
  emitCascadeTimeout(failedAt, failedTxHash, response.hash);
@@ -2083,11 +2208,11 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks, defaultEventHook, op
2083
2208
  }
2084
2209
  return buildCompletionResult(false, failedAt);
2085
2210
  }
2086
- }),
2087
- wait: (opts) => tslib_1.__awaiter(this, void 0, void 0, function* () { return cascadeResponse.waitForAll(opts); }),
2211
+ },
2212
+ wait: async (opts) => cascadeResponse.waitForAll(opts),
2088
2213
  };
2089
2214
  return cascadeResponse;
2090
- }),
2215
+ },
2091
2216
  };
2092
2217
  }
2093
2218
  //# sourceMappingURL=cascade.js.map