@pushchain/core 5.1.5-alpha.1 → 5.1.6
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.
- package/CHANGELOG.md +21 -0
- package/package.json +1 -1
- package/src/lib/constants/abi/index.d.ts +2 -0
- package/src/lib/constants/abi/index.js +6 -1
- package/src/lib/constants/abi/uniswap-v3-router.evm.d.ts +58 -0
- package/src/lib/constants/abi/uniswap-v3-router.evm.js +41 -0
- package/src/lib/constants/abi/wpc.evm.d.ts +24 -0
- package/src/lib/constants/abi/wpc.evm.js +27 -0
- package/src/lib/constants/chain.d.ts +1 -0
- package/src/lib/constants/chain.js +8 -6
- package/src/lib/constants/tokens.js +3 -3
- package/src/lib/generated/uexecutor/v2/types.d.ts +14 -0
- package/src/lib/generated/uexecutor/v2/types.js +96 -3
- package/src/lib/orchestrator/internals/cascade.d.ts +1 -1
- package/src/lib/orchestrator/internals/cascade.js +521 -239
- package/src/lib/orchestrator/internals/confirmation.js +60 -15
- package/src/lib/orchestrator/internals/context.d.ts +23 -0
- package/src/lib/orchestrator/internals/context.js +58 -0
- package/src/lib/orchestrator/internals/execute-funds-only.js +47 -21
- package/src/lib/orchestrator/internals/execute-funds-payload.js +70 -37
- package/src/lib/orchestrator/internals/execute-standard.js +137 -26
- package/src/lib/orchestrator/internals/gas-calculator.d.ts +5 -2
- package/src/lib/orchestrator/internals/gas-calculator.js +59 -11
- package/src/lib/orchestrator/internals/gas-usd-sizer.d.ts +53 -0
- package/src/lib/orchestrator/internals/gas-usd-sizer.js +165 -0
- package/src/lib/orchestrator/internals/helpers.js +2 -0
- package/src/lib/orchestrator/internals/inbound-tracker.d.ts +83 -0
- package/src/lib/orchestrator/internals/inbound-tracker.js +322 -0
- package/src/lib/orchestrator/internals/index.d.ts +3 -1
- package/src/lib/orchestrator/internals/index.js +8 -2
- package/src/lib/orchestrator/internals/outbound-sync.d.ts +32 -1
- package/src/lib/orchestrator/internals/outbound-sync.js +154 -17
- package/src/lib/orchestrator/internals/pc-usd-oracle.d.ts +28 -0
- package/src/lib/orchestrator/internals/pc-usd-oracle.js +285 -0
- package/src/lib/orchestrator/internals/progress-route-hooks.d.ts +23 -0
- package/src/lib/orchestrator/internals/progress-route-hooks.js +72 -0
- package/src/lib/orchestrator/internals/push-chain-tx.d.ts +27 -1
- package/src/lib/orchestrator/internals/push-chain-tx.js +111 -15
- package/src/lib/orchestrator/internals/rescue.js +1 -1
- package/src/lib/orchestrator/internals/response-builder.d.ts +5 -0
- package/src/lib/orchestrator/internals/response-builder.js +242 -66
- package/src/lib/orchestrator/internals/route-handlers.js +480 -206
- package/src/lib/orchestrator/internals/tx-transformer.js +293 -35
- package/src/lib/orchestrator/orchestrator.d.ts +7 -1
- package/src/lib/orchestrator/orchestrator.js +108 -56
- package/src/lib/orchestrator/orchestrator.types.d.ts +111 -5
- package/src/lib/orchestrator/payload-builders.d.ts +16 -0
- package/src/lib/orchestrator/payload-builders.js +6 -5
- package/src/lib/orchestrator/route-detector.js +20 -8
- package/src/lib/orchestrator/svm-idl/normalize-address.d.ts +19 -0
- package/src/lib/orchestrator/svm-idl/normalize-address.js +56 -0
- package/src/lib/orchestrator/svm-idl/registry.d.ts +13 -4
- package/src/lib/orchestrator/svm-idl/registry.js +15 -14
- package/src/lib/orchestrator/svm-idl/resolve.js +5 -2
- package/src/lib/progress-hook/progress-hook.d.ts +28 -0
- package/src/lib/progress-hook/progress-hook.js +552 -133
- package/src/lib/progress-hook/progress-hook.types.d.ts +77 -29
- package/src/lib/progress-hook/progress-hook.types.js +75 -35
- package/src/lib/push-client/push-client.d.ts +7 -0
- package/src/lib/push-client/push-client.js +18 -1
- package/src/lib/universal-tx-detector/__tests__/fixtures/builders.d.ts +32 -0
- package/src/lib/universal-tx-detector/__tests__/fixtures/builders.js +65 -0
- package/src/lib/universal-tx-detector/__tests__/fixtures/scenarios.d.ts +72 -0
- package/src/lib/universal-tx-detector/__tests__/fixtures/scenarios.js +669 -0
- package/src/lib/universal-tx-detector/auto-detect.d.ts +37 -0
- package/src/lib/universal-tx-detector/auto-detect.js +112 -0
- package/src/lib/universal-tx-detector/cascade.d.ts +64 -0
- package/src/lib/universal-tx-detector/cascade.js +254 -0
- package/src/lib/universal-tx-detector/child-inbounds.d.ts +58 -0
- package/src/lib/universal-tx-detector/child-inbounds.js +145 -0
- package/src/lib/universal-tx-detector/classify.d.ts +33 -0
- package/src/lib/universal-tx-detector/classify.js +228 -0
- package/src/lib/universal-tx-detector/detector-svm.d.ts +46 -0
- package/src/lib/universal-tx-detector/detector-svm.js +324 -0
- package/src/lib/universal-tx-detector/detector.d.ts +13 -0
- package/src/lib/universal-tx-detector/detector.js +233 -0
- package/src/lib/universal-tx-detector/events.d.ts +765 -0
- package/src/lib/universal-tx-detector/events.js +241 -0
- package/src/lib/universal-tx-detector/index.d.ts +18 -0
- package/src/lib/universal-tx-detector/index.js +36 -0
- package/src/lib/universal-tx-detector/svm-events.d.ts +41 -0
- package/src/lib/universal-tx-detector/svm-events.js +68 -0
- package/src/lib/universal-tx-detector/types.d.ts +100 -0
- package/src/lib/universal-tx-detector/types.js +3 -0
- package/src/lib/utils.d.ts +30 -13
- package/src/lib/utils.js +47 -21
|
@@ -22,14 +22,17 @@ const route_detector_1 = require("../route-detector");
|
|
|
22
22
|
const cea_utils_1 = require("../cea-utils");
|
|
23
23
|
const payload_builders_1 = require("../payload-builders");
|
|
24
24
|
const push_chain_1 = require("../../push-chain/push-chain");
|
|
25
|
+
const context_1 = require("./context");
|
|
25
26
|
const helpers_1 = require("./helpers");
|
|
26
27
|
const payload_builder_1 = require("./payload-builder");
|
|
27
28
|
const uea_manager_1 = require("./uea-manager");
|
|
28
29
|
const gas_calculator_1 = require("./gas-calculator");
|
|
30
|
+
const abi_1 = require("../../constants/abi");
|
|
29
31
|
const route_handlers_1 = require("./route-handlers");
|
|
30
|
-
const
|
|
31
|
-
const progress_hook_types_1 = require("../../progress-hook/progress-hook.types");
|
|
32
|
+
const progress_route_hooks_1 = require("./progress-route-hooks");
|
|
32
33
|
const progress_hook_1 = tslib_1.__importDefault(require("../../progress-hook/progress-hook"));
|
|
34
|
+
const progress_hook_types_1 = require("../../progress-hook/progress-hook.types");
|
|
35
|
+
const build_payload_1 = require("../svm-idl/build-payload");
|
|
33
36
|
// ============================================================================
|
|
34
37
|
// prepareTransaction
|
|
35
38
|
// ============================================================================
|
|
@@ -133,21 +136,29 @@ function buildHopDescriptor(ctx, params, route, ueaAddress) {
|
|
|
133
136
|
burnAmount = params.value;
|
|
134
137
|
}
|
|
135
138
|
else if (hasSvmExecute) {
|
|
139
|
+
// PAYLOAD-only SVM execute: still need native PRC-20 for chain namespace
|
|
140
|
+
// + gas fees, but burn amount must be zero. Matches executeUoaToCeaSvm
|
|
141
|
+
// (route-handlers.ts) and the EVM cascade branch below.
|
|
142
|
+
// Using burnAmount=1 would trigger transferFrom on the PRC-20 token,
|
|
143
|
+
// which reverts with InsufficientBalance when the UEA has no pSOL balance.
|
|
136
144
|
prc20Token = (0, helpers_1.getNativePRC20ForChain)(targetChain, ctx.pushNetwork);
|
|
137
|
-
burnAmount = BigInt(
|
|
145
|
+
burnAmount = BigInt(0);
|
|
138
146
|
}
|
|
139
147
|
let gasToken = selectors_1.ZERO_ADDRESS;
|
|
140
148
|
let gasFee = BigInt(0);
|
|
149
|
+
let sizing;
|
|
141
150
|
if (prc20Token !== selectors_1.ZERO_ADDRESS) {
|
|
142
|
-
const result = yield (0, gas_calculator_1.queryOutboundGasFee)(ctx, prc20Token, gasLimit);
|
|
151
|
+
const result = yield (0, gas_calculator_1.queryOutboundGasFee)(ctx, prc20Token, gasLimit, targetChain);
|
|
143
152
|
gasToken = result.gasToken;
|
|
144
153
|
gasFee = result.gasFee;
|
|
154
|
+
sizing = result.sizing;
|
|
145
155
|
}
|
|
146
156
|
return Object.assign(Object.assign({}, baseDescriptor), { targetChain, isSvmTarget: true, svmPayload,
|
|
147
157
|
prc20Token,
|
|
148
158
|
burnAmount,
|
|
149
159
|
gasToken,
|
|
150
|
-
gasFee
|
|
160
|
+
gasFee,
|
|
161
|
+
sizing });
|
|
151
162
|
}
|
|
152
163
|
// EVM path: Resolve CEA address + build CEA multicalls
|
|
153
164
|
const { cea: ceaAddress } = yield (0, cea_utils_1.getCEAAddress)(ueaAddress, targetChain, (_c = ctx.rpcUrls[targetChain]) === null || _c === void 0 ? void 0 : _c[0]);
|
|
@@ -157,16 +168,18 @@ function buildHopDescriptor(ctx, params, route, ueaAddress) {
|
|
|
157
168
|
const burnAmount = BigInt(0); // Migration is logic-only — no funds. CEA rejects msg.value != 0.
|
|
158
169
|
let gasToken = selectors_1.ZERO_ADDRESS;
|
|
159
170
|
let gasFee = BigInt(0);
|
|
171
|
+
let sizing;
|
|
160
172
|
if (prc20Token !== selectors_1.ZERO_ADDRESS) {
|
|
161
|
-
const result = yield (0, gas_calculator_1.queryOutboundGasFee)(ctx, prc20Token, gasLimit);
|
|
173
|
+
const result = yield (0, gas_calculator_1.queryOutboundGasFee)(ctx, prc20Token, gasLimit, targetChain);
|
|
162
174
|
gasToken = result.gasToken;
|
|
163
175
|
gasFee = result.gasFee;
|
|
176
|
+
sizing = result.sizing;
|
|
164
177
|
}
|
|
165
178
|
return Object.assign(Object.assign({}, baseDescriptor), { targetChain,
|
|
166
179
|
ceaAddress, ceaMulticalls: [], prc20Token,
|
|
167
180
|
burnAmount,
|
|
168
181
|
gasToken,
|
|
169
|
-
gasFee, isMigration: true });
|
|
182
|
+
gasFee, isMigration: true, sizing });
|
|
170
183
|
}
|
|
171
184
|
// Build CEA multicalls
|
|
172
185
|
const ceaMulticalls = [];
|
|
@@ -240,10 +253,12 @@ function buildHopDescriptor(ctx, params, route, ueaAddress) {
|
|
|
240
253
|
// Query gas fee
|
|
241
254
|
let gasToken = selectors_1.ZERO_ADDRESS;
|
|
242
255
|
let gasFee = BigInt(0);
|
|
256
|
+
let sizing;
|
|
243
257
|
if (prc20Token !== selectors_1.ZERO_ADDRESS) {
|
|
244
|
-
const result = yield (0, gas_calculator_1.queryOutboundGasFee)(ctx, prc20Token, gasLimit);
|
|
258
|
+
const result = yield (0, gas_calculator_1.queryOutboundGasFee)(ctx, prc20Token, gasLimit, targetChain);
|
|
245
259
|
gasToken = result.gasToken;
|
|
246
260
|
gasFee = result.gasFee;
|
|
261
|
+
sizing = result.sizing;
|
|
247
262
|
}
|
|
248
263
|
return Object.assign(Object.assign({}, baseDescriptor), { targetChain,
|
|
249
264
|
ceaAddress,
|
|
@@ -251,7 +266,8 @@ function buildHopDescriptor(ctx, params, route, ueaAddress) {
|
|
|
251
266
|
prc20Token,
|
|
252
267
|
burnAmount,
|
|
253
268
|
gasToken,
|
|
254
|
-
gasFee
|
|
269
|
+
gasFee,
|
|
270
|
+
sizing });
|
|
255
271
|
}
|
|
256
272
|
case route_detector_1.TransactionRoute.CEA_TO_PUSH: {
|
|
257
273
|
// Route 3: Build CEA multicalls for sendUniversalTxFromCEA
|
|
@@ -292,13 +308,16 @@ function buildHopDescriptor(ctx, params, route, ueaAddress) {
|
|
|
292
308
|
}
|
|
293
309
|
let gasToken = selectors_1.ZERO_ADDRESS;
|
|
294
310
|
let gasFee = BigInt(0);
|
|
311
|
+
let sizing;
|
|
295
312
|
if (prc20Token !== selectors_1.ZERO_ADDRESS) {
|
|
296
|
-
const result = yield (0, gas_calculator_1.queryOutboundGasFee)(ctx, prc20Token, gasLimit);
|
|
313
|
+
const result = yield (0, gas_calculator_1.queryOutboundGasFee)(ctx, prc20Token, gasLimit, sourceChain);
|
|
297
314
|
gasToken = result.gasToken;
|
|
298
315
|
gasFee = result.gasFee;
|
|
316
|
+
sizing = result.sizing;
|
|
299
317
|
}
|
|
300
318
|
return Object.assign(Object.assign({}, baseDescriptor), { sourceChain, ceaAddress: ceaPdaHex, isSvmTarget: true, prc20Token, burnAmount: amount, gasToken,
|
|
301
|
-
gasFee
|
|
319
|
+
gasFee,
|
|
320
|
+
sizing });
|
|
302
321
|
}
|
|
303
322
|
const { cea: ceaAddress } = yield (0, cea_utils_1.getCEAAddress)(ueaAddress, sourceChain, (_h = ctx.rpcUrls[sourceChain]) === null || _h === void 0 ? void 0 : _h[0]);
|
|
304
323
|
// CEA auto-deploys on-chain: Vault.finalizeUniversalTx calls CEAFactory.deployCEA()
|
|
@@ -328,15 +347,18 @@ function buildHopDescriptor(ctx, params, route, ueaAddress) {
|
|
|
328
347
|
const prc20Token = (0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork);
|
|
329
348
|
let gasToken = selectors_1.ZERO_ADDRESS;
|
|
330
349
|
let gasFee = BigInt(0);
|
|
350
|
+
let sizing;
|
|
331
351
|
if (prc20Token !== selectors_1.ZERO_ADDRESS) {
|
|
332
|
-
const result = yield (0, gas_calculator_1.queryOutboundGasFee)(ctx, prc20Token, gasLimit);
|
|
352
|
+
const result = yield (0, gas_calculator_1.queryOutboundGasFee)(ctx, prc20Token, gasLimit, sourceChain);
|
|
333
353
|
gasToken = result.gasToken;
|
|
334
354
|
gasFee = result.gasFee;
|
|
355
|
+
sizing = result.sizing;
|
|
335
356
|
}
|
|
336
357
|
return Object.assign(Object.assign({}, baseDescriptor), { sourceChain,
|
|
337
358
|
ceaAddress,
|
|
338
359
|
prc20Token, burnAmount: amount, gasToken,
|
|
339
|
-
gasFee
|
|
360
|
+
gasFee,
|
|
361
|
+
sizing });
|
|
340
362
|
}
|
|
341
363
|
default:
|
|
342
364
|
return baseDescriptor;
|
|
@@ -356,10 +378,20 @@ function classifyIntoSegments(hops) {
|
|
|
356
378
|
const chain = hop.targetChain || hop.sourceChain;
|
|
357
379
|
const canMerge = currentSegment &&
|
|
358
380
|
currentSegment.type === segType &&
|
|
359
|
-
// Same-chain merging for OUTBOUND_TO_CEA (EVM only — SVM hops are atomic)
|
|
381
|
+
// Same-chain merging for OUTBOUND_TO_CEA (EVM only — SVM hops are atomic).
|
|
382
|
+
// Same-source merging for INBOUND_FROM_CEA (EVM only) — multiple R3 hops
|
|
383
|
+
// that share a source chain collapse into ONE CEA→UEA round-trip whose
|
|
384
|
+
// inbound multicall runs every hop's target op. Without this merge the
|
|
385
|
+
// cascade composer would nest the second R3 as an outbound from inside
|
|
386
|
+
// the first inbound's UEA multicall, which fails inside UGPC.
|
|
387
|
+
// sendUniversalTxOutbound because the UEA holds no PRC20 at that point
|
|
388
|
+
// (see cascade.ts composeCascade INBOUND_FROM_CEA branch).
|
|
360
389
|
(segType === 'OUTBOUND_TO_CEA'
|
|
361
390
|
? currentSegment.targetChain === hop.targetChain && !hop.isSvmTarget
|
|
362
|
-
: segType === '
|
|
391
|
+
: segType === 'INBOUND_FROM_CEA'
|
|
392
|
+
? currentSegment.sourceChain === hop.sourceChain &&
|
|
393
|
+
!(0, payload_builders_1.isSvmChain)(hop.sourceChain)
|
|
394
|
+
: segType === 'PUSH_EXECUTION');
|
|
363
395
|
if (canMerge && currentSegment) {
|
|
364
396
|
// Merge into current segment
|
|
365
397
|
currentSegment.hops.push(hop);
|
|
@@ -379,12 +411,36 @@ function classifyIntoSegments(hops) {
|
|
|
379
411
|
currentSegment.gasFee =
|
|
380
412
|
(currentSegment.gasFee || BigInt(0)) + (hop.gasFee || BigInt(0));
|
|
381
413
|
}
|
|
414
|
+
else if (segType === 'INBOUND_FROM_CEA') {
|
|
415
|
+
// Sum burn amounts so the single sendUniversalTxToUEA carries the
|
|
416
|
+
// combined native value forwarded from CEA back to the UEA.
|
|
417
|
+
currentSegment.totalBurnAmount =
|
|
418
|
+
(currentSegment.totalBurnAmount || BigInt(0)) +
|
|
419
|
+
(hop.burnAmount || BigInt(0));
|
|
420
|
+
if (hop.gasLimit > (currentSegment.gasLimit || BigInt(0))) {
|
|
421
|
+
currentSegment.gasLimit = hop.gasLimit;
|
|
422
|
+
}
|
|
423
|
+
currentSegment.gasFee =
|
|
424
|
+
(currentSegment.gasFee || BigInt(0)) + (hop.gasFee || BigInt(0));
|
|
425
|
+
}
|
|
382
426
|
else if (segType === 'PUSH_EXECUTION') {
|
|
383
427
|
currentSegment.mergedPushMulticalls = [
|
|
384
428
|
...(currentSegment.mergedPushMulticalls || []),
|
|
385
429
|
...(hop.pushMulticalls || []),
|
|
386
430
|
];
|
|
387
431
|
}
|
|
432
|
+
// Merge sizing: take the strictest category (C > B > A). Overflow
|
|
433
|
+
// from a Case C hop wins so the segment's msg.value covers the
|
|
434
|
+
// highest-cost hop in the merge.
|
|
435
|
+
if (hop.sizing) {
|
|
436
|
+
if (!currentSegment.sizing) {
|
|
437
|
+
currentSegment.sizing = hop.sizing;
|
|
438
|
+
}
|
|
439
|
+
else if (categoryRank(hop.sizing.category) >
|
|
440
|
+
categoryRank(currentSegment.sizing.category)) {
|
|
441
|
+
currentSegment.sizing = hop.sizing;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
388
444
|
}
|
|
389
445
|
else {
|
|
390
446
|
// Start a new segment
|
|
@@ -404,12 +460,17 @@ function classifyIntoSegments(hops) {
|
|
|
404
460
|
gasToken: hop.gasToken,
|
|
405
461
|
gasFee: hop.gasFee,
|
|
406
462
|
gasLimit: hop.gasLimit,
|
|
463
|
+
sizing: hop.sizing,
|
|
407
464
|
};
|
|
408
465
|
segments.push(currentSegment);
|
|
409
466
|
}
|
|
410
467
|
}
|
|
411
468
|
return segments;
|
|
412
469
|
}
|
|
470
|
+
/** Ordering for sizing-category severity (higher = stricter). */
|
|
471
|
+
function categoryRank(c) {
|
|
472
|
+
return c === 'C' ? 2 : c === 'B' ? 1 : 0;
|
|
473
|
+
}
|
|
413
474
|
// ============================================================================
|
|
414
475
|
// getSegmentType
|
|
415
476
|
// ============================================================================
|
|
@@ -429,237 +490,365 @@ function getSegmentType(route) {
|
|
|
429
490
|
// composeCascade
|
|
430
491
|
// ============================================================================
|
|
431
492
|
function composeCascade(ctx, segments, ueaAddress, ueaBalance, ueaNonce) {
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
493
|
+
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
494
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
495
|
+
let accumulatedPushMulticalls = [];
|
|
496
|
+
const gatewayPcAddress = (0, helpers_1.getUniversalGatewayPCAddress)();
|
|
497
|
+
// Compute per-outbound nativeValueForGas from UEA balance
|
|
498
|
+
// Each outbound segment needs native value for the gas swap on the destination chain.
|
|
499
|
+
// The contract refunds excess, so over-allocating is safe.
|
|
500
|
+
const numOutbounds = segments.filter((s) => s.type !== 'PUSH_EXECUTION').length;
|
|
501
|
+
const CASCADE_GAS_RESERVE = BigInt(3e18); // 3 PC reserve for gas costs
|
|
502
|
+
let perOutboundNativeValue;
|
|
503
|
+
if (ueaBalance && numOutbounds > 0 && ueaBalance > CASCADE_GAS_RESERVE) {
|
|
504
|
+
perOutboundNativeValue =
|
|
505
|
+
(ueaBalance - CASCADE_GAS_RESERVE) / BigInt(numOutbounds);
|
|
506
|
+
}
|
|
507
|
+
// Pool-price-based native-value override for SVM outbounds.
|
|
508
|
+
// The flat split above works for EVM gas tokens (pBNB/pETH — cheap per unit),
|
|
509
|
+
// but pSOL has a much higher WPC/pSOL pool price, so the flat 1 PC split is
|
|
510
|
+
// insufficient to swap for gasFee wei of pSOL and Uniswap reverts "STF".
|
|
511
|
+
// Mirror executeUoaToCeaSvm's pool-price read (route-handlers.ts) per SVM segment.
|
|
512
|
+
const svmSegments = segments.filter((s) => {
|
|
513
|
+
var _a;
|
|
514
|
+
return s.type === 'OUTBOUND_TO_CEA' &&
|
|
515
|
+
((_a = s.hops[0]) === null || _a === void 0 ? void 0 : _a.isSvmTarget) === true &&
|
|
516
|
+
s.gasToken &&
|
|
517
|
+
s.gasFee &&
|
|
518
|
+
s.gasFee > BigInt(0);
|
|
519
|
+
});
|
|
520
|
+
const svmNativeValueBySegment = new Map();
|
|
521
|
+
if (svmSegments.length > 0 && ueaBalance && ueaBalance > CASCADE_GAS_RESERVE) {
|
|
522
|
+
const universalCoreAddress = yield ctx.pushClient.readContract({
|
|
523
|
+
address: gatewayPcAddress,
|
|
524
|
+
abi: abi_1.UNIVERSAL_GATEWAY_PC,
|
|
525
|
+
functionName: 'UNIVERSAL_CORE',
|
|
526
|
+
args: [],
|
|
527
|
+
});
|
|
528
|
+
// Compute per-SVM budget = ueaBalance − (PC needed for EVM outbounds) − 1 PC safety
|
|
529
|
+
// (outer-tx gas + refund headroom). Pass a huge accountBalance to the estimator so
|
|
530
|
+
// its internal cap is effectively disabled; we cap externally to the per-SVM budget.
|
|
531
|
+
const numEvmOutbounds = numOutbounds - svmSegments.length;
|
|
532
|
+
const evmReservation = perOutboundNativeValue && numEvmOutbounds > 0
|
|
533
|
+
? perOutboundNativeValue * BigInt(numEvmOutbounds)
|
|
534
|
+
: BigInt(0);
|
|
535
|
+
const outerSafety = BigInt(1e18); // 1 PC for outer-tx gas
|
|
536
|
+
const svmTotal = ueaBalance > evmReservation + outerSafety
|
|
537
|
+
? ueaBalance - evmReservation - outerSafety
|
|
538
|
+
: BigInt(0);
|
|
539
|
+
const perSvmCap = svmTotal / BigInt(svmSegments.length);
|
|
540
|
+
const UNCAPPED_BALANCE = BigInt(1e30); // effectively disable estimator's internal cap
|
|
541
|
+
for (const seg of svmSegments) {
|
|
542
|
+
const estimated = yield (0, gas_calculator_1.estimateNativeValueForSwap)(ctx, universalCoreAddress, seg.gasToken, seg.gasFee, UNCAPPED_BALANCE);
|
|
543
|
+
const flat = perOutboundNativeValue !== null && perOutboundNativeValue !== void 0 ? perOutboundNativeValue : BigInt(0);
|
|
544
|
+
let value = estimated > flat ? estimated : flat;
|
|
545
|
+
if (perSvmCap > BigInt(0) && value > perSvmCap)
|
|
546
|
+
value = perSvmCap;
|
|
547
|
+
(0, context_1.printLog)(ctx, `composeCascade — SVM outbound to ${(_a = seg.hops[0]) === null || _a === void 0 ? void 0 : _a.targetChain}: gasFee=${seg.gasFee}, estimatedWpc=${estimated}, flat=${flat}, perSvmCap=${perSvmCap}, chosen=${value}`);
|
|
548
|
+
svmNativeValueBySegment.set(seg, value);
|
|
455
549
|
}
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
targetForOutbound = svmTarget.address;
|
|
468
|
-
}
|
|
469
|
-
else if (isMigration) {
|
|
470
|
-
// Migration: use raw 4-byte MIGRATION_SELECTOR, no multicall wrapping
|
|
471
|
-
outboundPayload = (0, payload_builders_1.buildMigrationPayload)();
|
|
472
|
-
targetForOutbound = (firstHop === null || firstHop === void 0 ? void 0 : firstHop.ceaAddress) || ueaAddress;
|
|
473
|
-
}
|
|
474
|
-
else {
|
|
475
|
-
// EVM: build CEA payload from merged multicalls
|
|
476
|
-
outboundPayload = (0, payload_builders_1.buildCeaMulticallPayload)(segment.mergedCeaMulticalls || []);
|
|
477
|
-
// Get CEA address from the first hop
|
|
478
|
-
targetForOutbound = (firstHop === null || firstHop === void 0 ? void 0 : firstHop.ceaAddress) || ueaAddress;
|
|
550
|
+
}
|
|
551
|
+
for (let i = segments.length - 1; i >= 0; i--) {
|
|
552
|
+
const segment = segments[i];
|
|
553
|
+
switch (segment.type) {
|
|
554
|
+
case 'PUSH_EXECUTION': {
|
|
555
|
+
// Prepend Push Chain multicalls to accumulated
|
|
556
|
+
accumulatedPushMulticalls = [
|
|
557
|
+
...(segment.mergedPushMulticalls || []),
|
|
558
|
+
...accumulatedPushMulticalls,
|
|
559
|
+
];
|
|
560
|
+
break;
|
|
479
561
|
}
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
const
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
562
|
+
case 'OUTBOUND_TO_CEA': {
|
|
563
|
+
const firstHop = segment.hops[0];
|
|
564
|
+
const isSvmSegment = (firstHop === null || firstHop === void 0 ? void 0 : firstHop.isSvmTarget) === true;
|
|
565
|
+
let outboundPayload;
|
|
566
|
+
let targetForOutbound;
|
|
567
|
+
const isMigration = (firstHop === null || firstHop === void 0 ? void 0 : firstHop.isMigration) === true;
|
|
568
|
+
if (isSvmSegment) {
|
|
569
|
+
// SVM: use the pre-built SVM payload from the hop descriptor
|
|
570
|
+
outboundPayload = (_b = firstHop.svmPayload) !== null && _b !== void 0 ? _b : '0x';
|
|
571
|
+
// For SVM, to.address IS the target (program for execute, recipient for withdraw)
|
|
572
|
+
const svmTarget = firstHop.params.to;
|
|
573
|
+
targetForOutbound = svmTarget.address;
|
|
574
|
+
}
|
|
575
|
+
else if (isMigration) {
|
|
576
|
+
// Migration: use raw 4-byte MIGRATION_SELECTOR, no multicall wrapping
|
|
577
|
+
outboundPayload = (0, payload_builders_1.buildMigrationPayload)();
|
|
578
|
+
targetForOutbound = (firstHop === null || firstHop === void 0 ? void 0 : firstHop.ceaAddress) || ueaAddress;
|
|
579
|
+
}
|
|
580
|
+
else {
|
|
581
|
+
// EVM: build CEA payload from merged multicalls
|
|
582
|
+
outboundPayload = (0, payload_builders_1.buildCeaMulticallPayload)(segment.mergedCeaMulticalls || []);
|
|
583
|
+
// Get CEA address from the first hop
|
|
584
|
+
targetForOutbound = (firstHop === null || firstHop === void 0 ? void 0 : firstHop.ceaAddress) || ueaAddress;
|
|
585
|
+
}
|
|
586
|
+
// Build outbound request
|
|
587
|
+
const outboundReq = (0, payload_builders_1.buildOutboundRequest)(targetForOutbound, segment.prc20Token || selectors_1.ZERO_ADDRESS, segment.totalBurnAmount || BigInt(0), (_c = segment.gasLimit) !== null && _c !== void 0 ? _c : BigInt(0), outboundPayload, ueaAddress);
|
|
588
|
+
// Build approval + outbound multicalls
|
|
589
|
+
const segGasFee = segment.gasFee || BigInt(0);
|
|
590
|
+
const svmOverride = svmNativeValueBySegment.get(segment);
|
|
591
|
+
// When UEA balance < CASCADE_GAS_RESERVE, perOutboundNativeValue is
|
|
592
|
+
// undefined. Mirror the drained-UEA branch in executeUoaToCea
|
|
593
|
+
// (route-handlers.ts): send the entire available balance divided
|
|
594
|
+
// across outbound segments. The contract refunds excess via
|
|
595
|
+
// swapAndBurnGas, so overshooting is safe; undershooting causes STF.
|
|
596
|
+
let evmFallbackValue;
|
|
597
|
+
if (ueaBalance && ueaBalance > BigInt(0)) {
|
|
598
|
+
evmFallbackValue = ueaBalance / BigInt(Math.max(numOutbounds, 1));
|
|
599
|
+
}
|
|
600
|
+
else {
|
|
601
|
+
evmFallbackValue = segGasFee * BigInt(1000000);
|
|
602
|
+
}
|
|
603
|
+
let nativeValueForGas = (_d = svmOverride !== null && svmOverride !== void 0 ? svmOverride : perOutboundNativeValue) !== null && _d !== void 0 ? _d : evmFallbackValue;
|
|
604
|
+
// SDK 5.2 Case C: when the segment's sizing is category C, compose
|
|
605
|
+
// R2/OUTBOUND_TO_CEA segments no longer apply Case A/B/C sizing
|
|
606
|
+
// (scoped to R1 + R3 only). segment.sizing may still be populated
|
|
607
|
+
// from the hop descriptor merge, but it is not consulted here —
|
|
608
|
+
// nativeValueForGas is driven by pool-quote on the single-hop path
|
|
609
|
+
// and by the cascade-level per-outbound allocation above.
|
|
610
|
+
const outboundMulticalls = (0, payload_builders_1.buildOutboundApprovalAndCall)({
|
|
611
|
+
prc20Token: segment.prc20Token || selectors_1.ZERO_ADDRESS,
|
|
612
|
+
gasToken: segment.gasToken || selectors_1.ZERO_ADDRESS,
|
|
613
|
+
burnAmount: segment.totalBurnAmount || BigInt(0),
|
|
614
|
+
gasFee: segGasFee,
|
|
615
|
+
nativeValueForGas,
|
|
616
|
+
gatewayPcAddress,
|
|
617
|
+
outboundRequest: outboundReq,
|
|
514
618
|
});
|
|
515
|
-
// Prepend
|
|
619
|
+
// Prepend to accumulated
|
|
516
620
|
accumulatedPushMulticalls = [
|
|
517
|
-
...
|
|
621
|
+
...outboundMulticalls,
|
|
518
622
|
...accumulatedPushMulticalls,
|
|
519
623
|
];
|
|
624
|
+
break;
|
|
520
625
|
}
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
//
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
626
|
+
case 'INBOUND_FROM_CEA': {
|
|
627
|
+
// The accumulated multicalls = what runs on Push Chain AFTER inbound arrives.
|
|
628
|
+
// Wrap in UniversalPayload struct with correct UEA nonce for the relay.
|
|
629
|
+
// Build push multicalls from EVERY merged hop's own data in original
|
|
630
|
+
// order (e.g. counter.increment() + value transfer). Multi-hop R3
|
|
631
|
+
// segments (same sourceChain) bundle all target ops into the single
|
|
632
|
+
// UEA multicall so no nested outbound is needed.
|
|
633
|
+
const mergedHopMulticalls = [];
|
|
634
|
+
for (const h of segment.hops) {
|
|
635
|
+
if (((_e = h === null || h === void 0 ? void 0 : h.params) === null || _e === void 0 ? void 0 : _e.data) || ((_f = h === null || h === void 0 ? void 0 : h.params) === null || _f === void 0 ? void 0 : _f.value)) {
|
|
636
|
+
const hopCalls = (0, payload_builders_1.buildExecuteMulticall)({
|
|
637
|
+
execute: {
|
|
638
|
+
to: h.params.to,
|
|
639
|
+
value: h.params.value,
|
|
640
|
+
data: h.params.data,
|
|
641
|
+
},
|
|
642
|
+
ueaAddress,
|
|
643
|
+
});
|
|
644
|
+
mergedHopMulticalls.push(...hopCalls);
|
|
645
|
+
}
|
|
535
646
|
}
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
647
|
+
if (mergedHopMulticalls.length > 0) {
|
|
648
|
+
accumulatedPushMulticalls = [
|
|
649
|
+
...mergedHopMulticalls,
|
|
650
|
+
...accumulatedPushMulticalls,
|
|
651
|
+
];
|
|
652
|
+
}
|
|
653
|
+
// Primary hop carries the sourceChain / CEA address (shared across merged hops).
|
|
654
|
+
const hop0 = segment.hops[0];
|
|
655
|
+
let intermediatePayload = '0x';
|
|
656
|
+
if (accumulatedPushMulticalls.length > 0) {
|
|
657
|
+
const multicallPayload = (0, payload_builder_1.buildMulticallPayloadData)(ctx, ueaAddress, accumulatedPushMulticalls);
|
|
658
|
+
// +1: the outbound tx consumes one nonce via execute()
|
|
659
|
+
intermediatePayload = (0, payload_builders_1.buildInboundUniversalPayload)(multicallPayload, { nonce: (ueaNonce !== null && ueaNonce !== void 0 ? ueaNonce : BigInt(0)) + BigInt(1) });
|
|
660
|
+
}
|
|
661
|
+
const hop = segment.hops[0];
|
|
662
|
+
const sourceChain = hop.sourceChain;
|
|
663
|
+
const ceaAddress = hop.ceaAddress || ueaAddress;
|
|
664
|
+
// SVM chains: build SVM CPI payload instead of EVM CEA multicall
|
|
665
|
+
if ((0, payload_builders_1.isSvmChain)(sourceChain)) {
|
|
666
|
+
const lockerContract = chain_1.CHAIN_INFO[sourceChain].lockerContract;
|
|
667
|
+
if (!lockerContract) {
|
|
668
|
+
throw new Error(`No SVM gateway program configured for chain ${sourceChain}`);
|
|
549
669
|
}
|
|
670
|
+
const programPk = new web3_js_1.PublicKey(lockerContract);
|
|
671
|
+
const gatewayProgramHex = ('0x' +
|
|
672
|
+
Buffer.from(programPk.toBytes()).toString('hex'));
|
|
673
|
+
let drainAmount = BigInt(0);
|
|
674
|
+
let tokenMintHex;
|
|
675
|
+
const params = hop.params;
|
|
676
|
+
if (((_g = params.funds) === null || _g === void 0 ? void 0 : _g.amount) && params.funds.amount > BigInt(0)) {
|
|
677
|
+
drainAmount = params.funds.amount;
|
|
678
|
+
const token = params.funds.token;
|
|
679
|
+
if (token && token.address) {
|
|
680
|
+
const mintPk = new web3_js_1.PublicKey(token.address);
|
|
681
|
+
tokenMintHex = ('0x' +
|
|
682
|
+
Buffer.from(mintPk.toBytes()).toString('hex'));
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
else if (params.value && params.value > BigInt(0)) {
|
|
686
|
+
drainAmount = params.value;
|
|
687
|
+
}
|
|
688
|
+
// Derive CEA PDA as revert recipient
|
|
689
|
+
const ueaBytes = Buffer.from(ueaAddress.slice(2), 'hex');
|
|
690
|
+
const [ceaPda] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from('push_identity'), ueaBytes], programPk);
|
|
691
|
+
const ceaPdaHex = ('0x' +
|
|
692
|
+
Buffer.from(ceaPda.toBytes()).toString('hex'));
|
|
693
|
+
// Build SVM payload with intermediate Push Chain payload embedded
|
|
694
|
+
const svmPayload = (0, payload_builders_1.encodeSvmCeaToUeaPayload)({
|
|
695
|
+
gatewayProgramHex,
|
|
696
|
+
drainAmount,
|
|
697
|
+
tokenMintHex,
|
|
698
|
+
revertRecipientHex: ceaPdaHex,
|
|
699
|
+
extraPayload: intermediatePayload !== '0x'
|
|
700
|
+
? new Uint8Array(Buffer.from(intermediatePayload.slice(2), 'hex'))
|
|
701
|
+
: undefined,
|
|
702
|
+
});
|
|
703
|
+
const svmBurnAmount = segment.totalBurnAmount || BigInt(0);
|
|
704
|
+
const outboundReq = (0, payload_builders_1.buildOutboundRequest)(gatewayProgramHex, segment.prc20Token ||
|
|
705
|
+
(0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork), svmBurnAmount, (_h = segment.gasLimit) !== null && _h !== void 0 ? _h : BigInt(0), svmPayload, ueaAddress);
|
|
706
|
+
const inboundGasFee = segment.gasFee || BigInt(0);
|
|
707
|
+
let svmInboundFallback;
|
|
708
|
+
if (ueaBalance && ueaBalance > BigInt(0)) {
|
|
709
|
+
svmInboundFallback = ueaBalance / BigInt(Math.max(numOutbounds, 1));
|
|
710
|
+
}
|
|
711
|
+
else {
|
|
712
|
+
svmInboundFallback = inboundGasFee * BigInt(1000000);
|
|
713
|
+
}
|
|
714
|
+
let svmInboundNativeValue = perOutboundNativeValue !== null && perOutboundNativeValue !== void 0 ? perOutboundNativeValue : svmInboundFallback;
|
|
715
|
+
// SDK 5.2 R3-style Case C bump: when the inbound segment's sizing
|
|
716
|
+
// is C, top up the swap budget by the overflow. No bridge-swap —
|
|
717
|
+
// R3 has no destination funds delivery.
|
|
718
|
+
if (((_j = segment.sizing) === null || _j === void 0 ? void 0 : _j.category) === 'C' &&
|
|
719
|
+
segment.sizing.overflowNativePc > BigInt(0)) {
|
|
720
|
+
const bumped = svmInboundNativeValue + segment.sizing.overflowNativePc;
|
|
721
|
+
(0, context_1.printLog)(ctx, `composeCascade — INBOUND_FROM_CEA (SVM) Case C: bumping nativeValueForGas from ${svmInboundNativeValue} to ${bumped} (overflow=${segment.sizing.overflowNativePc})`);
|
|
722
|
+
svmInboundNativeValue = bumped;
|
|
723
|
+
}
|
|
724
|
+
const outboundMulticalls = (0, payload_builders_1.buildOutboundApprovalAndCall)({
|
|
725
|
+
prc20Token: segment.prc20Token ||
|
|
726
|
+
(0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork),
|
|
727
|
+
gasToken: segment.gasToken || selectors_1.ZERO_ADDRESS,
|
|
728
|
+
burnAmount: svmBurnAmount,
|
|
729
|
+
gasFee: inboundGasFee,
|
|
730
|
+
nativeValueForGas: svmInboundNativeValue,
|
|
731
|
+
gatewayPcAddress,
|
|
732
|
+
outboundRequest: outboundReq,
|
|
733
|
+
});
|
|
734
|
+
accumulatedPushMulticalls = [...outboundMulticalls];
|
|
735
|
+
break;
|
|
550
736
|
}
|
|
551
|
-
|
|
552
|
-
|
|
737
|
+
// EVM path: Build CEA multicall: [approve?, sendUniversalTxFromCEA(payload)]
|
|
738
|
+
const ceaMulticalls = [];
|
|
739
|
+
// Add primary hop's own CEA operations if any
|
|
740
|
+
// (e.g., approve + swap before bridging back)
|
|
741
|
+
if (hop.ceaMulticalls && hop.ceaMulticalls.length > 0) {
|
|
742
|
+
ceaMulticalls.push(...hop.ceaMulticalls);
|
|
553
743
|
}
|
|
554
|
-
//
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
744
|
+
// Aggregate token/amount across all merged hops so the single
|
|
745
|
+
// sendUniversalTxToUEA call carries the combined forward amount.
|
|
746
|
+
// For multi-hop R3 segments (same sourceChain), ERC20 funds across
|
|
747
|
+
// hops must resolve to the same token — otherwise this path can't
|
|
748
|
+
// express it in one call and the caller should split manually.
|
|
749
|
+
let tokenAddress = selectors_1.ZERO_ADDRESS;
|
|
750
|
+
let amount = BigInt(0);
|
|
751
|
+
for (const mergedHop of segment.hops) {
|
|
752
|
+
const hParams = mergedHop.params;
|
|
753
|
+
if ((_k = hParams.funds) === null || _k === void 0 ? void 0 : _k.amount) {
|
|
754
|
+
const token = hParams.funds.token;
|
|
755
|
+
if (token) {
|
|
756
|
+
if (token.mechanism === 'native') {
|
|
757
|
+
amount += hParams.funds.amount;
|
|
758
|
+
}
|
|
759
|
+
else {
|
|
760
|
+
const addr = token.address;
|
|
761
|
+
if (tokenAddress !== selectors_1.ZERO_ADDRESS &&
|
|
762
|
+
tokenAddress.toLowerCase() !== addr.toLowerCase()) {
|
|
763
|
+
throw new Error(`composeCascade: merged R3 hops target different ERC20 tokens (${tokenAddress} vs ${addr}); split into separate executeTransactions batches`);
|
|
764
|
+
}
|
|
765
|
+
tokenAddress = addr;
|
|
766
|
+
amount += hParams.funds.amount;
|
|
767
|
+
// CEA approves gateway once with the cumulative allowance.
|
|
768
|
+
const gatewayAddr = chain_1.UNIVERSAL_GATEWAY_ADDRESSES[sourceChain];
|
|
769
|
+
if (!gatewayAddr) {
|
|
770
|
+
throw new Error(`No UniversalGateway address configured for chain ${sourceChain}`);
|
|
771
|
+
}
|
|
772
|
+
// Reset allowance to 0 first for USDT-style tokens
|
|
773
|
+
const approveZeroData = (0, viem_1.encodeFunctionData)({
|
|
774
|
+
abi: erc20_evm_1.ERC20_EVM,
|
|
775
|
+
functionName: 'approve',
|
|
776
|
+
args: [gatewayAddr, BigInt(0)],
|
|
777
|
+
});
|
|
778
|
+
ceaMulticalls.push({
|
|
779
|
+
to: tokenAddress,
|
|
780
|
+
value: BigInt(0),
|
|
781
|
+
data: approveZeroData,
|
|
782
|
+
});
|
|
783
|
+
const approveData = (0, viem_1.encodeFunctionData)({
|
|
784
|
+
abi: erc20_evm_1.ERC20_EVM,
|
|
785
|
+
functionName: 'approve',
|
|
786
|
+
args: [gatewayAddr, hParams.funds.amount],
|
|
787
|
+
});
|
|
788
|
+
ceaMulticalls.push({
|
|
789
|
+
to: tokenAddress,
|
|
790
|
+
value: BigInt(0),
|
|
791
|
+
data: approveData,
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
else if (hParams.value && hParams.value > BigInt(0)) {
|
|
797
|
+
amount += hParams.value;
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
// Build sendUniversalTxToUEA self-call on CEA (to=CEA, value=0)
|
|
801
|
+
const sendCall = (0, payload_builders_1.buildSendUniversalTxToUEA)(ceaAddress, tokenAddress, amount, intermediatePayload, ceaAddress);
|
|
802
|
+
ceaMulticalls.push(sendCall);
|
|
803
|
+
// Wrap CEA multicall in outbound from Push Chain
|
|
804
|
+
const ceaPayload = (0, payload_builders_1.buildCeaMulticallPayload)(ceaMulticalls);
|
|
805
|
+
// The nested outbound back to the source chain needs a non-zero
|
|
806
|
+
// gasLimit — UGPC.sendUniversalTxOutbound reverts with
|
|
807
|
+
// "Missing or invalid parameters" on 0. When the hop didn't supply
|
|
808
|
+
// one (typical for a pure-R3 leg composed inside a cascade), fall
|
|
809
|
+
// back to DEFAULT_OUTBOUND_GAS_LIMIT just like the initial outbound
|
|
810
|
+
// path does.
|
|
811
|
+
const effectiveGasLimit = segment.gasLimit && segment.gasLimit > BigInt(0)
|
|
812
|
+
? segment.gasLimit
|
|
813
|
+
: selectors_1.DEFAULT_OUTBOUND_GAS_LIMIT;
|
|
814
|
+
const outboundReq = (0, payload_builders_1.buildOutboundRequest)(ceaAddress, segment.prc20Token ||
|
|
815
|
+
(0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork), segment.totalBurnAmount || BigInt(0), effectiveGasLimit, ceaPayload, ueaAddress);
|
|
572
816
|
const inboundGasFee = segment.gasFee || BigInt(0);
|
|
817
|
+
let evmInboundFallback;
|
|
818
|
+
if (ueaBalance && ueaBalance > BigInt(0)) {
|
|
819
|
+
evmInboundFallback = ueaBalance / BigInt(Math.max(numOutbounds, 1));
|
|
820
|
+
}
|
|
821
|
+
else {
|
|
822
|
+
evmInboundFallback = inboundGasFee * BigInt(1000000);
|
|
823
|
+
}
|
|
824
|
+
let evmInboundNativeValue = perOutboundNativeValue !== null && perOutboundNativeValue !== void 0 ? perOutboundNativeValue : evmInboundFallback;
|
|
825
|
+
// SDK 5.2 R3-style Case C bump: when the inbound segment's sizing
|
|
826
|
+
// is C, top up the swap budget by the overflow. No bridge-swap —
|
|
827
|
+
// R3 has no destination funds delivery.
|
|
828
|
+
if (((_l = segment.sizing) === null || _l === void 0 ? void 0 : _l.category) === 'C' &&
|
|
829
|
+
segment.sizing.overflowNativePc > BigInt(0)) {
|
|
830
|
+
const bumped = evmInboundNativeValue + segment.sizing.overflowNativePc;
|
|
831
|
+
(0, context_1.printLog)(ctx, `composeCascade — INBOUND_FROM_CEA (EVM) Case C: bumping nativeValueForGas from ${evmInboundNativeValue} to ${bumped} (overflow=${segment.sizing.overflowNativePc})`);
|
|
832
|
+
evmInboundNativeValue = bumped;
|
|
833
|
+
}
|
|
573
834
|
const outboundMulticalls = (0, payload_builders_1.buildOutboundApprovalAndCall)({
|
|
574
835
|
prc20Token: segment.prc20Token ||
|
|
575
836
|
(0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork),
|
|
576
837
|
gasToken: segment.gasToken || selectors_1.ZERO_ADDRESS,
|
|
577
|
-
burnAmount:
|
|
838
|
+
burnAmount: segment.totalBurnAmount || BigInt(0),
|
|
578
839
|
gasFee: inboundGasFee,
|
|
579
|
-
nativeValueForGas:
|
|
840
|
+
nativeValueForGas: evmInboundNativeValue,
|
|
580
841
|
gatewayPcAddress,
|
|
581
842
|
outboundRequest: outboundReq,
|
|
582
843
|
});
|
|
844
|
+
// Reset accumulated -- everything is now inside this outbound
|
|
583
845
|
accumulatedPushMulticalls = [...outboundMulticalls];
|
|
584
846
|
break;
|
|
585
847
|
}
|
|
586
|
-
// EVM path: Build CEA multicall: [approve?, sendUniversalTxFromCEA(payload)]
|
|
587
|
-
const ceaMulticalls = [];
|
|
588
|
-
// Add hop's own CEA operations if any
|
|
589
|
-
// (e.g., approve + swap before bridging back)
|
|
590
|
-
if (hop.ceaMulticalls && hop.ceaMulticalls.length > 0) {
|
|
591
|
-
ceaMulticalls.push(...hop.ceaMulticalls);
|
|
592
|
-
}
|
|
593
|
-
// Determine token/amount for inbound
|
|
594
|
-
let tokenAddress = selectors_1.ZERO_ADDRESS;
|
|
595
|
-
let amount = BigInt(0);
|
|
596
|
-
const params = hop.params;
|
|
597
|
-
if ((_f = params.funds) === null || _f === void 0 ? void 0 : _f.amount) {
|
|
598
|
-
const token = params.funds.token;
|
|
599
|
-
if (token) {
|
|
600
|
-
if (token.mechanism === 'native') {
|
|
601
|
-
amount = params.funds.amount;
|
|
602
|
-
}
|
|
603
|
-
else {
|
|
604
|
-
tokenAddress = token.address;
|
|
605
|
-
amount = params.funds.amount;
|
|
606
|
-
// Add approve for ERC20 (CEA approves gateway)
|
|
607
|
-
const gatewayAddr = chain_1.UNIVERSAL_GATEWAY_ADDRESSES[sourceChain];
|
|
608
|
-
if (!gatewayAddr) {
|
|
609
|
-
throw new Error(`No UniversalGateway address configured for chain ${sourceChain}`);
|
|
610
|
-
}
|
|
611
|
-
// Reset allowance to 0 first for USDT-style tokens
|
|
612
|
-
const approveZeroData = (0, viem_1.encodeFunctionData)({
|
|
613
|
-
abi: erc20_evm_1.ERC20_EVM,
|
|
614
|
-
functionName: 'approve',
|
|
615
|
-
args: [gatewayAddr, BigInt(0)],
|
|
616
|
-
});
|
|
617
|
-
ceaMulticalls.push({
|
|
618
|
-
to: tokenAddress,
|
|
619
|
-
value: BigInt(0),
|
|
620
|
-
data: approveZeroData,
|
|
621
|
-
});
|
|
622
|
-
const approveData = (0, viem_1.encodeFunctionData)({
|
|
623
|
-
abi: erc20_evm_1.ERC20_EVM,
|
|
624
|
-
functionName: 'approve',
|
|
625
|
-
args: [gatewayAddr, amount],
|
|
626
|
-
});
|
|
627
|
-
ceaMulticalls.push({
|
|
628
|
-
to: tokenAddress,
|
|
629
|
-
value: BigInt(0),
|
|
630
|
-
data: approveData,
|
|
631
|
-
});
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
else if (params.value && params.value > BigInt(0)) {
|
|
636
|
-
amount = params.value;
|
|
637
|
-
}
|
|
638
|
-
// Build sendUniversalTxToUEA self-call on CEA (to=CEA, value=0)
|
|
639
|
-
const sendCall = (0, payload_builders_1.buildSendUniversalTxToUEA)(ceaAddress, tokenAddress, amount, intermediatePayload, ceaAddress);
|
|
640
|
-
ceaMulticalls.push(sendCall);
|
|
641
|
-
// Wrap CEA multicall in outbound from Push Chain
|
|
642
|
-
const ceaPayload = (0, payload_builders_1.buildCeaMulticallPayload)(ceaMulticalls);
|
|
643
|
-
const outboundReq = (0, payload_builders_1.buildOutboundRequest)(ceaAddress, segment.prc20Token ||
|
|
644
|
-
(0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork), segment.totalBurnAmount || BigInt(0), (_g = segment.gasLimit) !== null && _g !== void 0 ? _g : BigInt(0), ceaPayload, ueaAddress);
|
|
645
|
-
const inboundGasFee = segment.gasFee || BigInt(0);
|
|
646
|
-
const outboundMulticalls = (0, payload_builders_1.buildOutboundApprovalAndCall)({
|
|
647
|
-
prc20Token: segment.prc20Token ||
|
|
648
|
-
(0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork),
|
|
649
|
-
gasToken: segment.gasToken || selectors_1.ZERO_ADDRESS,
|
|
650
|
-
burnAmount: segment.totalBurnAmount || BigInt(0),
|
|
651
|
-
gasFee: inboundGasFee,
|
|
652
|
-
nativeValueForGas: perOutboundNativeValue !== null && perOutboundNativeValue !== void 0 ? perOutboundNativeValue : inboundGasFee * BigInt(1000),
|
|
653
|
-
gatewayPcAddress,
|
|
654
|
-
outboundRequest: outboundReq,
|
|
655
|
-
});
|
|
656
|
-
// Reset accumulated -- everything is now inside this outbound
|
|
657
|
-
accumulatedPushMulticalls = [...outboundMulticalls];
|
|
658
|
-
break;
|
|
659
848
|
}
|
|
660
849
|
}
|
|
661
|
-
|
|
662
|
-
|
|
850
|
+
return accumulatedPushMulticalls;
|
|
851
|
+
});
|
|
663
852
|
}
|
|
664
853
|
// ============================================================================
|
|
665
854
|
// createCascadedBuilder
|
|
@@ -752,7 +941,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks) {
|
|
|
752
941
|
const ueaNonceCascade = ueaCodeCascade !== undefined
|
|
753
942
|
? yield (0, uea_manager_1.getUEANonce)(ctx, ueaAddress)
|
|
754
943
|
: BigInt(0);
|
|
755
|
-
const composedMulticalls = composeCascade(ctx, segments, ueaAddress, ueaBalance, ueaNonceCascade);
|
|
944
|
+
const composedMulticalls = yield composeCascade(ctx, segments, ueaAddress, ueaBalance, ueaNonceCascade);
|
|
756
945
|
// Execute the composed multicall as a single Push Chain tx
|
|
757
946
|
const executeParams = {
|
|
758
947
|
to: ueaAddress,
|
|
@@ -777,14 +966,77 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks) {
|
|
|
777
966
|
hops: hopInfos,
|
|
778
967
|
hopCount: hops.length,
|
|
779
968
|
waitForAll: (opts) => tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
780
|
-
var _a, _b;
|
|
969
|
+
var _a, _b, _c, _d, _e;
|
|
781
970
|
const { pollingIntervalMs = 10000, timeout = 300000, progressHook: cascadeProgressHook, eventHook, } = opts || {};
|
|
782
971
|
const startTime = Date.now();
|
|
972
|
+
// Per-hop progress emission. Drops events whose id is '' (the no-op
|
|
973
|
+
// sentinel returned by pickWaitHooks for R4/unknown routes), so
|
|
974
|
+
// consumers don't see placeholder events while the cascade still
|
|
975
|
+
// drives its own state machine.
|
|
976
|
+
const emitHopEvent = (hook, event) => {
|
|
977
|
+
if (!hook || !event.id)
|
|
978
|
+
return;
|
|
979
|
+
hook(event);
|
|
980
|
+
};
|
|
981
|
+
// ---- Multichain (multi-hop) cascade markers (001 / 002 / 999) ----
|
|
982
|
+
// Only fire when this is genuinely a multi-hop cascade. Single-hop
|
|
983
|
+
// R1/R2 takes the early-return paths above and never reaches here,
|
|
984
|
+
// but guard explicitly in case someone wires a 1-element prepared list.
|
|
985
|
+
const totalHops = hopInfos.length;
|
|
986
|
+
const isMulti = totalHops > 1;
|
|
987
|
+
const chainLabels = hops.map((h) => {
|
|
988
|
+
var _a, _b;
|
|
989
|
+
return String((_b = (_a = h.targetChain) !== null && _a !== void 0 ? _a : h.sourceChain) !== null && _b !== void 0 ? _b : (0, helpers_1.getPushChainForNetwork)(ctx.pushNetwork));
|
|
990
|
+
});
|
|
991
|
+
const signerOriginChain = String((_a = ctx.universalSigner.account.chain) !== null && _a !== void 0 ? _a : 'origin');
|
|
992
|
+
const emitHopStart = (hopIndex0) => {
|
|
993
|
+
if (!isMulti)
|
|
994
|
+
return;
|
|
995
|
+
const n = hopIndex0 + 1;
|
|
996
|
+
const fromChain = hopIndex0 === 0
|
|
997
|
+
? signerOriginChain
|
|
998
|
+
: chainLabels[hopIndex0 - 1];
|
|
999
|
+
const toChain = chainLabels[hopIndex0];
|
|
1000
|
+
emitHopEvent(eventHook, progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_002_01](n, totalHops, fromChain, toChain));
|
|
1001
|
+
};
|
|
1002
|
+
const emitHopComplete = (hopIndex0) => {
|
|
1003
|
+
if (!isMulti)
|
|
1004
|
+
return;
|
|
1005
|
+
const n = hopIndex0 + 1;
|
|
1006
|
+
if (n >= totalHops)
|
|
1007
|
+
return; // skip after final hop — 999-01 takes over
|
|
1008
|
+
emitHopEvent(eventHook, progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_002_99_99](n, totalHops));
|
|
1009
|
+
};
|
|
1010
|
+
const emitCascadeSuccess = () => {
|
|
1011
|
+
if (!isMulti)
|
|
1012
|
+
return;
|
|
1013
|
+
emitHopEvent(eventHook, progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_999_01](totalHops));
|
|
1014
|
+
};
|
|
1015
|
+
const emitCascadeFailed = (failedIdx0, errMsg) => {
|
|
1016
|
+
if (!isMulti)
|
|
1017
|
+
return;
|
|
1018
|
+
emitHopEvent(eventHook, progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_999_02](failedIdx0 + 1, totalHops, errMsg));
|
|
1019
|
+
};
|
|
1020
|
+
const emitCascadeTimeout = (failedIdx0) => {
|
|
1021
|
+
if (!isMulti)
|
|
1022
|
+
return;
|
|
1023
|
+
emitHopEvent(eventHook, progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_999_03](failedIdx0 + 1, totalHops));
|
|
1024
|
+
};
|
|
1025
|
+
if (isMulti) {
|
|
1026
|
+
emitHopEvent(eventHook, progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_001](totalHops, chainLabels));
|
|
1027
|
+
}
|
|
783
1028
|
try {
|
|
784
1029
|
// 1. Wait for initial Push Chain tx confirmation
|
|
1030
|
+
// Emit 002-01 for hop 0 ONLY when the first hop is a Push (R1) tx —
|
|
1031
|
+
// its "start" coincides with submitting the composed Push tx. For
|
|
1032
|
+
// cascades that begin with R2/R3, the per-outbound/inbound branches
|
|
1033
|
+
// below own the emitHopStart for hop 0 to avoid a duplicate marker.
|
|
1034
|
+
if (((_b = hopInfos[0]) === null || _b === void 0 ? void 0 : _b.route) === 'UOA_TO_PUSH') {
|
|
1035
|
+
emitHopStart(0);
|
|
1036
|
+
}
|
|
785
1037
|
cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
|
|
786
1038
|
hopIndex: 0,
|
|
787
|
-
route: ((
|
|
1039
|
+
route: ((_c = hopInfos[0]) === null || _c === void 0 ? void 0 : _c.route) || 'UOA_TO_PUSH',
|
|
788
1040
|
chain: (0, helpers_1.getPushChainForNetwork)(ctx.pushNetwork),
|
|
789
1041
|
status: 'waiting',
|
|
790
1042
|
elapsed: Date.now() - startTime,
|
|
@@ -803,6 +1055,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks) {
|
|
|
803
1055
|
txHash: response.hash,
|
|
804
1056
|
elapsed: Date.now() - startTime,
|
|
805
1057
|
});
|
|
1058
|
+
emitHopComplete(hop.hopIndex);
|
|
806
1059
|
}
|
|
807
1060
|
}
|
|
808
1061
|
// 2. Track outbound hops (Route 2: UOA_TO_CEA)
|
|
@@ -833,6 +1086,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks) {
|
|
|
833
1086
|
if (outboundHops.length === 1) {
|
|
834
1087
|
// Single direct outbound hop: use the existing V1-based tracking
|
|
835
1088
|
const hop = outboundHops[0];
|
|
1089
|
+
const hopHooks = (0, progress_route_hooks_1.pickWaitHooks)(hop.route);
|
|
836
1090
|
const remainingTimeout = timeout - (Date.now() - startTime);
|
|
837
1091
|
if (remainingTimeout <= 0) {
|
|
838
1092
|
hop.status = 'failed';
|
|
@@ -843,14 +1097,16 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks) {
|
|
|
843
1097
|
status: 'timeout',
|
|
844
1098
|
elapsed: Date.now() - startTime,
|
|
845
1099
|
});
|
|
846
|
-
eventHook
|
|
1100
|
+
emitHopEvent(eventHook, hopHooks.timeout(hop.executionChain, Date.now() - startTime));
|
|
1101
|
+
emitCascadeTimeout(hop.hopIndex);
|
|
847
1102
|
return {
|
|
848
1103
|
success: false,
|
|
849
1104
|
hops: hopInfos,
|
|
850
1105
|
failedAt: hop.hopIndex,
|
|
851
1106
|
};
|
|
852
1107
|
}
|
|
853
|
-
|
|
1108
|
+
emitHopStart(hop.hopIndex);
|
|
1109
|
+
emitHopEvent(eventHook, hopHooks.awaiting(hop.executionChain));
|
|
854
1110
|
cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
|
|
855
1111
|
hopIndex: hop.hopIndex,
|
|
856
1112
|
route: hop.route,
|
|
@@ -875,14 +1131,15 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks) {
|
|
|
875
1131
|
if (event.status === 'polling' &&
|
|
876
1132
|
lastEmittedStatus !== 'polling') {
|
|
877
1133
|
lastEmittedStatus = 'polling';
|
|
878
|
-
eventHook
|
|
1134
|
+
emitHopEvent(eventHook, hopHooks.polling(hop.executionChain, event.elapsed));
|
|
879
1135
|
}
|
|
880
1136
|
},
|
|
881
1137
|
});
|
|
882
1138
|
hop.status = 'confirmed';
|
|
883
1139
|
hop.txHash = outboundDetails.externalTxHash;
|
|
884
1140
|
hop.outboundDetails = outboundDetails;
|
|
885
|
-
eventHook
|
|
1141
|
+
emitHopEvent(eventHook, hopHooks.success(outboundDetails));
|
|
1142
|
+
emitHopComplete(hop.hopIndex);
|
|
886
1143
|
cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
|
|
887
1144
|
hopIndex: hop.hopIndex,
|
|
888
1145
|
route: hop.route,
|
|
@@ -895,9 +1152,15 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks) {
|
|
|
895
1152
|
catch (err) {
|
|
896
1153
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
897
1154
|
const isTimeout = errMsg.startsWith('Timeout waiting for outbound transaction');
|
|
898
|
-
eventHook
|
|
899
|
-
?
|
|
900
|
-
:
|
|
1155
|
+
emitHopEvent(eventHook, isTimeout
|
|
1156
|
+
? hopHooks.timeout(hop.executionChain, remainingTimeout)
|
|
1157
|
+
: hopHooks.failed(hop.executionChain, errMsg));
|
|
1158
|
+
if (isTimeout) {
|
|
1159
|
+
emitCascadeTimeout(hop.hopIndex);
|
|
1160
|
+
}
|
|
1161
|
+
else {
|
|
1162
|
+
emitCascadeFailed(hop.hopIndex, errMsg);
|
|
1163
|
+
}
|
|
901
1164
|
hop.status = 'failed';
|
|
902
1165
|
cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
|
|
903
1166
|
hopIndex: hop.hopIndex,
|
|
@@ -916,9 +1179,10 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks) {
|
|
|
916
1179
|
else {
|
|
917
1180
|
// Multiple outbound hops: use V2 API which returns outboundTx[]
|
|
918
1181
|
// Emit "Awaiting External Chain" once per outbound hop before
|
|
919
|
-
// polling begins.
|
|
1182
|
+
// polling begins, using the route-specific awaiting hook.
|
|
920
1183
|
for (const outHop of outboundHops) {
|
|
921
|
-
|
|
1184
|
+
emitHopStart(outHop.hopIndex);
|
|
1185
|
+
emitHopEvent(eventHook, (0, progress_route_hooks_1.pickWaitHooks)(outHop.route).awaiting(outHop.executionChain));
|
|
922
1186
|
}
|
|
923
1187
|
const perHopLastStatus = new Map();
|
|
924
1188
|
const confirmedHops = new Set();
|
|
@@ -927,6 +1191,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks) {
|
|
|
927
1191
|
pollingIntervalMs,
|
|
928
1192
|
timeout: timeout - (Date.now() - startTime),
|
|
929
1193
|
progressHook: (event) => {
|
|
1194
|
+
var _a, _b;
|
|
930
1195
|
cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
|
|
931
1196
|
hopIndex: event.hopIndex,
|
|
932
1197
|
route: event.route,
|
|
@@ -935,11 +1200,13 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks) {
|
|
|
935
1200
|
txHash: event.txHash,
|
|
936
1201
|
elapsed: Date.now() - startTime,
|
|
937
1202
|
});
|
|
1203
|
+
const matchedHop = outboundHops.find((h) => h.hopIndex === event.hopIndex);
|
|
1204
|
+
const eventHooks = (0, progress_route_hooks_1.pickWaitHooks)(matchedHop === null || matchedHop === void 0 ? void 0 : matchedHop.route);
|
|
938
1205
|
const prev = perHopLastStatus.get(event.hopIndex);
|
|
939
1206
|
if (event.status === 'polling' &&
|
|
940
1207
|
prev !== 'polling') {
|
|
941
1208
|
perHopLastStatus.set(event.hopIndex, 'polling');
|
|
942
|
-
eventHook === null ||
|
|
1209
|
+
emitHopEvent(eventHook, 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));
|
|
943
1210
|
}
|
|
944
1211
|
if (event.status === 'confirmed' &&
|
|
945
1212
|
event.txHash &&
|
|
@@ -949,21 +1216,22 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks) {
|
|
|
949
1216
|
// outbound-sync.ts:348-355). Auto-promotion paths
|
|
950
1217
|
// without a txHash are skipped by the `event.txHash`
|
|
951
1218
|
// guard above, so we only reach here with full details.
|
|
952
|
-
const matchedHop = outboundHops.find((h) => h.hopIndex === event.hopIndex);
|
|
953
1219
|
if (matchedHop === null || matchedHop === void 0 ? void 0 : matchedHop.outboundDetails) {
|
|
954
1220
|
confirmedHops.add(event.hopIndex);
|
|
955
|
-
eventHook
|
|
1221
|
+
emitHopEvent(eventHook, eventHooks.success(matchedHop.outboundDetails));
|
|
1222
|
+
emitHopComplete(event.hopIndex);
|
|
956
1223
|
}
|
|
957
1224
|
}
|
|
958
1225
|
},
|
|
959
1226
|
});
|
|
960
1227
|
if (!allOutboundDetails.success) {
|
|
961
|
-
const failedIdx = (
|
|
1228
|
+
const failedIdx = (_d = allOutboundDetails.failedAt) !== null && _d !== void 0 ? _d : 0;
|
|
962
1229
|
const failedHop = outboundHops.find((h) => h.hopIndex === failedIdx);
|
|
963
1230
|
const failMsg = failedHop
|
|
964
1231
|
? `Outbound failed for hop ${failedIdx} on ${failedHop.executionChain}`
|
|
965
1232
|
: `Outbound cascade failed at hop ${failedIdx}`;
|
|
966
|
-
eventHook === null ||
|
|
1233
|
+
emitHopEvent(eventHook, (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));
|
|
1234
|
+
emitCascadeFailed(failedIdx, failMsg);
|
|
967
1235
|
return {
|
|
968
1236
|
success: false,
|
|
969
1237
|
hops: hopInfos,
|
|
@@ -973,9 +1241,13 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks) {
|
|
|
973
1241
|
}
|
|
974
1242
|
}
|
|
975
1243
|
// 3. Route 3 (CEA_TO_PUSH) tracking - mark as submitted
|
|
1244
|
+
// Cascade does not poll inbound completion; we surface the hop-start
|
|
1245
|
+
// marker so consumers see a "Starting tx N" event for the inbound,
|
|
1246
|
+
// but skip emitHopComplete since we never observe terminal state.
|
|
976
1247
|
const inboundHops = hopInfos.filter((h) => h.route === 'CEA_TO_PUSH');
|
|
977
1248
|
for (const inboundHop of inboundHops) {
|
|
978
1249
|
inboundHop.status = 'submitted';
|
|
1250
|
+
emitHopStart(inboundHop.hopIndex);
|
|
979
1251
|
cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
|
|
980
1252
|
hopIndex: inboundHop.hopIndex,
|
|
981
1253
|
route: inboundHop.route,
|
|
@@ -984,14 +1256,24 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks) {
|
|
|
984
1256
|
elapsed: Date.now() - startTime,
|
|
985
1257
|
});
|
|
986
1258
|
}
|
|
1259
|
+
emitCascadeSuccess();
|
|
987
1260
|
return { success: true, hops: hopInfos };
|
|
988
1261
|
}
|
|
989
1262
|
catch (err) {
|
|
990
1263
|
const failedIdx = hopInfos.findIndex((h) => h.status !== 'confirmed');
|
|
1264
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
1265
|
+
const failedAt = failedIdx >= 0 ? failedIdx : 0;
|
|
1266
|
+
const isTimeout = errMsg.startsWith('Timeout');
|
|
1267
|
+
if (isTimeout) {
|
|
1268
|
+
emitCascadeTimeout(failedAt);
|
|
1269
|
+
}
|
|
1270
|
+
else {
|
|
1271
|
+
emitCascadeFailed(failedAt, errMsg);
|
|
1272
|
+
}
|
|
991
1273
|
return {
|
|
992
1274
|
success: false,
|
|
993
1275
|
hops: hopInfos,
|
|
994
|
-
failedAt
|
|
1276
|
+
failedAt,
|
|
995
1277
|
};
|
|
996
1278
|
}
|
|
997
1279
|
}),
|