@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.
Files changed (86) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/package.json +1 -1
  3. package/src/lib/constants/abi/index.d.ts +2 -0
  4. package/src/lib/constants/abi/index.js +6 -1
  5. package/src/lib/constants/abi/uniswap-v3-router.evm.d.ts +58 -0
  6. package/src/lib/constants/abi/uniswap-v3-router.evm.js +41 -0
  7. package/src/lib/constants/abi/wpc.evm.d.ts +24 -0
  8. package/src/lib/constants/abi/wpc.evm.js +27 -0
  9. package/src/lib/constants/chain.d.ts +1 -0
  10. package/src/lib/constants/chain.js +8 -6
  11. package/src/lib/constants/tokens.js +3 -3
  12. package/src/lib/generated/uexecutor/v2/types.d.ts +14 -0
  13. package/src/lib/generated/uexecutor/v2/types.js +96 -3
  14. package/src/lib/orchestrator/internals/cascade.d.ts +1 -1
  15. package/src/lib/orchestrator/internals/cascade.js +556 -225
  16. package/src/lib/orchestrator/internals/confirmation.js +60 -15
  17. package/src/lib/orchestrator/internals/context.d.ts +23 -0
  18. package/src/lib/orchestrator/internals/context.js +54 -0
  19. package/src/lib/orchestrator/internals/execute-funds-only.js +47 -21
  20. package/src/lib/orchestrator/internals/execute-funds-payload.js +53 -31
  21. package/src/lib/orchestrator/internals/execute-standard.js +137 -26
  22. package/src/lib/orchestrator/internals/gas-calculator.d.ts +5 -2
  23. package/src/lib/orchestrator/internals/gas-calculator.js +59 -11
  24. package/src/lib/orchestrator/internals/gas-usd-sizer.d.ts +53 -0
  25. package/src/lib/orchestrator/internals/gas-usd-sizer.js +165 -0
  26. package/src/lib/orchestrator/internals/helpers.js +2 -0
  27. package/src/lib/orchestrator/internals/inbound-tracker.d.ts +83 -0
  28. package/src/lib/orchestrator/internals/inbound-tracker.js +322 -0
  29. package/src/lib/orchestrator/internals/index.d.ts +3 -1
  30. package/src/lib/orchestrator/internals/index.js +8 -2
  31. package/src/lib/orchestrator/internals/outbound-sync.d.ts +32 -1
  32. package/src/lib/orchestrator/internals/outbound-sync.js +154 -17
  33. package/src/lib/orchestrator/internals/pc-usd-oracle.d.ts +28 -0
  34. package/src/lib/orchestrator/internals/pc-usd-oracle.js +285 -0
  35. package/src/lib/orchestrator/internals/progress-route-hooks.d.ts +23 -0
  36. package/src/lib/orchestrator/internals/progress-route-hooks.js +72 -0
  37. package/src/lib/orchestrator/internals/push-chain-tx.d.ts +27 -1
  38. package/src/lib/orchestrator/internals/push-chain-tx.js +111 -15
  39. package/src/lib/orchestrator/internals/rescue.js +1 -1
  40. package/src/lib/orchestrator/internals/response-builder.d.ts +5 -0
  41. package/src/lib/orchestrator/internals/response-builder.js +252 -53
  42. package/src/lib/orchestrator/internals/route-handlers.js +480 -206
  43. package/src/lib/orchestrator/internals/tx-transformer.js +293 -35
  44. package/src/lib/orchestrator/orchestrator.d.ts +7 -1
  45. package/src/lib/orchestrator/orchestrator.js +108 -56
  46. package/src/lib/orchestrator/orchestrator.types.d.ts +120 -5
  47. package/src/lib/orchestrator/payload-builders.d.ts +16 -0
  48. package/src/lib/orchestrator/payload-builders.js +6 -5
  49. package/src/lib/orchestrator/route-detector.js +20 -8
  50. package/src/lib/orchestrator/svm-idl/normalize-address.d.ts +19 -0
  51. package/src/lib/orchestrator/svm-idl/normalize-address.js +56 -0
  52. package/src/lib/orchestrator/svm-idl/registry.d.ts +13 -4
  53. package/src/lib/orchestrator/svm-idl/registry.js +15 -14
  54. package/src/lib/orchestrator/svm-idl/resolve.js +5 -2
  55. package/src/lib/progress-hook/progress-hook.d.ts +28 -0
  56. package/src/lib/progress-hook/progress-hook.js +555 -101
  57. package/src/lib/progress-hook/progress-hook.types.d.ts +76 -24
  58. package/src/lib/progress-hook/progress-hook.types.js +74 -29
  59. package/src/lib/push-client/push-client.d.ts +7 -0
  60. package/src/lib/push-client/push-client.js +18 -1
  61. package/src/lib/universal-tx-detector/__tests__/fixtures/builders.d.ts +32 -0
  62. package/src/lib/universal-tx-detector/__tests__/fixtures/builders.js +65 -0
  63. package/src/lib/universal-tx-detector/__tests__/fixtures/scenarios.d.ts +72 -0
  64. package/src/lib/universal-tx-detector/__tests__/fixtures/scenarios.js +669 -0
  65. package/src/lib/universal-tx-detector/auto-detect.d.ts +37 -0
  66. package/src/lib/universal-tx-detector/auto-detect.js +112 -0
  67. package/src/lib/universal-tx-detector/cascade.d.ts +64 -0
  68. package/src/lib/universal-tx-detector/cascade.js +254 -0
  69. package/src/lib/universal-tx-detector/child-inbounds.d.ts +58 -0
  70. package/src/lib/universal-tx-detector/child-inbounds.js +145 -0
  71. package/src/lib/universal-tx-detector/classify.d.ts +33 -0
  72. package/src/lib/universal-tx-detector/classify.js +228 -0
  73. package/src/lib/universal-tx-detector/detector-svm.d.ts +46 -0
  74. package/src/lib/universal-tx-detector/detector-svm.js +324 -0
  75. package/src/lib/universal-tx-detector/detector.d.ts +13 -0
  76. package/src/lib/universal-tx-detector/detector.js +233 -0
  77. package/src/lib/universal-tx-detector/events.d.ts +765 -0
  78. package/src/lib/universal-tx-detector/events.js +241 -0
  79. package/src/lib/universal-tx-detector/index.d.ts +18 -0
  80. package/src/lib/universal-tx-detector/index.js +36 -0
  81. package/src/lib/universal-tx-detector/svm-events.d.ts +41 -0
  82. package/src/lib/universal-tx-detector/svm-events.js +68 -0
  83. package/src/lib/universal-tx-detector/types.d.ts +100 -0
  84. package/src/lib/universal-tx-detector/types.js +3 -0
  85. package/src/lib/utils.d.ts +30 -13
  86. package/src/lib/utils.js +47 -21
@@ -16,6 +16,8 @@ const viem_1 = require("viem");
16
16
  const uea_evm_1 = require("../../constants/abi/uea.evm");
17
17
  const chain_1 = require("../../constants/chain");
18
18
  const enums_1 = require("../../constants/enums");
19
+ const progress_hook_types_1 = require("../../progress-hook/progress-hook.types");
20
+ const progress_hook_1 = tslib_1.__importDefault(require("../../progress-hook/progress-hook"));
19
21
  const account_1 = require("../../universal/account/account");
20
22
  const push_client_1 = require("../../push-client/push-client");
21
23
  const route_detector_1 = require("../route-detector");
@@ -23,7 +25,39 @@ const context_1 = require("./context");
23
25
  const helpers_1 = require("./helpers");
24
26
  const svm_helpers_1 = require("./svm-helpers");
25
27
  const outbound_tracker_1 = require("./outbound-tracker");
28
+ const inbound_tracker_1 = require("./inbound-tracker");
26
29
  const tx_transformer_1 = require("./tx-transformer");
30
+ const progress_route_hooks_1 = require("./progress-route-hooks");
31
+ const outbound_sync_1 = require("./outbound-sync");
32
+ /**
33
+ * Intermediate Push-success markers (199-99-99 for R3, 299-99 for R2) are
34
+ * internal transition signals — they tell response-builder's wait-phase
35
+ * branch that the Push leg has settled so it can skip re-emitting the
36
+ * intermediate marker on top of a reconstructed stream. They are not part
37
+ * of the published 1XX/2XX/3XX/4XX spec and are suppressed at the consumer
38
+ * dispatch boundary. `printLog` still fires for these so internal traces
39
+ * retain the transition record.
40
+ */
41
+ const INTERMEDIATE_INTERNAL_IDS = new Set([
42
+ progress_hook_types_1.PROGRESS_HOOK.SEND_TX_199_99_99,
43
+ progress_hook_types_1.PROGRESS_HOOK.SEND_TX_299_99,
44
+ ]);
45
+ /**
46
+ * Fan out a progress event to one or two registered callbacks, deduping
47
+ * when both slots hold the same reference. Keeps the emission policy in a
48
+ * single place shared by `emitProgress` (execute phase) and `emit` (wait
49
+ * phase) — so consumers that wire the same callback at multiple levels
50
+ * never see double-invocation. Intermediate internal markers are dropped
51
+ * here so they never reach the consumer.
52
+ */
53
+ function fanOut(event, primary, secondary) {
54
+ if (INTERMEDIATE_INTERNAL_IDS.has(event.id))
55
+ return;
56
+ if (primary)
57
+ primary(event);
58
+ if (secondary && secondary !== primary)
59
+ secondary(event);
60
+ }
27
61
  // ============================================================================
28
62
  // queryUniversalTxStatusFromGatewayTx
29
63
  // ============================================================================
@@ -105,8 +139,8 @@ txHash, evmGatewayMethod) {
105
139
  }, null, 2));
106
140
  // Fetch UniversalTx via gRPC with linear-then-exponential retry
107
141
  const LINEAR_ATTEMPTS = 10;
108
- const LINEAR_DELAY_MS = 3000;
109
- const EXPONENTIAL_BASE_MS = 4000;
142
+ const LINEAR_DELAY_MS = 3600;
143
+ const EXPONENTIAL_BASE_MS = 4800;
110
144
  const MAX_ATTEMPTS = 15;
111
145
  let universalTxObj;
112
146
  for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
@@ -146,6 +180,7 @@ txHash, evmGatewayMethod) {
146
180
  // ============================================================================
147
181
  function trackTransaction(ctx, txHash, options, callbacks) {
148
182
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
183
+ var _a, _b;
149
184
  if (!callbacks) {
150
185
  throw new Error('trackTransaction requires ResponseBuilderCallbacks');
151
186
  }
@@ -153,18 +188,13 @@ function trackTransaction(ctx, txHash, options, callbacks) {
153
188
  const { timeout = 300000, pollingIntervalMs = 1000, rpcUrls = {} } = advanced;
154
189
  // Event buffer for replay via response.progressHook()
155
190
  const eventBuffer = [];
156
- // Helper to emit progress events
191
+ // Helper to emit progress events. Per-tx hook (passed to trackTransaction)
192
+ // fires first, then the orchestrator-level hook — deduped via `fanOut` so
193
+ // a caller that wires the same callback at both levels isn't double-fired.
157
194
  const emitProgress = (event) => {
158
195
  eventBuffer.push(event);
159
196
  (0, context_1.printLog)(ctx, event.message);
160
- // Per-transaction hook called FIRST
161
- if (progressHook) {
162
- progressHook(event);
163
- }
164
- // Orchestrator-level hook called SECOND
165
- if (ctx.progressHook) {
166
- ctx.progressHook(event);
167
- }
197
+ fanOut(event, progressHook, ctx.progressHook);
168
198
  };
169
199
  // Create client for target chain with optional RPC override
170
200
  const chainRPCs = rpcUrls[chain] || ctx.rpcUrls[chain] || chain_1.CHAIN_INFO[chain].defaultRPC;
@@ -203,7 +233,7 @@ function trackTransaction(ctx, txHash, options, callbacks) {
203
233
  universalTxData = utxResponse.universalTx;
204
234
  }
205
235
  }
206
- catch (_a) {
236
+ catch (_c) {
207
237
  // Ignore - try computed ID next
208
238
  }
209
239
  if (!universalTxData) {
@@ -215,7 +245,7 @@ function trackTransaction(ctx, txHash, options, callbacks) {
215
245
  universalTxData = utxResponse.universalTx;
216
246
  }
217
247
  }
218
- catch (_b) {
248
+ catch (_d) {
219
249
  // Ignore - try Cosmos event extraction next
220
250
  }
221
251
  }
@@ -230,7 +260,7 @@ function trackTransaction(ctx, txHash, options, callbacks) {
230
260
  }
231
261
  }
232
262
  }
233
- catch (_c) {
263
+ catch (_e) {
234
264
  // Ignore - no universal tx data available
235
265
  }
236
266
  }
@@ -241,12 +271,48 @@ function trackTransaction(ctx, txHash, options, callbacks) {
241
271
  const detectedRoute = (0, tx_transformer_1.detectRouteFromUniversalTxData)(universalTxData);
242
272
  if (detectedRoute) {
243
273
  universalTxResponse.route = detectedRoute;
274
+ // Mirror the route on the context so emission code in wait()/cascade
275
+ // picks the correct ID range — keeps trackTransaction in lockstep with
276
+ // sendUniversalTransaction, which sets currentRoute in execute().
277
+ ctx.currentRoute = detectedRoute;
278
+ // Populate the destination/source chain on the response so
279
+ // reconstructR2/R3 (and any downstream emission that reads
280
+ // universalTxResponse.chain) emit the proper friendly chain name
281
+ // instead of the "external" fallback. The first outbound's destination
282
+ // is the target chain for R2 and the source CEA chain for R3.
283
+ const firstOutboundChain = (_b = (_a = universalTxData === null || universalTxData === void 0 ? void 0 : universalTxData.outboundTx) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.destinationChain;
284
+ if (firstOutboundChain && !universalTxResponse.chain) {
285
+ universalTxResponse.chain = firstOutboundChain;
286
+ universalTxResponse.chainNamespace = firstOutboundChain;
287
+ }
288
+ // For R3 replays, enable inbound round-trip tracking so `.wait()` runs
289
+ // the 310-01 → 399-01 sequence. Live execute sets this in
290
+ // route-handlers.ts via `amount > 0n`; trackTransaction has no
291
+ // execute-phase args, so we enable it unconditionally for CEA_TO_PUSH.
292
+ // The inbound block tolerates no-inbound (payload-only R3) by timing
293
+ // out — same as live behaviour when the source tx has no round-trip.
294
+ if (detectedRoute === route_detector_1.TransactionRoute.CEA_TO_PUSH) {
295
+ universalTxResponse._expectsInboundRoundTrip = true;
296
+ }
244
297
  }
245
298
  // Reconstruct and emit SEND-TX-* progress events
246
299
  const reconstructedEvents = (0, tx_transformer_1.reconstructProgressEvents)(universalTxResponse, universalTxData);
247
300
  for (const event of reconstructedEvents) {
248
301
  emitProgress(event);
249
302
  }
303
+ // Mark the response so the wait() closure (when later invoked) doesn't
304
+ // call trackTransaction a second time and re-emit the same reconstructed
305
+ // events to the user's progressHook.
306
+ universalTxResponse._eventsReconstructed = true;
307
+ // Auto-register the caller's per-call progressHook as the response's
308
+ // registeredProgressHook so that tracked.wait() fires wait-phase events
309
+ // (209-xx / 299-xx / 399-xx) to the same callback without requiring
310
+ // tracked.progressHook(cb) to be called again. Skip replay of buffered
311
+ // events because we just fired them above via emitProgress — calling
312
+ // response.progressHook(cb) here would re-emit the reconstructed stream.
313
+ if (progressHook && universalTxResponse._setProgressHookNoReplay) {
314
+ universalTxResponse._setProgressHookNoReplay(progressHook);
315
+ }
250
316
  return universalTxResponse;
251
317
  });
252
318
  }
@@ -278,18 +344,36 @@ function transformToUniversalTxResponse(ctx_1, tx_1) {
278
344
  }
279
345
  from = (0, viem_1.getAddress)(tx.to);
280
346
  let decoded;
347
+ let decodedPayload = null;
281
348
  if (tx.input !== '0x') {
282
- decoded = (0, viem_1.decodeFunctionData)({
283
- abi: uea_evm_1.UEA_EVM,
284
- data: tx.input,
285
- });
286
- if (!(decoded === null || decoded === void 0 ? void 0 : decoded.args)) {
287
- throw new Error('Failed to decode function data');
349
+ // Try to decode the input as a known UEA_EVM call. Older txs — or
350
+ // txs sent to a UEA that was upgraded to expose a function not yet
351
+ // reflected in the bundled `uea.evm.ts` ABI — carry a selector that
352
+ // viem can't match. In that case the reconstructor should still
353
+ // produce a usable response (and emit progress hooks) using the raw
354
+ // tx fields, rather than throwing before any hook fires.
355
+ try {
356
+ decoded = (0, viem_1.decodeFunctionData)({ abi: uea_evm_1.UEA_EVM, data: tx.input });
357
+ if (decoded === null || decoded === void 0 ? void 0 : decoded.args) {
358
+ decodedPayload = decoded.args[0];
359
+ }
360
+ }
361
+ catch (_a) {
362
+ decodedPayload = null;
363
+ }
364
+ if (decodedPayload) {
365
+ to = decodedPayload.to;
366
+ value = BigInt(decodedPayload.value);
367
+ data = decodedPayload.data;
368
+ }
369
+ else {
370
+ // Unknown / unsupported UEA function — fall back to raw fields so
371
+ // transformToUniversalTxResponse can still return a valid response
372
+ // and downstream progress-hook reconstruction can proceed.
373
+ to = (0, viem_1.getAddress)(tx.to);
374
+ value = tx.value;
375
+ data = tx.input;
288
376
  }
289
- const universalPayload = decoded === null || decoded === void 0 ? void 0 : decoded.args[0];
290
- to = universalPayload.to;
291
- value = BigInt(universalPayload.value);
292
- data = universalPayload.data;
293
377
  // Extract 'to' from single-element multicall
294
378
  if (data && data.length >= 10) {
295
379
  const multicallSelector = (0, viem_1.keccak256)((0, viem_1.toBytes)('UEA_MULTICALL')).slice(0, 10);
@@ -311,7 +395,7 @@ function transformToUniversalTxResponse(ctx_1, tx_1) {
311
395
  to = (0, viem_1.getAddress)(decodedCalls[0].to);
312
396
  }
313
397
  }
314
- catch (_a) {
398
+ catch (_b) {
315
399
  // Keep original 'to' if decoding fails
316
400
  }
317
401
  }
@@ -379,7 +463,7 @@ function transformToUniversalTxResponse(ctx_1, tx_1) {
379
463
  from = '0x0000000000000000000000000000000000000000';
380
464
  }
381
465
  }
382
- catch (_b) {
466
+ catch (_c) {
383
467
  // Keep original values if decoding fails
384
468
  }
385
469
  }
@@ -395,7 +479,7 @@ function transformToUniversalTxResponse(ctx_1, tx_1) {
395
479
  yParity: tx.yParity,
396
480
  };
397
481
  }
398
- catch (_c) {
482
+ catch (_d) {
399
483
  // Fallback signature if parsing fails
400
484
  signature = {
401
485
  r: '0x0000000000000000000000000000000000000000000000000000000000000000',
@@ -451,44 +535,159 @@ function transformToUniversalTxResponse(ctx_1, tx_1) {
451
535
  maxPriorityFeePerGas: tx.maxPriorityFeePerGas,
452
536
  accessList: Array.isArray(tx.accessList) ? [...tx.accessList] : [],
453
537
  // 6. Utilities
454
- wait: () => tslib_1.__awaiter(this, void 0, void 0, function* () {
455
- // Use trackTransaction with registered hook if available
456
- let baseReceipt;
457
- if (registeredProgressHook) {
458
- const trackedResponse = yield callbacks.trackTransaction(tx.hash, {
459
- waitForCompletion: true,
460
- progressHook: registeredProgressHook,
461
- });
462
- // Get receipt from the tracked response
463
- const receipt = yield tx.wait();
464
- baseReceipt = callbacks.transformToUniversalTxReceipt(receipt, trackedResponse);
465
- }
466
- else {
467
- const receipt = yield tx.wait();
468
- baseReceipt = callbacks.transformToUniversalTxReceipt(receipt, universalTxResponse);
469
- }
538
+ wait: (waitOptions) => tslib_1.__awaiter(this, void 0, void 0, function* () {
539
+ var _a, _b, _c, _d, _e, _f;
540
+ // For trackTransaction replays the tx is historical — cosmos already
541
+ // has the full graph indexed, so the 20s/30s initial-wait buffers
542
+ // designed for live sends are pure deadweight. Default them to 0 when
543
+ // replaying; callers can still override via WaitOptions.
544
+ const isReplay = universalTxResponse._eventsReconstructed === true;
545
+ const outboundTimeoutMs = (_a = waitOptions === null || waitOptions === void 0 ? void 0 : waitOptions.outboundTimeoutMs) !== null && _a !== void 0 ? _a : callbacks.outboundConstants.maxTimeoutMs;
546
+ const inboundTimeoutMs = (_b = waitOptions === null || waitOptions === void 0 ? void 0 : waitOptions.inboundTimeoutMs) !== null && _b !== void 0 ? _b : callbacks.inboundConstants.maxTimeoutMs;
547
+ const outboundInitialWaitMs = (_c = waitOptions === null || waitOptions === void 0 ? void 0 : waitOptions.outboundInitialWaitMs) !== null && _c !== void 0 ? _c : (isReplay ? 0 : callbacks.outboundConstants.initialWaitMs);
548
+ const outboundPollingIntervalMs = (_d = waitOptions === null || waitOptions === void 0 ? void 0 : waitOptions.outboundPollingIntervalMs) !== null && _d !== void 0 ? _d : callbacks.outboundConstants.pollingIntervalMs;
549
+ const inboundInitialWaitMs = isReplay
550
+ ? 0
551
+ : callbacks.inboundConstants.initialWaitMs;
552
+ // Track whether reconstruction already emitted the route's
553
+ // intermediate Push-success marker (299-99 / 199-99-99). When it has,
554
+ // skip re-emitting from the outbound branch below to avoid duplicates.
555
+ // This flag is only true when `this` response was created by
556
+ // trackTransaction() and the reconstructed stream already fired.
557
+ const alreadyReconstructed = universalTxResponse._eventsReconstructed === true;
558
+ let intermediateAlreadyEmitted = alreadyReconstructed;
559
+ // Build the base receipt directly from this response. Any per-call
560
+ // `tx.progressHook(cb)` already replayed buffered execute-phase events
561
+ // to `cb` via the progressHook setter — re-running trackTransaction
562
+ // here would duplicate the reconstructed stream on top of that replay,
563
+ // which is exactly what broke R1 parity. Wait-phase events (209-xx /
564
+ // 299-xx / 399-xx) still reach `registeredProgressHook` and
565
+ // `ctx.progressHook` via the `emit` closure in the outbound branch.
566
+ const receipt = yield tx.wait();
567
+ let baseReceipt = callbacks.transformToUniversalTxReceipt(receipt, universalTxResponse);
470
568
  // If outbound route, poll for external chain details
471
- const routeInfo = universalTxResponse.route
472
- ? (0, route_detector_1.getRouteInfo)(universalTxResponse.route)
473
- : undefined;
569
+ const route = universalTxResponse.route;
570
+ const routeInfo = route ? (0, route_detector_1.getRouteInfo)(route) : undefined;
474
571
  if (routeInfo === null || routeInfo === void 0 ? void 0 : routeInfo.isOutbound) {
572
+ // Pick route-specific hook builders. R4 (CEA_TO_CEA) currently has
573
+ // no spec'd IDs — pickWaitHooks returns a no-op set that yields
574
+ // events with `id: ''`. `emit` drops those so the consumer never
575
+ // sees placeholder events, while receipt mutation still runs.
576
+ const hooks = (0, progress_route_hooks_1.pickWaitHooks)(route);
577
+ const targetChain = ((_e = universalTxResponse.chain) !== null && _e !== void 0 ? _e : 'external');
578
+ const emit = (event) => {
579
+ if (!event.id)
580
+ return;
581
+ fanOut(event, registeredProgressHook, ctx.progressHook);
582
+ };
583
+ // Intermediate INFO marker — Push Chain tx confirmed, external/inbound
584
+ // leg still pending. Per spec, R1 keeps 199-01 as terminal; R2/R3
585
+ // reclassify Push success as intermediate. Skip when trackTransaction
586
+ // (called above) already reconstructed the same marker via
587
+ // reconstructR2/R3, otherwise the consumer sees 299-99 / 199-99-99
588
+ // twice in their stream.
589
+ if (hooks.intermediatePushOk && !intermediateAlreadyEmitted) {
590
+ emit(hooks.intermediatePushOk(targetChain, tx.hash));
591
+ }
592
+ // Awaiting relay
593
+ emit(hooks.awaiting(targetChain));
594
+ let lastEmittedStatus;
595
+ const outboundTranslator = (event) => {
596
+ if (event.status === 'polling' && lastEmittedStatus !== 'polling') {
597
+ lastEmittedStatus = 'polling';
598
+ emit(hooks.polling(targetChain, event.elapsed));
599
+ }
600
+ };
475
601
  try {
476
602
  const outboundDetails = yield callbacks.waitForOutboundTx(tx.hash, {
477
- initialWaitMs: callbacks.outboundConstants.initialWaitMs,
478
- pollingIntervalMs: callbacks.outboundConstants.pollingIntervalMs,
479
- timeout: callbacks.outboundConstants.maxTimeoutMs,
603
+ initialWaitMs: outboundInitialWaitMs,
604
+ pollingIntervalMs: outboundPollingIntervalMs,
605
+ timeout: outboundTimeoutMs,
606
+ progressHook: outboundTranslator,
480
607
  });
608
+ emit(hooks.success(outboundDetails));
481
609
  // Merge external chain details into receipt
482
- baseReceipt = Object.assign(Object.assign({}, baseReceipt), { externalTxHash: outboundDetails.externalTxHash, externalChain: outboundDetails.destinationChain, externalExplorerUrl: outboundDetails.explorerUrl, externalRecipient: outboundDetails.recipient, externalAmount: outboundDetails.amount, externalAssetAddr: outboundDetails.assetAddr });
610
+ baseReceipt = Object.assign(Object.assign({}, baseReceipt), { externalTxHash: outboundDetails.externalTxHash, externalChain: outboundDetails.destinationChain, externalExplorerUrl: outboundDetails.explorerUrl, externalRecipient: outboundDetails.recipient, externalAmount: outboundDetails.amount, externalAssetAddr: outboundDetails.assetAddr, externalStatus: 'success' });
611
+ // R3 round-trip: poll Push Chain for the inbound tx that the
612
+ // source-chain CEA's sendUniversalTxToUEA call produces. Correlate
613
+ // via the outbound external tx hash → child UTX id → child pcTx.
614
+ // Only run when the CEA payload actually fires sendUniversalTxToUEA;
615
+ // payload-only R3 (no funds flowing back) has no child UTX and would
616
+ // otherwise time out after 300s.
617
+ if (route === route_detector_1.TransactionRoute.CEA_TO_PUSH &&
618
+ universalTxResponse._expectsInboundRoundTrip === true) {
619
+ emit(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_310_01](targetChain));
620
+ let inboundLastEmitted;
621
+ try {
622
+ const inbound = yield (0, inbound_tracker_1.waitForInboundPushTx)(ctx, outboundDetails.externalTxHash, targetChain, {
623
+ initialWaitMs: inboundInitialWaitMs,
624
+ pollingIntervalMs: callbacks.inboundConstants.pollingIntervalMs,
625
+ timeout: inboundTimeoutMs,
626
+ progressHook: (event) => {
627
+ if (event.status === 'polling' &&
628
+ inboundLastEmitted !== 'polling') {
629
+ inboundLastEmitted = 'polling';
630
+ emit(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_310_02](targetChain, event.elapsedMs));
631
+ }
632
+ },
633
+ });
634
+ if (inbound.status === 'confirmed') {
635
+ emit(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_399_01](targetChain, inbound.pushTxHash, baseReceipt));
636
+ baseReceipt = Object.assign(Object.assign({}, baseReceipt), { pushInboundTxHash: inbound.pushTxHash, pushInboundUtxId: inbound.childUtxId });
637
+ }
638
+ else {
639
+ // status === 'failed' (terminal failure status from chain)
640
+ const failMsg = (_f = inbound.errorMessage) !== null && _f !== void 0 ? _f : 'inbound execution failed on Push Chain';
641
+ emit(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_399_02](failMsg, 'inbound', targetChain));
642
+ baseReceipt = Object.assign(Object.assign({}, baseReceipt), { pushInboundUtxId: inbound.childUtxId, pushInboundTxHash: inbound.pushTxHash || undefined, externalStatus: 'failed', externalError: failMsg });
643
+ }
644
+ }
645
+ catch (inboundErr) {
646
+ const errMsg = inboundErr instanceof Error
647
+ ? inboundErr.message
648
+ : String(inboundErr);
649
+ const isTimeout = inboundErr instanceof inbound_tracker_1.InboundTimeoutError;
650
+ // Annotate receipt for inbound-leg outcome too.
651
+ baseReceipt = Object.assign(Object.assign({}, baseReceipt), { externalStatus: isTimeout ? 'timeout' : 'failed', externalError: errMsg });
652
+ emit(isTimeout
653
+ ? progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_399_03](targetChain, inboundTimeoutMs, 'inbound')
654
+ : progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_399_02](errMsg, 'inbound', targetChain));
655
+ callbacks.printLog(`[wait] R3 inbound tracking failed: ${errMsg}`);
656
+ }
657
+ }
483
658
  }
484
659
  catch (error) {
485
- // Outbound polling timed out - return partial receipt (don't throw)
486
- // Push Chain tx succeeded, external tracking can be retried later
487
- callbacks.printLog(`[wait] External chain tracking failed: ${error instanceof Error ? error.message : String(error)}`);
660
+ const errMsg = error instanceof Error ? error.message : String(error);
661
+ const isTimeout = error instanceof outbound_sync_1.OutboundTimeoutError;
662
+ emit(isTimeout
663
+ ? hooks.timeout(targetChain, outboundTimeoutMs)
664
+ : hooks.failed(targetChain, errMsg));
665
+ // Annotate the receipt so callers can distinguish external-leg
666
+ // outcomes without sniffing errMsg. Push Chain leg still succeeded
667
+ // (status stays 1); inspect `externalStatus` / `externalError` for
668
+ // the external/inbound leg outcome. On a reverted outbound the
669
+ // observed tx hash is carried by OutboundFailedError — expose it
670
+ // as externalTxHash so consumers can link to the explorer.
671
+ const failedExternalTxHash = error instanceof outbound_sync_1.OutboundFailedError ? error.externalTxHash : undefined;
672
+ baseReceipt = Object.assign(Object.assign(Object.assign({}, baseReceipt), { externalStatus: isTimeout ? 'timeout' : 'failed', externalError: errMsg }), (failedExternalTxHash
673
+ ? { externalTxHash: failedExternalTxHash }
674
+ : {}));
675
+ // Outbound polling timed out or failed — return the annotated
676
+ // receipt without throwing. Push Chain tx succeeded, external
677
+ // tracking can be retried later.
678
+ callbacks.printLog(`[wait] External chain tracking ${isTimeout ? 'timed out' : 'failed'}: ${errMsg}`);
488
679
  }
489
680
  }
490
681
  return baseReceipt;
491
682
  }),
683
+ // Internal: register a wait-phase progressHook without replaying
684
+ // buffered events. Used by trackTransaction() after it has already
685
+ // emitted the reconstructed stream to the caller's per-call hook,
686
+ // so that tracked.wait() can deliver wait-phase events (209-xx /
687
+ // 299-xx / 399-xx) to the same callback without duplicating replay.
688
+ _setProgressHookNoReplay: (callback) => {
689
+ registeredProgressHook = callback;
690
+ },
492
691
  progressHook: (callback) => {
493
692
  registeredProgressHook = callback;
494
693
  // Immediately replay buffered events from execution