@pushchain/core 5.1.5-alpha.0 → 5.1.5
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 +19 -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 +556 -225
- 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 +54 -0
- package/src/lib/orchestrator/internals/execute-funds-only.js +47 -21
- package/src/lib/orchestrator/internals/execute-funds-payload.js +53 -31
- 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 +252 -53
- 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 +120 -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 +555 -101
- package/src/lib/progress-hook/progress-hook.types.d.ts +76 -24
- package/src/lib/progress-hook/progress-hook.types.js +74 -29
- 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,11 +22,16 @@ 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");
|
|
32
|
+
const progress_route_hooks_1 = require("./progress-route-hooks");
|
|
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");
|
|
30
35
|
const build_payload_1 = require("../svm-idl/build-payload");
|
|
31
36
|
// ============================================================================
|
|
32
37
|
// prepareTransaction
|
|
@@ -131,21 +136,29 @@ function buildHopDescriptor(ctx, params, route, ueaAddress) {
|
|
|
131
136
|
burnAmount = params.value;
|
|
132
137
|
}
|
|
133
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.
|
|
134
144
|
prc20Token = (0, helpers_1.getNativePRC20ForChain)(targetChain, ctx.pushNetwork);
|
|
135
|
-
burnAmount = BigInt(
|
|
145
|
+
burnAmount = BigInt(0);
|
|
136
146
|
}
|
|
137
147
|
let gasToken = selectors_1.ZERO_ADDRESS;
|
|
138
148
|
let gasFee = BigInt(0);
|
|
149
|
+
let sizing;
|
|
139
150
|
if (prc20Token !== selectors_1.ZERO_ADDRESS) {
|
|
140
|
-
const result = yield (0, gas_calculator_1.queryOutboundGasFee)(ctx, prc20Token, gasLimit);
|
|
151
|
+
const result = yield (0, gas_calculator_1.queryOutboundGasFee)(ctx, prc20Token, gasLimit, targetChain);
|
|
141
152
|
gasToken = result.gasToken;
|
|
142
153
|
gasFee = result.gasFee;
|
|
154
|
+
sizing = result.sizing;
|
|
143
155
|
}
|
|
144
156
|
return Object.assign(Object.assign({}, baseDescriptor), { targetChain, isSvmTarget: true, svmPayload,
|
|
145
157
|
prc20Token,
|
|
146
158
|
burnAmount,
|
|
147
159
|
gasToken,
|
|
148
|
-
gasFee
|
|
160
|
+
gasFee,
|
|
161
|
+
sizing });
|
|
149
162
|
}
|
|
150
163
|
// EVM path: Resolve CEA address + build CEA multicalls
|
|
151
164
|
const { cea: ceaAddress } = yield (0, cea_utils_1.getCEAAddress)(ueaAddress, targetChain, (_c = ctx.rpcUrls[targetChain]) === null || _c === void 0 ? void 0 : _c[0]);
|
|
@@ -155,16 +168,18 @@ function buildHopDescriptor(ctx, params, route, ueaAddress) {
|
|
|
155
168
|
const burnAmount = BigInt(0); // Migration is logic-only — no funds. CEA rejects msg.value != 0.
|
|
156
169
|
let gasToken = selectors_1.ZERO_ADDRESS;
|
|
157
170
|
let gasFee = BigInt(0);
|
|
171
|
+
let sizing;
|
|
158
172
|
if (prc20Token !== selectors_1.ZERO_ADDRESS) {
|
|
159
|
-
const result = yield (0, gas_calculator_1.queryOutboundGasFee)(ctx, prc20Token, gasLimit);
|
|
173
|
+
const result = yield (0, gas_calculator_1.queryOutboundGasFee)(ctx, prc20Token, gasLimit, targetChain);
|
|
160
174
|
gasToken = result.gasToken;
|
|
161
175
|
gasFee = result.gasFee;
|
|
176
|
+
sizing = result.sizing;
|
|
162
177
|
}
|
|
163
178
|
return Object.assign(Object.assign({}, baseDescriptor), { targetChain,
|
|
164
179
|
ceaAddress, ceaMulticalls: [], prc20Token,
|
|
165
180
|
burnAmount,
|
|
166
181
|
gasToken,
|
|
167
|
-
gasFee, isMigration: true });
|
|
182
|
+
gasFee, isMigration: true, sizing });
|
|
168
183
|
}
|
|
169
184
|
// Build CEA multicalls
|
|
170
185
|
const ceaMulticalls = [];
|
|
@@ -238,10 +253,12 @@ function buildHopDescriptor(ctx, params, route, ueaAddress) {
|
|
|
238
253
|
// Query gas fee
|
|
239
254
|
let gasToken = selectors_1.ZERO_ADDRESS;
|
|
240
255
|
let gasFee = BigInt(0);
|
|
256
|
+
let sizing;
|
|
241
257
|
if (prc20Token !== selectors_1.ZERO_ADDRESS) {
|
|
242
|
-
const result = yield (0, gas_calculator_1.queryOutboundGasFee)(ctx, prc20Token, gasLimit);
|
|
258
|
+
const result = yield (0, gas_calculator_1.queryOutboundGasFee)(ctx, prc20Token, gasLimit, targetChain);
|
|
243
259
|
gasToken = result.gasToken;
|
|
244
260
|
gasFee = result.gasFee;
|
|
261
|
+
sizing = result.sizing;
|
|
245
262
|
}
|
|
246
263
|
return Object.assign(Object.assign({}, baseDescriptor), { targetChain,
|
|
247
264
|
ceaAddress,
|
|
@@ -249,7 +266,8 @@ function buildHopDescriptor(ctx, params, route, ueaAddress) {
|
|
|
249
266
|
prc20Token,
|
|
250
267
|
burnAmount,
|
|
251
268
|
gasToken,
|
|
252
|
-
gasFee
|
|
269
|
+
gasFee,
|
|
270
|
+
sizing });
|
|
253
271
|
}
|
|
254
272
|
case route_detector_1.TransactionRoute.CEA_TO_PUSH: {
|
|
255
273
|
// Route 3: Build CEA multicalls for sendUniversalTxFromCEA
|
|
@@ -290,13 +308,16 @@ function buildHopDescriptor(ctx, params, route, ueaAddress) {
|
|
|
290
308
|
}
|
|
291
309
|
let gasToken = selectors_1.ZERO_ADDRESS;
|
|
292
310
|
let gasFee = BigInt(0);
|
|
311
|
+
let sizing;
|
|
293
312
|
if (prc20Token !== selectors_1.ZERO_ADDRESS) {
|
|
294
|
-
const result = yield (0, gas_calculator_1.queryOutboundGasFee)(ctx, prc20Token, gasLimit);
|
|
313
|
+
const result = yield (0, gas_calculator_1.queryOutboundGasFee)(ctx, prc20Token, gasLimit, sourceChain);
|
|
295
314
|
gasToken = result.gasToken;
|
|
296
315
|
gasFee = result.gasFee;
|
|
316
|
+
sizing = result.sizing;
|
|
297
317
|
}
|
|
298
318
|
return Object.assign(Object.assign({}, baseDescriptor), { sourceChain, ceaAddress: ceaPdaHex, isSvmTarget: true, prc20Token, burnAmount: amount, gasToken,
|
|
299
|
-
gasFee
|
|
319
|
+
gasFee,
|
|
320
|
+
sizing });
|
|
300
321
|
}
|
|
301
322
|
const { cea: ceaAddress } = yield (0, cea_utils_1.getCEAAddress)(ueaAddress, sourceChain, (_h = ctx.rpcUrls[sourceChain]) === null || _h === void 0 ? void 0 : _h[0]);
|
|
302
323
|
// CEA auto-deploys on-chain: Vault.finalizeUniversalTx calls CEAFactory.deployCEA()
|
|
@@ -326,15 +347,18 @@ function buildHopDescriptor(ctx, params, route, ueaAddress) {
|
|
|
326
347
|
const prc20Token = (0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork);
|
|
327
348
|
let gasToken = selectors_1.ZERO_ADDRESS;
|
|
328
349
|
let gasFee = BigInt(0);
|
|
350
|
+
let sizing;
|
|
329
351
|
if (prc20Token !== selectors_1.ZERO_ADDRESS) {
|
|
330
|
-
const result = yield (0, gas_calculator_1.queryOutboundGasFee)(ctx, prc20Token, gasLimit);
|
|
352
|
+
const result = yield (0, gas_calculator_1.queryOutboundGasFee)(ctx, prc20Token, gasLimit, sourceChain);
|
|
331
353
|
gasToken = result.gasToken;
|
|
332
354
|
gasFee = result.gasFee;
|
|
355
|
+
sizing = result.sizing;
|
|
333
356
|
}
|
|
334
357
|
return Object.assign(Object.assign({}, baseDescriptor), { sourceChain,
|
|
335
358
|
ceaAddress,
|
|
336
359
|
prc20Token, burnAmount: amount, gasToken,
|
|
337
|
-
gasFee
|
|
360
|
+
gasFee,
|
|
361
|
+
sizing });
|
|
338
362
|
}
|
|
339
363
|
default:
|
|
340
364
|
return baseDescriptor;
|
|
@@ -354,10 +378,20 @@ function classifyIntoSegments(hops) {
|
|
|
354
378
|
const chain = hop.targetChain || hop.sourceChain;
|
|
355
379
|
const canMerge = currentSegment &&
|
|
356
380
|
currentSegment.type === segType &&
|
|
357
|
-
// 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).
|
|
358
389
|
(segType === 'OUTBOUND_TO_CEA'
|
|
359
390
|
? currentSegment.targetChain === hop.targetChain && !hop.isSvmTarget
|
|
360
|
-
: segType === '
|
|
391
|
+
: segType === 'INBOUND_FROM_CEA'
|
|
392
|
+
? currentSegment.sourceChain === hop.sourceChain &&
|
|
393
|
+
!(0, payload_builders_1.isSvmChain)(hop.sourceChain)
|
|
394
|
+
: segType === 'PUSH_EXECUTION');
|
|
361
395
|
if (canMerge && currentSegment) {
|
|
362
396
|
// Merge into current segment
|
|
363
397
|
currentSegment.hops.push(hop);
|
|
@@ -377,12 +411,36 @@ function classifyIntoSegments(hops) {
|
|
|
377
411
|
currentSegment.gasFee =
|
|
378
412
|
(currentSegment.gasFee || BigInt(0)) + (hop.gasFee || BigInt(0));
|
|
379
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
|
+
}
|
|
380
426
|
else if (segType === 'PUSH_EXECUTION') {
|
|
381
427
|
currentSegment.mergedPushMulticalls = [
|
|
382
428
|
...(currentSegment.mergedPushMulticalls || []),
|
|
383
429
|
...(hop.pushMulticalls || []),
|
|
384
430
|
];
|
|
385
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
|
+
}
|
|
386
444
|
}
|
|
387
445
|
else {
|
|
388
446
|
// Start a new segment
|
|
@@ -402,12 +460,17 @@ function classifyIntoSegments(hops) {
|
|
|
402
460
|
gasToken: hop.gasToken,
|
|
403
461
|
gasFee: hop.gasFee,
|
|
404
462
|
gasLimit: hop.gasLimit,
|
|
463
|
+
sizing: hop.sizing,
|
|
405
464
|
};
|
|
406
465
|
segments.push(currentSegment);
|
|
407
466
|
}
|
|
408
467
|
}
|
|
409
468
|
return segments;
|
|
410
469
|
}
|
|
470
|
+
/** Ordering for sizing-category severity (higher = stricter). */
|
|
471
|
+
function categoryRank(c) {
|
|
472
|
+
return c === 'C' ? 2 : c === 'B' ? 1 : 0;
|
|
473
|
+
}
|
|
411
474
|
// ============================================================================
|
|
412
475
|
// getSegmentType
|
|
413
476
|
// ============================================================================
|
|
@@ -427,237 +490,365 @@ function getSegmentType(route) {
|
|
|
427
490
|
// composeCascade
|
|
428
491
|
// ============================================================================
|
|
429
492
|
function composeCascade(ctx, segments, ueaAddress, ueaBalance, ueaNonce) {
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
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);
|
|
453
549
|
}
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
targetForOutbound = svmTarget.address;
|
|
466
|
-
}
|
|
467
|
-
else if (isMigration) {
|
|
468
|
-
// Migration: use raw 4-byte MIGRATION_SELECTOR, no multicall wrapping
|
|
469
|
-
outboundPayload = (0, payload_builders_1.buildMigrationPayload)();
|
|
470
|
-
targetForOutbound = (firstHop === null || firstHop === void 0 ? void 0 : firstHop.ceaAddress) || ueaAddress;
|
|
471
|
-
}
|
|
472
|
-
else {
|
|
473
|
-
// EVM: build CEA payload from merged multicalls
|
|
474
|
-
outboundPayload = (0, payload_builders_1.buildCeaMulticallPayload)(segment.mergedCeaMulticalls || []);
|
|
475
|
-
// Get CEA address from the first hop
|
|
476
|
-
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;
|
|
477
561
|
}
|
|
478
|
-
|
|
479
|
-
|
|
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
|
-
const
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
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,
|
|
512
618
|
});
|
|
513
|
-
// Prepend
|
|
619
|
+
// Prepend to accumulated
|
|
514
620
|
accumulatedPushMulticalls = [
|
|
515
|
-
...
|
|
621
|
+
...outboundMulticalls,
|
|
516
622
|
...accumulatedPushMulticalls,
|
|
517
623
|
];
|
|
624
|
+
break;
|
|
518
625
|
}
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
//
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
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
|
+
}
|
|
533
646
|
}
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
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}`);
|
|
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
|
+
}
|
|
547
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;
|
|
548
736
|
}
|
|
549
|
-
|
|
550
|
-
|
|
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);
|
|
551
743
|
}
|
|
552
|
-
//
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
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);
|
|
570
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
|
+
}
|
|
571
834
|
const outboundMulticalls = (0, payload_builders_1.buildOutboundApprovalAndCall)({
|
|
572
835
|
prc20Token: segment.prc20Token ||
|
|
573
836
|
(0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork),
|
|
574
837
|
gasToken: segment.gasToken || selectors_1.ZERO_ADDRESS,
|
|
575
|
-
burnAmount:
|
|
838
|
+
burnAmount: segment.totalBurnAmount || BigInt(0),
|
|
576
839
|
gasFee: inboundGasFee,
|
|
577
|
-
nativeValueForGas:
|
|
840
|
+
nativeValueForGas: evmInboundNativeValue,
|
|
578
841
|
gatewayPcAddress,
|
|
579
842
|
outboundRequest: outboundReq,
|
|
580
843
|
});
|
|
844
|
+
// Reset accumulated -- everything is now inside this outbound
|
|
581
845
|
accumulatedPushMulticalls = [...outboundMulticalls];
|
|
582
846
|
break;
|
|
583
847
|
}
|
|
584
|
-
// EVM path: Build CEA multicall: [approve?, sendUniversalTxFromCEA(payload)]
|
|
585
|
-
const ceaMulticalls = [];
|
|
586
|
-
// Add hop's own CEA operations if any
|
|
587
|
-
// (e.g., approve + swap before bridging back)
|
|
588
|
-
if (hop.ceaMulticalls && hop.ceaMulticalls.length > 0) {
|
|
589
|
-
ceaMulticalls.push(...hop.ceaMulticalls);
|
|
590
|
-
}
|
|
591
|
-
// Determine token/amount for inbound
|
|
592
|
-
let tokenAddress = selectors_1.ZERO_ADDRESS;
|
|
593
|
-
let amount = BigInt(0);
|
|
594
|
-
const params = hop.params;
|
|
595
|
-
if ((_f = params.funds) === null || _f === void 0 ? void 0 : _f.amount) {
|
|
596
|
-
const token = params.funds.token;
|
|
597
|
-
if (token) {
|
|
598
|
-
if (token.mechanism === 'native') {
|
|
599
|
-
amount = params.funds.amount;
|
|
600
|
-
}
|
|
601
|
-
else {
|
|
602
|
-
tokenAddress = token.address;
|
|
603
|
-
amount = params.funds.amount;
|
|
604
|
-
// Add approve for ERC20 (CEA approves gateway)
|
|
605
|
-
const gatewayAddr = chain_1.UNIVERSAL_GATEWAY_ADDRESSES[sourceChain];
|
|
606
|
-
if (!gatewayAddr) {
|
|
607
|
-
throw new Error(`No UniversalGateway address configured for chain ${sourceChain}`);
|
|
608
|
-
}
|
|
609
|
-
// Reset allowance to 0 first for USDT-style tokens
|
|
610
|
-
const approveZeroData = (0, viem_1.encodeFunctionData)({
|
|
611
|
-
abi: erc20_evm_1.ERC20_EVM,
|
|
612
|
-
functionName: 'approve',
|
|
613
|
-
args: [gatewayAddr, BigInt(0)],
|
|
614
|
-
});
|
|
615
|
-
ceaMulticalls.push({
|
|
616
|
-
to: tokenAddress,
|
|
617
|
-
value: BigInt(0),
|
|
618
|
-
data: approveZeroData,
|
|
619
|
-
});
|
|
620
|
-
const approveData = (0, viem_1.encodeFunctionData)({
|
|
621
|
-
abi: erc20_evm_1.ERC20_EVM,
|
|
622
|
-
functionName: 'approve',
|
|
623
|
-
args: [gatewayAddr, amount],
|
|
624
|
-
});
|
|
625
|
-
ceaMulticalls.push({
|
|
626
|
-
to: tokenAddress,
|
|
627
|
-
value: BigInt(0),
|
|
628
|
-
data: approveData,
|
|
629
|
-
});
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
else if (params.value && params.value > BigInt(0)) {
|
|
634
|
-
amount = params.value;
|
|
635
|
-
}
|
|
636
|
-
// Build sendUniversalTxToUEA self-call on CEA (to=CEA, value=0)
|
|
637
|
-
const sendCall = (0, payload_builders_1.buildSendUniversalTxToUEA)(ceaAddress, tokenAddress, amount, intermediatePayload, ceaAddress);
|
|
638
|
-
ceaMulticalls.push(sendCall);
|
|
639
|
-
// Wrap CEA multicall in outbound from Push Chain
|
|
640
|
-
const ceaPayload = (0, payload_builders_1.buildCeaMulticallPayload)(ceaMulticalls);
|
|
641
|
-
const outboundReq = (0, payload_builders_1.buildOutboundRequest)(ceaAddress, segment.prc20Token ||
|
|
642
|
-
(0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork), segment.totalBurnAmount || BigInt(0), (_g = segment.gasLimit) !== null && _g !== void 0 ? _g : BigInt(0), ceaPayload, ueaAddress);
|
|
643
|
-
const inboundGasFee = segment.gasFee || BigInt(0);
|
|
644
|
-
const outboundMulticalls = (0, payload_builders_1.buildOutboundApprovalAndCall)({
|
|
645
|
-
prc20Token: segment.prc20Token ||
|
|
646
|
-
(0, helpers_1.getNativePRC20ForChain)(sourceChain, ctx.pushNetwork),
|
|
647
|
-
gasToken: segment.gasToken || selectors_1.ZERO_ADDRESS,
|
|
648
|
-
burnAmount: segment.totalBurnAmount || BigInt(0),
|
|
649
|
-
gasFee: inboundGasFee,
|
|
650
|
-
nativeValueForGas: perOutboundNativeValue !== null && perOutboundNativeValue !== void 0 ? perOutboundNativeValue : inboundGasFee * BigInt(1000),
|
|
651
|
-
gatewayPcAddress,
|
|
652
|
-
outboundRequest: outboundReq,
|
|
653
|
-
});
|
|
654
|
-
// Reset accumulated -- everything is now inside this outbound
|
|
655
|
-
accumulatedPushMulticalls = [...outboundMulticalls];
|
|
656
|
-
break;
|
|
657
848
|
}
|
|
658
849
|
}
|
|
659
|
-
|
|
660
|
-
|
|
850
|
+
return accumulatedPushMulticalls;
|
|
851
|
+
});
|
|
661
852
|
}
|
|
662
853
|
// ============================================================================
|
|
663
854
|
// createCascadedBuilder
|
|
@@ -750,7 +941,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks) {
|
|
|
750
941
|
const ueaNonceCascade = ueaCodeCascade !== undefined
|
|
751
942
|
? yield (0, uea_manager_1.getUEANonce)(ctx, ueaAddress)
|
|
752
943
|
: BigInt(0);
|
|
753
|
-
const composedMulticalls = composeCascade(ctx, segments, ueaAddress, ueaBalance, ueaNonceCascade);
|
|
944
|
+
const composedMulticalls = yield composeCascade(ctx, segments, ueaAddress, ueaBalance, ueaNonceCascade);
|
|
754
945
|
// Execute the composed multicall as a single Push Chain tx
|
|
755
946
|
const executeParams = {
|
|
756
947
|
to: ueaAddress,
|
|
@@ -775,14 +966,77 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks) {
|
|
|
775
966
|
hops: hopInfos,
|
|
776
967
|
hopCount: hops.length,
|
|
777
968
|
waitForAll: (opts) => tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
778
|
-
var _a;
|
|
779
|
-
const { pollingIntervalMs = 10000, timeout = 300000, progressHook: cascadeProgressHook, } = opts || {};
|
|
969
|
+
var _a, _b, _c, _d, _e;
|
|
970
|
+
const { pollingIntervalMs = 10000, timeout = 300000, progressHook: cascadeProgressHook, eventHook, } = opts || {};
|
|
780
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
|
+
}
|
|
781
1028
|
try {
|
|
782
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
|
+
}
|
|
783
1037
|
cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
|
|
784
1038
|
hopIndex: 0,
|
|
785
|
-
route: ((
|
|
1039
|
+
route: ((_c = hopInfos[0]) === null || _c === void 0 ? void 0 : _c.route) || 'UOA_TO_PUSH',
|
|
786
1040
|
chain: (0, helpers_1.getPushChainForNetwork)(ctx.pushNetwork),
|
|
787
1041
|
status: 'waiting',
|
|
788
1042
|
elapsed: Date.now() - startTime,
|
|
@@ -801,6 +1055,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks) {
|
|
|
801
1055
|
txHash: response.hash,
|
|
802
1056
|
elapsed: Date.now() - startTime,
|
|
803
1057
|
});
|
|
1058
|
+
emitHopComplete(hop.hopIndex);
|
|
804
1059
|
}
|
|
805
1060
|
}
|
|
806
1061
|
// 2. Track outbound hops (Route 2: UOA_TO_CEA)
|
|
@@ -831,6 +1086,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks) {
|
|
|
831
1086
|
if (outboundHops.length === 1) {
|
|
832
1087
|
// Single direct outbound hop: use the existing V1-based tracking
|
|
833
1088
|
const hop = outboundHops[0];
|
|
1089
|
+
const hopHooks = (0, progress_route_hooks_1.pickWaitHooks)(hop.route);
|
|
834
1090
|
const remainingTimeout = timeout - (Date.now() - startTime);
|
|
835
1091
|
if (remainingTimeout <= 0) {
|
|
836
1092
|
hop.status = 'failed';
|
|
@@ -841,12 +1097,16 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks) {
|
|
|
841
1097
|
status: 'timeout',
|
|
842
1098
|
elapsed: Date.now() - startTime,
|
|
843
1099
|
});
|
|
1100
|
+
emitHopEvent(eventHook, hopHooks.timeout(hop.executionChain, Date.now() - startTime));
|
|
1101
|
+
emitCascadeTimeout(hop.hopIndex);
|
|
844
1102
|
return {
|
|
845
1103
|
success: false,
|
|
846
1104
|
hops: hopInfos,
|
|
847
1105
|
failedAt: hop.hopIndex,
|
|
848
1106
|
};
|
|
849
1107
|
}
|
|
1108
|
+
emitHopStart(hop.hopIndex);
|
|
1109
|
+
emitHopEvent(eventHook, hopHooks.awaiting(hop.executionChain));
|
|
850
1110
|
cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
|
|
851
1111
|
hopIndex: hop.hopIndex,
|
|
852
1112
|
route: hop.route,
|
|
@@ -854,6 +1114,7 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks) {
|
|
|
854
1114
|
status: 'polling',
|
|
855
1115
|
elapsed: Date.now() - startTime,
|
|
856
1116
|
});
|
|
1117
|
+
let lastEmittedStatus;
|
|
857
1118
|
try {
|
|
858
1119
|
const outboundDetails = yield callbacks.waitForOutboundTxFn(response.hash, {
|
|
859
1120
|
initialWaitMs: Math.min(60000, remainingTimeout),
|
|
@@ -867,11 +1128,18 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks) {
|
|
|
867
1128
|
status: event.status,
|
|
868
1129
|
elapsed: Date.now() - startTime,
|
|
869
1130
|
});
|
|
1131
|
+
if (event.status === 'polling' &&
|
|
1132
|
+
lastEmittedStatus !== 'polling') {
|
|
1133
|
+
lastEmittedStatus = 'polling';
|
|
1134
|
+
emitHopEvent(eventHook, hopHooks.polling(hop.executionChain, event.elapsed));
|
|
1135
|
+
}
|
|
870
1136
|
},
|
|
871
1137
|
});
|
|
872
1138
|
hop.status = 'confirmed';
|
|
873
1139
|
hop.txHash = outboundDetails.externalTxHash;
|
|
874
1140
|
hop.outboundDetails = outboundDetails;
|
|
1141
|
+
emitHopEvent(eventHook, hopHooks.success(outboundDetails));
|
|
1142
|
+
emitHopComplete(hop.hopIndex);
|
|
875
1143
|
cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
|
|
876
1144
|
hopIndex: hop.hopIndex,
|
|
877
1145
|
route: hop.route,
|
|
@@ -882,6 +1150,17 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks) {
|
|
|
882
1150
|
});
|
|
883
1151
|
}
|
|
884
1152
|
catch (err) {
|
|
1153
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
1154
|
+
const isTimeout = errMsg.startsWith('Timeout waiting for outbound transaction');
|
|
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
|
+
}
|
|
885
1164
|
hop.status = 'failed';
|
|
886
1165
|
cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
|
|
887
1166
|
hopIndex: hop.hopIndex,
|
|
@@ -899,11 +1178,20 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks) {
|
|
|
899
1178
|
}
|
|
900
1179
|
else {
|
|
901
1180
|
// Multiple outbound hops: use V2 API which returns outboundTx[]
|
|
1181
|
+
// Emit "Awaiting External Chain" once per outbound hop before
|
|
1182
|
+
// polling begins, using the route-specific awaiting hook.
|
|
1183
|
+
for (const outHop of outboundHops) {
|
|
1184
|
+
emitHopStart(outHop.hopIndex);
|
|
1185
|
+
emitHopEvent(eventHook, (0, progress_route_hooks_1.pickWaitHooks)(outHop.route).awaiting(outHop.executionChain));
|
|
1186
|
+
}
|
|
1187
|
+
const perHopLastStatus = new Map();
|
|
1188
|
+
const confirmedHops = new Set();
|
|
902
1189
|
const allOutboundDetails = yield callbacks.waitForAllOutboundTxsFn(response.hash, outboundHops, {
|
|
903
1190
|
initialWaitMs: Math.min(60000, timeout - (Date.now() - startTime)),
|
|
904
1191
|
pollingIntervalMs,
|
|
905
1192
|
timeout: timeout - (Date.now() - startTime),
|
|
906
1193
|
progressHook: (event) => {
|
|
1194
|
+
var _a, _b;
|
|
907
1195
|
cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
|
|
908
1196
|
hopIndex: event.hopIndex,
|
|
909
1197
|
route: event.route,
|
|
@@ -912,21 +1200,54 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks) {
|
|
|
912
1200
|
txHash: event.txHash,
|
|
913
1201
|
elapsed: Date.now() - startTime,
|
|
914
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);
|
|
1205
|
+
const prev = perHopLastStatus.get(event.hopIndex);
|
|
1206
|
+
if (event.status === 'polling' &&
|
|
1207
|
+
prev !== 'polling') {
|
|
1208
|
+
perHopLastStatus.set(event.hopIndex, 'polling');
|
|
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));
|
|
1210
|
+
}
|
|
1211
|
+
if (event.status === 'confirmed' &&
|
|
1212
|
+
event.txHash &&
|
|
1213
|
+
!confirmedHops.has(event.hopIndex)) {
|
|
1214
|
+
// waitForAllOutboundTxsV2 populates hop.outboundDetails
|
|
1215
|
+
// on the matched hop before emitting 'confirmed' (see
|
|
1216
|
+
// outbound-sync.ts:348-355). Auto-promotion paths
|
|
1217
|
+
// without a txHash are skipped by the `event.txHash`
|
|
1218
|
+
// guard above, so we only reach here with full details.
|
|
1219
|
+
if (matchedHop === null || matchedHop === void 0 ? void 0 : matchedHop.outboundDetails) {
|
|
1220
|
+
confirmedHops.add(event.hopIndex);
|
|
1221
|
+
emitHopEvent(eventHook, eventHooks.success(matchedHop.outboundDetails));
|
|
1222
|
+
emitHopComplete(event.hopIndex);
|
|
1223
|
+
}
|
|
1224
|
+
}
|
|
915
1225
|
},
|
|
916
1226
|
});
|
|
917
1227
|
if (!allOutboundDetails.success) {
|
|
1228
|
+
const failedIdx = (_d = allOutboundDetails.failedAt) !== null && _d !== void 0 ? _d : 0;
|
|
1229
|
+
const failedHop = outboundHops.find((h) => h.hopIndex === failedIdx);
|
|
1230
|
+
const failMsg = failedHop
|
|
1231
|
+
? `Outbound failed for hop ${failedIdx} on ${failedHop.executionChain}`
|
|
1232
|
+
: `Outbound cascade failed at hop ${failedIdx}`;
|
|
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);
|
|
918
1235
|
return {
|
|
919
1236
|
success: false,
|
|
920
1237
|
hops: hopInfos,
|
|
921
|
-
failedAt:
|
|
1238
|
+
failedAt: failedIdx,
|
|
922
1239
|
};
|
|
923
1240
|
}
|
|
924
1241
|
}
|
|
925
1242
|
}
|
|
926
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.
|
|
927
1247
|
const inboundHops = hopInfos.filter((h) => h.route === 'CEA_TO_PUSH');
|
|
928
1248
|
for (const inboundHop of inboundHops) {
|
|
929
1249
|
inboundHop.status = 'submitted';
|
|
1250
|
+
emitHopStart(inboundHop.hopIndex);
|
|
930
1251
|
cascadeProgressHook === null || cascadeProgressHook === void 0 ? void 0 : cascadeProgressHook({
|
|
931
1252
|
hopIndex: inboundHop.hopIndex,
|
|
932
1253
|
route: inboundHop.route,
|
|
@@ -935,14 +1256,24 @@ function createCascadedBuilder(ctx, preparedTxs, callbacks) {
|
|
|
935
1256
|
elapsed: Date.now() - startTime,
|
|
936
1257
|
});
|
|
937
1258
|
}
|
|
1259
|
+
emitCascadeSuccess();
|
|
938
1260
|
return { success: true, hops: hopInfos };
|
|
939
1261
|
}
|
|
940
1262
|
catch (err) {
|
|
941
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
|
+
}
|
|
942
1273
|
return {
|
|
943
1274
|
success: false,
|
|
944
1275
|
hops: hopInfos,
|
|
945
|
-
failedAt
|
|
1276
|
+
failedAt,
|
|
946
1277
|
};
|
|
947
1278
|
}
|
|
948
1279
|
}),
|