@pushchain/core 6.0.20 → 6.0.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/package.json +1 -1
  3. package/src/lib/cache/cache.js +2 -0
  4. package/src/lib/constants/abi/index.d.ts +1 -0
  5. package/src/lib/constants/abi/index.js +8 -1
  6. package/src/lib/constants/abi/pc20.evm.d.ts +217 -0
  7. package/src/lib/constants/abi/pc20.evm.js +150 -0
  8. package/src/lib/constants/abi/prc20.evm.d.ts +172 -0
  9. package/src/lib/constants/abi/prc20.evm.js +97 -0
  10. package/src/lib/constants/abi/universalGatewayV0.json +1 -1
  11. package/src/lib/constants/chain.js +38 -8
  12. package/src/lib/constants/selectors.js +2 -2
  13. package/src/lib/constants/tokens.js +4 -3
  14. package/src/lib/formatters.js +16 -6
  15. package/src/lib/generated/uexecutor/v1/query.js +2 -4
  16. package/src/lib/generated/uexecutor/v1/types.d.ts +14 -0
  17. package/src/lib/generated/uexecutor/v1/types.js +90 -47
  18. package/src/lib/generated/uexecutor/v2/query.js +2 -4
  19. package/src/lib/generated/uexecutor/v2/types.d.ts +20 -0
  20. package/src/lib/generated/uexecutor/v2/types.js +49 -33
  21. package/src/lib/generated/v1/tx.js +30 -37
  22. package/src/lib/index.d.ts +3 -0
  23. package/src/lib/index.js +21 -3
  24. package/src/lib/internal/buffer.js +1 -2
  25. package/src/lib/orchestrator/cea-utils.js +73 -83
  26. package/src/lib/orchestrator/internals/account-manager.js +130 -139
  27. package/src/lib/orchestrator/internals/cascade.d.ts +0 -6
  28. package/src/lib/orchestrator/internals/cascade.js +1184 -1059
  29. package/src/lib/orchestrator/internals/confirmation.js +138 -148
  30. package/src/lib/orchestrator/internals/error-decoder.js +1 -1
  31. package/src/lib/orchestrator/internals/errors.js +12 -3
  32. package/src/lib/orchestrator/internals/execute-funds-only.d.ts +2 -2
  33. package/src/lib/orchestrator/internals/execute-funds-only.js +297 -259
  34. package/src/lib/orchestrator/internals/execute-funds-payload.d.ts +2 -2
  35. package/src/lib/orchestrator/internals/execute-funds-payload.js +220 -220
  36. package/src/lib/orchestrator/internals/execute-standard.d.ts +0 -8
  37. package/src/lib/orchestrator/internals/execute-standard.js +339 -342
  38. package/src/lib/orchestrator/internals/gas-calculator.js +497 -520
  39. package/src/lib/orchestrator/internals/gas-usd-sizer.js +101 -110
  40. package/src/lib/orchestrator/internals/gateway-client.js +102 -103
  41. package/src/lib/orchestrator/internals/helpers.js +30 -27
  42. package/src/lib/orchestrator/internals/inbound-tracker.js +234 -238
  43. package/src/lib/orchestrator/internals/outbound-sync.js +463 -452
  44. package/src/lib/orchestrator/internals/outbound-tracker.js +27 -34
  45. package/src/lib/orchestrator/internals/payload-builder.d.ts +2 -2
  46. package/src/lib/orchestrator/internals/payload-builder.js +119 -100
  47. package/src/lib/orchestrator/internals/pc-usd-oracle.js +121 -129
  48. package/src/lib/orchestrator/internals/pc20/address-codec.d.ts +31 -0
  49. package/src/lib/orchestrator/internals/pc20/address-codec.js +124 -0
  50. package/src/lib/orchestrator/internals/pc20/chain-namespace.d.ts +68 -0
  51. package/src/lib/orchestrator/internals/pc20/chain-namespace.js +136 -0
  52. package/src/lib/orchestrator/internals/pc20/errors.d.ts +160 -0
  53. package/src/lib/orchestrator/internals/pc20/errors.js +236 -0
  54. package/src/lib/orchestrator/internals/pc20/export.d.ts +172 -0
  55. package/src/lib/orchestrator/internals/pc20/export.js +412 -0
  56. package/src/lib/orchestrator/internals/pc20/gate.d.ts +67 -0
  57. package/src/lib/orchestrator/internals/pc20/gate.js +184 -0
  58. package/src/lib/orchestrator/internals/pc20/resolver.d.ts +187 -0
  59. package/src/lib/orchestrator/internals/pc20/resolver.js +658 -0
  60. package/src/lib/orchestrator/internals/pc20/svm.d.ts +189 -0
  61. package/src/lib/orchestrator/internals/pc20/svm.js +286 -0
  62. package/src/lib/orchestrator/internals/pc20/tracking.d.ts +46 -0
  63. package/src/lib/orchestrator/internals/pc20/tracking.js +109 -0
  64. package/src/lib/orchestrator/internals/preflight.js +1 -1
  65. package/src/lib/orchestrator/internals/progress-route-hooks.js +2 -2
  66. package/src/lib/orchestrator/internals/push-chain-tx.js +319 -329
  67. package/src/lib/orchestrator/internals/quote.js +90 -94
  68. package/src/lib/orchestrator/internals/rescue.js +76 -79
  69. package/src/lib/orchestrator/internals/response-builder.d.ts +28 -1
  70. package/src/lib/orchestrator/internals/response-builder.js +817 -701
  71. package/src/lib/orchestrator/internals/route-handlers.d.ts +7 -15
  72. package/src/lib/orchestrator/internals/route-handlers.js +1512 -1178
  73. package/src/lib/orchestrator/internals/signing.js +78 -83
  74. package/src/lib/orchestrator/internals/svm-bridge.d.ts +2 -2
  75. package/src/lib/orchestrator/internals/svm-bridge.js +161 -93
  76. package/src/lib/orchestrator/internals/svm-helpers.d.ts +15 -1
  77. package/src/lib/orchestrator/internals/svm-helpers.js +68 -24
  78. package/src/lib/orchestrator/internals/svm-rent.d.ts +19 -1
  79. package/src/lib/orchestrator/internals/svm-rent.js +163 -141
  80. package/src/lib/orchestrator/internals/tx-transformer.js +26 -23
  81. package/src/lib/orchestrator/internals/uea-manager.js +89 -86
  82. package/src/lib/orchestrator/max-pc-for-gas.js +5 -6
  83. package/src/lib/orchestrator/orchestrator.js +217 -200
  84. package/src/lib/orchestrator/orchestrator.types.d.ts +121 -1
  85. package/src/lib/orchestrator/orchestrator.types.js +19 -0
  86. package/src/lib/orchestrator/payload-builders.d.ts +2 -2
  87. package/src/lib/orchestrator/payload-builders.js +26 -24
  88. package/src/lib/orchestrator/route-detector.js +26 -9
  89. package/src/lib/orchestrator/svm-idl/ix-encoder.js +1 -2
  90. package/src/lib/orchestrator/svm-idl/normalize-address.js +2 -2
  91. package/src/lib/orchestrator/svm-idl/resolve.js +1 -2
  92. package/src/lib/price-fetch/price-fetch.js +67 -68
  93. package/src/lib/progress-hook/progress-hook.js +124 -72
  94. package/src/lib/push-chain/push-chain.js +171 -153
  95. package/src/lib/push-client/push-client.js +223 -237
  96. package/src/lib/universal/account/account.js +349 -365
  97. package/src/lib/universal/signer/signer.js +306 -294
  98. package/src/lib/universal-tx-detector/auto-detect.js +55 -60
  99. package/src/lib/universal-tx-detector/cascade.js +160 -172
  100. package/src/lib/universal-tx-detector/child-inbounds.js +67 -75
  101. package/src/lib/universal-tx-detector/classify.js +2 -3
  102. package/src/lib/universal-tx-detector/detector-svm.js +139 -147
  103. package/src/lib/universal-tx-detector/detector.js +145 -157
  104. package/src/lib/universal-tx-detector/normalize.js +7 -7
  105. package/src/lib/utils/external-tx-hash.js +1 -2
  106. package/src/lib/utils.d.ts +44 -0
  107. package/src/lib/utils.js +762 -683
  108. package/src/lib/vm-client/evm-client.js +186 -202
  109. package/src/lib/vm-client/svm-client.d.ts +1 -1
  110. package/src/lib/vm-client/svm-client.js +220 -224
  111. package/src/lib/vm-client/vm-client.types.d.ts +14 -2
@@ -9,7 +9,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
9
9
  exports.OutboundFailedError = exports.OutboundTimeoutError = exports.INBOUND_INITIAL_WAIT_MS = exports.INBOUND_MAX_TIMEOUT_MS = exports.OUTBOUND_MAX_TIMEOUT_MS = exports.OUTBOUND_POLL_INTERVAL_MS = exports.OUTBOUND_INITIAL_WAIT_MS = void 0;
10
10
  exports.waitForOutboundTx = waitForOutboundTx;
11
11
  exports.waitForAllOutboundTxsV2 = waitForAllOutboundTxsV2;
12
- const tslib_1 = require("tslib");
12
+ const tracking_1 = require("./pc20/tracking");
13
13
  const viem_1 = require("viem");
14
14
  const chain_1 = require("../../constants/chain");
15
15
  const enums_1 = require("../../constants/enums");
@@ -42,9 +42,11 @@ exports.INBOUND_INITIAL_WAIT_MS = 15000;
42
42
  * (→ 299-02) without resorting to error-message prefix matching.
43
43
  */
44
44
  class OutboundTimeoutError extends Error {
45
+ code = 'OUTBOUND_TIMEOUT';
46
+ pushChainTxHash;
47
+ elapsedMs;
45
48
  constructor(pushChainTxHash, elapsedMs, timeoutMs) {
46
49
  super(`Timeout waiting for outbound transaction. Push Chain TX: ${pushChainTxHash}. Timeout: ${timeoutMs}ms. The relay may still be processing.`);
47
- this.code = 'OUTBOUND_TIMEOUT';
48
50
  this.name = 'OutboundTimeoutError';
49
51
  this.pushChainTxHash = pushChainTxHash;
50
52
  this.elapsedMs = elapsedMs;
@@ -57,9 +59,15 @@ exports.OutboundTimeoutError = OutboundTimeoutError;
57
59
  * a *failure* (→ 299-02) from a *timeout* (→ 299-03).
58
60
  */
59
61
  class OutboundFailedError extends Error {
62
+ code = 'OUTBOUND_FAILED';
63
+ pushChainTxHash;
64
+ destinationChain;
65
+ /** Observed tx hash on the destination chain (when available). Lets the
66
+ * receipt expose the reverted external tx so consumers can link to the
67
+ * explorer without parsing the error message. */
68
+ externalTxHash;
60
69
  constructor(message, pushChainTxHash, destinationChain, externalTxHash) {
61
70
  super(message);
62
- this.code = 'OUTBOUND_FAILED';
63
71
  this.name = 'OutboundFailedError';
64
72
  this.pushChainTxHash = pushChainTxHash;
65
73
  this.destinationChain = destinationChain;
@@ -84,48 +92,45 @@ exports.OutboundFailedError = OutboundFailedError;
84
92
  *
85
93
  * EVM-only — SVM tiebreaker would need a separate signature lookup path.
86
94
  */
87
- function verifyExternalEvmReceipt(ctx, destinationChain, externalTxHash) {
88
- return tslib_1.__awaiter(this, void 0, void 0, function* () {
89
- var _a;
90
- const chainInfo = chain_1.CHAIN_INFO[destinationChain];
91
- if (!chainInfo || chainInfo.vm !== enums_1.VM.EVM) {
92
- return 'unsupported';
93
- }
94
- // Guard against unit-test contexts that don't pass a real rpcUrls map —
95
- // we'd otherwise hit live testnet RPC inside Jest.
96
- if (!ctx.rpcUrls)
97
- return 'unsupported';
98
- const rpcUrls = ((_a = ctx.rpcUrls) === null || _a === void 0 ? void 0 : _a[destinationChain]) && ctx.rpcUrls[destinationChain].length > 0
99
- ? ctx.rpcUrls[destinationChain]
100
- : chainInfo.defaultRPC;
101
- if (!(rpcUrls === null || rpcUrls === void 0 ? void 0 : rpcUrls.length))
102
- return 'unsupported';
103
- try {
104
- const client = (0, viem_1.createPublicClient)({
105
- transport: (0, viem_1.fallback)(rpcUrls.map((url) => (0, viem_1.http)(url))),
106
- });
107
- const receipt = yield client.getTransactionReceipt({
108
- hash: externalTxHash,
109
- });
110
- if (!receipt)
111
- return 'pending';
112
- if (receipt.status === 'success')
113
- return 'success';
114
- if (receipt.status === 'reverted')
115
- return 'reverted';
95
+ async function verifyExternalEvmReceipt(ctx, destinationChain, externalTxHash) {
96
+ const chainInfo = chain_1.CHAIN_INFO[destinationChain];
97
+ if (!chainInfo || chainInfo.vm !== enums_1.VM.EVM) {
98
+ return 'unsupported';
99
+ }
100
+ // Guard against unit-test contexts that don't pass a real rpcUrls map —
101
+ // we'd otherwise hit live testnet RPC inside Jest.
102
+ if (!ctx.rpcUrls)
103
+ return 'unsupported';
104
+ const rpcUrls = ctx.rpcUrls?.[destinationChain] && ctx.rpcUrls[destinationChain].length > 0
105
+ ? ctx.rpcUrls[destinationChain]
106
+ : chainInfo.defaultRPC;
107
+ if (!rpcUrls?.length)
108
+ return 'unsupported';
109
+ try {
110
+ const client = (0, viem_1.createPublicClient)({
111
+ transport: (0, viem_1.fallback)(rpcUrls.map((url) => (0, viem_1.http)(url))),
112
+ });
113
+ const receipt = await client.getTransactionReceipt({
114
+ hash: externalTxHash,
115
+ });
116
+ if (!receipt)
116
117
  return 'pending';
117
- }
118
- catch (err) {
119
- // viem throws TransactionReceiptNotFoundError when the tx isn't mined
120
- // yet — treat as 'pending' rather than 'unsupported' so we keep polling.
121
- const msg = err instanceof Error ? err.message : String(err);
122
- if (/not found|TransactionReceiptNotFound|could not be found/i.test(msg)) {
123
- return 'pending';
124
- }
125
- (0, context_1.printLog)(ctx, `[verifyExternalEvmReceipt] RPC error for ${destinationChain} tx ${externalTxHash}: ${msg}`);
118
+ if (receipt.status === 'success')
119
+ return 'success';
120
+ if (receipt.status === 'reverted')
121
+ return 'reverted';
122
+ return 'pending';
123
+ }
124
+ catch (err) {
125
+ // viem throws TransactionReceiptNotFoundError when the tx isn't mined
126
+ // yet — treat as 'pending' rather than 'unsupported' so we keep polling.
127
+ const msg = err instanceof Error ? err.message : String(err);
128
+ if (/not found|TransactionReceiptNotFound|could not be found/i.test(msg)) {
126
129
  return 'pending';
127
130
  }
128
- });
131
+ (0, context_1.printLog)(ctx, `[verifyExternalEvmReceipt] RPC error for ${destinationChain} tx ${externalTxHash}: ${msg}`);
132
+ return 'pending';
133
+ }
129
134
  }
130
135
  /**
131
136
  * Wait for outbound transaction to complete and return external chain details.
@@ -140,148 +145,370 @@ function verifyExternalEvmReceipt(ctx, destinationChain, externalTxHash) {
140
145
  * @returns External chain tx details
141
146
  * @throws Error on timeout
142
147
  */
143
- function waitForOutboundTx(ctx_1, pushChainTxHash_1) {
144
- return tslib_1.__awaiter(this, arguments, void 0, function* (ctx, pushChainTxHash, options = {}) {
145
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s;
146
- const { initialWaitMs = exports.OUTBOUND_INITIAL_WAIT_MS, pollingIntervalMs = exports.OUTBOUND_POLL_INTERVAL_MS, timeout = exports.OUTBOUND_MAX_TIMEOUT_MS, progressHook, _resolvedSubTxId, _expectedDestinationChain, _outboundIndex, } = options;
147
- // Terminal failure states — fail fast instead of polling until timeout
148
- const TERMINAL_FAILURE_STATES = new Set([
149
- types_1.UniversalTxStatus.OUTBOUND_FAILED,
150
- types_1.UniversalTxStatus.PC_EXECUTED_FAILED,
151
- types_1.UniversalTxStatus.CANCELED,
152
- ]);
153
- const startTime = Date.now();
154
- (0, context_1.printLog)(ctx, `[waitForOutboundTx] Starting | txHash: ${pushChainTxHash} | initialWait: ${initialWaitMs}ms | pollInterval: ${pollingIntervalMs}ms | timeout: ${timeout}ms`);
155
- progressHook === null || progressHook === void 0 ? void 0 : progressHook({ status: 'waiting', elapsed: 0 });
156
- // Clamp the initial wait to the configured timeout so a short per-call
157
- // `outboundTimeoutMs` (e.g. from `tx.wait({ outboundTimeoutMs })`) isn't
158
- // blocked by the default 20s settle-time. Mirrors the equivalent
159
- // Math.min(initialWaitMs, timeout) clamp in inbound-tracker.ts.
160
- const effectiveInitialWaitMs = Math.min(initialWaitMs, timeout);
161
- (0, context_1.printLog)(ctx, `[waitForOutboundTx] Initial wait of ${effectiveInitialWaitMs}ms (configured: ${initialWaitMs}ms, clamped to timeout ${timeout}ms)...`);
162
- yield new Promise((resolve) => setTimeout(resolve, effectiveInitialWaitMs));
163
- // Fast-exit: if the clamped initial wait already consumed the full budget,
164
- // skip the poll loop and go straight to the timeout throw below.
165
- if (Date.now() - startTime >= timeout) {
166
- const elapsedMs = Date.now() - startTime;
167
- (0, context_1.printLog)(ctx, `[waitForOutboundTx] Timeout reached during initial wait (elapsed: ${elapsedMs}ms / ${timeout}ms)`);
168
- progressHook === null || progressHook === void 0 ? void 0 : progressHook({ status: 'timeout', elapsed: elapsedMs });
169
- throw new OutboundTimeoutError(pushChainTxHash, elapsedMs, timeout);
170
- }
171
- // Start polling
172
- (0, context_1.printLog)(ctx, `[waitForOutboundTx] Initial wait done. Starting polling. Elapsed: ${Date.now() - startTime}ms`);
173
- progressHook === null || progressHook === void 0 ? void 0 : progressHook({ status: 'polling', elapsed: Date.now() - startTime });
174
- // Cache the universalSubTxId after first extraction to avoid redundant receipt fetches.
175
- // If a pre-resolved ID was provided (cascade per-hop tracking), use it directly.
176
- let cachedUniversalSubTxId = _resolvedSubTxId;
177
- let pollCount = 0;
178
- while (Date.now() - startTime < timeout) {
179
- pollCount++;
180
- const pollStart = Date.now();
181
- (0, context_1.printLog)(ctx, `[waitForOutboundTx] Poll #${pollCount} | Elapsed: ${pollStart - startTime}ms / ${timeout}ms`);
148
+ async function waitForOutboundTx(ctx, pushChainTxHash, options = {}) {
149
+ const { initialWaitMs = exports.OUTBOUND_INITIAL_WAIT_MS, pollingIntervalMs = exports.OUTBOUND_POLL_INTERVAL_MS, timeout = exports.OUTBOUND_MAX_TIMEOUT_MS, progressHook, _resolvedSubTxId, _expectedDestinationChain, _outboundIndex, } = options;
150
+ // Terminal failure states — fail fast instead of polling until timeout
151
+ const TERMINAL_FAILURE_STATES = new Set([
152
+ types_1.UniversalTxStatus.OUTBOUND_FAILED,
153
+ types_1.UniversalTxStatus.PC_EXECUTED_FAILED,
154
+ types_1.UniversalTxStatus.CANCELED,
155
+ ]);
156
+ const startTime = Date.now();
157
+ (0, context_1.printLog)(ctx, `[waitForOutboundTx] Starting | txHash: ${pushChainTxHash} | initialWait: ${initialWaitMs}ms | pollInterval: ${pollingIntervalMs}ms | timeout: ${timeout}ms`);
158
+ progressHook?.({ status: 'waiting', elapsed: 0 });
159
+ // Clamp the initial wait to the configured timeout so a short per-call
160
+ // `outboundTimeoutMs` (e.g. from `tx.wait({ outboundTimeoutMs })`) isn't
161
+ // blocked by the default 20s settle-time. Mirrors the equivalent
162
+ // Math.min(initialWaitMs, timeout) clamp in inbound-tracker.ts.
163
+ const effectiveInitialWaitMs = Math.min(initialWaitMs, timeout);
164
+ (0, context_1.printLog)(ctx, `[waitForOutboundTx] Initial wait of ${effectiveInitialWaitMs}ms (configured: ${initialWaitMs}ms, clamped to timeout ${timeout}ms)...`);
165
+ await new Promise((resolve) => setTimeout(resolve, effectiveInitialWaitMs));
166
+ // Fast-exit: if the clamped initial wait already consumed the full budget,
167
+ // skip the poll loop and go straight to the timeout throw below.
168
+ if (Date.now() - startTime >= timeout) {
169
+ const elapsedMs = Date.now() - startTime;
170
+ (0, context_1.printLog)(ctx, `[waitForOutboundTx] Timeout reached during initial wait (elapsed: ${elapsedMs}ms / ${timeout}ms)`);
171
+ progressHook?.({ status: 'timeout', elapsed: elapsedMs });
172
+ throw new OutboundTimeoutError(pushChainTxHash, elapsedMs, timeout);
173
+ }
174
+ // Start polling
175
+ (0, context_1.printLog)(ctx, `[waitForOutboundTx] Initial wait done. Starting polling. Elapsed: ${Date.now() - startTime}ms`);
176
+ progressHook?.({ status: 'polling', elapsed: Date.now() - startTime });
177
+ // Cache the universalSubTxId after first extraction to avoid redundant receipt fetches.
178
+ // If a pre-resolved ID was provided (cascade per-hop tracking), use it directly.
179
+ let cachedUniversalSubTxId = _resolvedSubTxId;
180
+ let pollCount = 0;
181
+ while (Date.now() - startTime < timeout) {
182
+ pollCount++;
183
+ const pollStart = Date.now();
184
+ (0, context_1.printLog)(ctx, `[waitForOutboundTx] Poll #${pollCount} | Elapsed: ${pollStart - startTime}ms / ${timeout}ms`);
185
+ if (!cachedUniversalSubTxId) {
186
+ cachedUniversalSubTxId =
187
+ (await (0, outbound_tracker_1.extractUniversalSubTxIdFromTx)(ctx, pushChainTxHash)) ??
188
+ undefined;
182
189
  if (!cachedUniversalSubTxId) {
183
- cachedUniversalSubTxId =
184
- (_a = (yield (0, outbound_tracker_1.extractUniversalSubTxIdFromTx)(ctx, pushChainTxHash))) !== null && _a !== void 0 ? _a : undefined;
185
- if (!cachedUniversalSubTxId) {
186
- cachedUniversalSubTxId = (0, outbound_tracker_1.computeUniversalTxId)(ctx.pushNetwork, pushChainTxHash);
187
- }
188
- (0, context_1.printLog)(ctx, `[waitForOutboundTx] Extracted & cached universalSubTxId: ${cachedUniversalSubTxId}`);
190
+ cachedUniversalSubTxId = (0, outbound_tracker_1.computeUniversalTxId)(ctx.pushNetwork, pushChainTxHash);
189
191
  }
190
- // Query with cached ID
191
- const queryId = cachedUniversalSubTxId.startsWith('0x')
192
- ? cachedUniversalSubTxId.slice(2)
193
- : cachedUniversalSubTxId;
194
- try {
195
- const utxResponse = yield ctx.pushClient.getUniversalTxByIdV2(queryId);
196
- const statusNum = (_b = utxResponse === null || utxResponse === void 0 ? void 0 : utxResponse.universalTx) === null || _b === void 0 ? void 0 : _b.universalStatus;
197
- const statusName = (_c = types_1.UniversalTxStatus[statusNum]) !== null && _c !== void 0 ? _c : statusNum;
198
- const outbounds = ((_d = utxResponse === null || utxResponse === void 0 ? void 0 : utxResponse.universalTx) === null || _d === void 0 ? void 0 : _d.outboundTx) || [];
199
- const firstOutbound = outbounds[0];
200
- const firstOutboundChain = (firstOutbound === null || firstOutbound === void 0 ? void 0 : firstOutbound.destinationChain)
201
- ? (0, helpers_1.chainFromNamespace)(firstOutbound.destinationChain)
202
- : undefined;
203
- const firstDisplayTxHash = (_h = (_f = (0, helpers_1.toExternalTxHashDisplay)(firstOutboundChain !== null && firstOutboundChain !== void 0 ? firstOutboundChain : undefined, (_e = firstOutbound === null || firstOutbound === void 0 ? void 0 : firstOutbound.observedTx) === null || _e === void 0 ? void 0 : _e.txHash)) !== null && _f !== void 0 ? _f : (_g = firstOutbound === null || firstOutbound === void 0 ? void 0 : firstOutbound.observedTx) === null || _g === void 0 ? void 0 : _g.txHash) !== null && _h !== void 0 ? _h : '';
204
- (0, context_1.printLog)(ctx, `[waitForOutboundTx] Poll #${pollCount} | status: ${statusNum} (${statusName}) | outboundTx count: ${outbounds.length} | first txHash: '${firstDisplayTxHash}' | first dest: '${(firstOutbound === null || firstOutbound === void 0 ? void 0 : firstOutbound.destinationChain) || ''}'`);
205
- // Check for terminal failure states — fail fast
206
- if (TERMINAL_FAILURE_STATES.has(statusNum)) {
207
- (0, context_1.printLog)(ctx, `[waitForOutboundTx] Terminal failure state: ${statusName}`);
208
- progressHook === null || progressHook === void 0 ? void 0 : progressHook({ status: 'failed', elapsed: Date.now() - startTime });
209
- throw new OutboundFailedError(`Outbound transaction failed with status ${statusName}. Push Chain TX: ${pushChainTxHash}.`, pushChainTxHash);
192
+ (0, context_1.printLog)(ctx, `[waitForOutboundTx] Extracted & cached universalSubTxId: ${cachedUniversalSubTxId}`);
193
+ }
194
+ // Query with cached ID
195
+ const queryId = cachedUniversalSubTxId.startsWith('0x')
196
+ ? cachedUniversalSubTxId.slice(2)
197
+ : cachedUniversalSubTxId;
198
+ try {
199
+ const utxResponse = await ctx.pushClient.getUniversalTxByIdV2(queryId);
200
+ const statusNum = utxResponse?.universalTx?.universalStatus;
201
+ const statusName = types_1.UniversalTxStatus[statusNum] ?? statusNum;
202
+ const outbounds = utxResponse?.universalTx?.outboundTx || [];
203
+ const firstOutbound = outbounds[0];
204
+ const firstOutboundChain = firstOutbound?.destinationChain
205
+ ? (0, helpers_1.chainFromNamespace)(firstOutbound.destinationChain)
206
+ : undefined;
207
+ const firstDisplayTxHash = (0, helpers_1.toExternalTxHashDisplay)(firstOutboundChain ?? undefined, firstOutbound?.observedTx?.txHash) ??
208
+ firstOutbound?.observedTx?.txHash ??
209
+ '';
210
+ (0, context_1.printLog)(ctx, `[waitForOutboundTx] Poll #${pollCount} | status: ${statusNum} (${statusName}) | outboundTx count: ${outbounds.length} | first txHash: '${firstDisplayTxHash}' | first dest: '${firstOutbound?.destinationChain || ''}'`);
211
+ // Check for terminal failure states — fail fast
212
+ if (TERMINAL_FAILURE_STATES.has(statusNum)) {
213
+ (0, context_1.printLog)(ctx, `[waitForOutboundTx] Terminal failure state: ${statusName}`);
214
+ progressHook?.({ status: 'failed', elapsed: Date.now() - startTime });
215
+ throw new OutboundFailedError(`Outbound transaction failed with status ${statusName}. Push Chain TX: ${pushChainTxHash}.`, pushChainTxHash);
216
+ }
217
+ const pcTxs = utxResponse?.universalTx?.pcTx || [];
218
+ const failedPcTx = pcTxs.find((pcTx) => {
219
+ const status = String(pcTx.status || '').toUpperCase();
220
+ return status === 'FAILED' || Boolean(pcTx.errorMsg);
221
+ });
222
+ if (failedPcTx) {
223
+ const failedTxHash = failedPcTx.txHash || pushChainTxHash;
224
+ const reason = failedPcTx.errorMsg
225
+ ? (0, formatters_1.normalizePcInsufficientFundsError)(failedPcTx.errorMsg)
226
+ : 'Push Chain execution failed';
227
+ (0, context_1.printLog)(ctx, `[waitForOutboundTx] Push Chain execution failed before outbound emission: ${reason}`);
228
+ progressHook?.({ status: 'failed', elapsed: Date.now() - startTime });
229
+ throw new OutboundFailedError(`Push Chain execution failed before outbound emission: ${reason}. Push Chain TX: ${failedTxHash}.`, pushChainTxHash, undefined, failedPcTx.txHash || undefined);
230
+ }
231
+ // Iterate V2 outbound array. For cascade tracking, multiple outbound
232
+ // entries can target the same destination chain (for example R2 BSC
233
+ // followed by R3 BSC -> Push). `_outboundIndex` disambiguates those
234
+ // same-chain entries by order after `_expectedDestinationChain` filtering.
235
+ let matchingOutboundIndex = 0;
236
+ for (const ob of outbounds) {
237
+ // If a destination chain filter is set, skip outbound entries that don't match.
238
+ if (_expectedDestinationChain &&
239
+ ob.destinationChain !== _expectedDestinationChain) {
240
+ (0, context_1.printLog)(ctx, `[waitForOutboundTx] Poll #${pollCount} | outbound chain '${ob.destinationChain}' does not match expected '${_expectedDestinationChain}', skipping`);
241
+ continue;
242
+ }
243
+ const currentOutboundIndex = matchingOutboundIndex;
244
+ matchingOutboundIndex += 1;
245
+ if (_outboundIndex !== undefined &&
246
+ currentOutboundIndex !== _outboundIndex) {
247
+ (0, context_1.printLog)(ctx, `[waitForOutboundTx] Poll #${pollCount} | outbound index ${currentOutboundIndex} for '${ob.destinationChain}' does not match expected index ${_outboundIndex}, skipping`);
248
+ continue;
210
249
  }
211
- const pcTxs = ((_j = utxResponse === null || utxResponse === void 0 ? void 0 : utxResponse.universalTx) === null || _j === void 0 ? void 0 : _j.pcTx) || [];
212
- const failedPcTx = pcTxs.find((pcTx) => {
213
- const status = String(pcTx.status || '').toUpperCase();
214
- return status === 'FAILED' || Boolean(pcTx.errorMsg);
215
- });
216
- if (failedPcTx) {
217
- const failedTxHash = failedPcTx.txHash || pushChainTxHash;
218
- const reason = failedPcTx.errorMsg
219
- ? (0, formatters_1.normalizePcInsufficientFundsError)(failedPcTx.errorMsg)
220
- : 'Push Chain execution failed';
221
- (0, context_1.printLog)(ctx, `[waitForOutboundTx] Push Chain execution failed before outbound emission: ${reason}`);
222
- progressHook === null || progressHook === void 0 ? void 0 : progressHook({ status: 'failed', elapsed: Date.now() - startTime });
223
- throw new OutboundFailedError(`Push Chain execution failed before outbound emission: ${reason}. Push Chain TX: ${failedTxHash}.`, pushChainTxHash, undefined, failedPcTx.txHash || undefined);
250
+ // Fail fast on per-outbound REVERTED status
251
+ if (ob.outboundStatus === types_2.OutboundStatus.REVERTED) {
252
+ (0, context_1.printLog)(ctx, `[waitForOutboundTx] Outbound to ${ob.destinationChain} REVERTED`);
253
+ progressHook?.({ status: 'failed', elapsed: Date.now() - startTime });
254
+ throw new OutboundFailedError(`Outbound to ${ob.destinationChain} reverted: ${ob.observedTx?.errorMsg || 'Unknown'}. Push Chain TX: ${pushChainTxHash}.`, pushChainTxHash, ob.destinationChain, ob.observedTx?.txHash);
224
255
  }
225
- // Iterate V2 outbound array. For cascade tracking, multiple outbound
226
- // entries can target the same destination chain (for example R2 BSC
227
- // followed by R3 BSC -> Push). `_outboundIndex` disambiguates those
228
- // same-chain entries by order after `_expectedDestinationChain` filtering.
229
- let matchingOutboundIndex = 0;
230
- for (const ob of outbounds) {
231
- // If a destination chain filter is set, skip outbound entries that don't match.
232
- if (_expectedDestinationChain &&
233
- ob.destinationChain !== _expectedDestinationChain) {
234
- (0, context_1.printLog)(ctx, `[waitForOutboundTx] Poll #${pollCount} | outbound chain '${ob.destinationChain}' does not match expected '${_expectedDestinationChain}', skipping`);
256
+ // FOUND gating logic:
257
+ // 1. Primary path — cosmos has transitioned to OBSERVED (the
258
+ // authoritative success signal from the indexer).
259
+ // 2. Tiebreaker — cosmos has the txHash but outboundStatus is
260
+ // still UNSPECIFIED/PENDING (indexer lag). We ask the
261
+ // destination chain's own RPC: if its receipt is success,
262
+ // treat as FOUND; if reverted, throw OutboundFailedError.
263
+ // Pending receipts keep polling.
264
+ if (ob.observedTx?.txHash) {
265
+ const chain = (0, helpers_1.chainFromNamespace)(ob.destinationChain);
266
+ if (!chain)
235
267
  continue;
268
+ const isCosmosObserved = ob.outboundStatus === types_2.OutboundStatus.OBSERVED;
269
+ if (!isCosmosObserved) {
270
+ // Tiebreaker: ask the destination-chain RPC directly.
271
+ const verdict = await verifyExternalEvmReceipt(ctx, chain, ob.observedTx.txHash);
272
+ if (verdict === 'reverted') {
273
+ (0, context_1.printLog)(ctx, `[waitForOutboundTx] Source-chain RPC reports REVERTED for ${ob.destinationChain} tx ${ob.observedTx.txHash} (cosmos still ${types_1.UniversalTxStatus[statusNum] ?? statusNum})`);
274
+ progressHook?.({
275
+ status: 'failed',
276
+ elapsed: Date.now() - startTime,
277
+ });
278
+ throw new OutboundFailedError(`Outbound to ${ob.destinationChain} reverted on source-chain RPC (tx: ${ob.observedTx.txHash}). Push Chain TX: ${pushChainTxHash}.`, pushChainTxHash, ob.destinationChain, ob.observedTx.txHash);
279
+ }
280
+ if (verdict !== 'success') {
281
+ // 'pending' or 'unsupported' — keep polling, cosmos will catch up.
282
+ continue;
283
+ }
284
+ (0, context_1.printLog)(ctx, `[waitForOutboundTx] Source-chain RPC tiebreaker: ${chain} tx ${ob.observedTx.txHash} succeeded; treating as FOUND despite cosmos status ${types_1.UniversalTxStatus[statusNum] ?? statusNum}`);
236
285
  }
237
- const currentOutboundIndex = matchingOutboundIndex;
238
- matchingOutboundIndex += 1;
239
- if (_outboundIndex !== undefined &&
240
- currentOutboundIndex !== _outboundIndex) {
241
- (0, context_1.printLog)(ctx, `[waitForOutboundTx] Poll #${pollCount} | outbound index ${currentOutboundIndex} for '${ob.destinationChain}' does not match expected index ${_outboundIndex}, skipping`);
242
- continue;
286
+ {
287
+ const explorerBaseUrl = chain_1.CHAIN_INFO[chain]?.explorerUrl;
288
+ const isSvm = chain_1.CHAIN_INFO[chain]?.vm === enums_1.VM.SVM;
289
+ const displayTxHash = (0, helpers_1.toExternalTxHashDisplay)(chain, ob.observedTx.txHash) ??
290
+ ob.observedTx.txHash;
291
+ let explorerUrl = '';
292
+ if (isSvm) {
293
+ const cluster = chain === enums_1.CHAIN.SOLANA_DEVNET
294
+ ? '?cluster=devnet'
295
+ : chain === enums_1.CHAIN.SOLANA_TESTNET
296
+ ? '?cluster=testnet'
297
+ : '';
298
+ explorerUrl = explorerBaseUrl
299
+ ? `${explorerBaseUrl}/tx/${displayTxHash}${cluster}`
300
+ : '';
301
+ }
302
+ else {
303
+ explorerUrl = explorerBaseUrl
304
+ ? `${explorerBaseUrl}/tx/${displayTxHash}`
305
+ : '';
306
+ }
307
+ // OutboundTxDetails carries the raw `0x`-hex form so internal
308
+ // consumers (R3 inbound tracker in response-builder.ts:786 →
309
+ // findChildUtxIdFromExternalTx → Cosmos query) keep working.
310
+ // The base58 display form is applied at every user-facing
311
+ // boundary via `toExternalTxHashDisplay` (helpers.ts).
312
+ // PC20: `externalAssetAddr` is empty when the outbound is first
313
+ // created, and a repeat export deploys no wrapper so nothing is
314
+ // ever observed to backfill it. Resolve from UniversalCore instead,
315
+ // keyed by the Push-native source token.
316
+ const pc20Wrapper = await (0, tracking_1.resolvePC20WrapperForReceipt)(ctx, ob, { network: ctx.pushNetwork, rpcUrls: ctx.rpcUrls });
317
+ const details = {
318
+ externalTxHash: ob.observedTx.txHash,
319
+ destinationChain: chain,
320
+ explorerUrl,
321
+ recipient: ob.recipient,
322
+ amount: ob.amount,
323
+ assetAddr: pc20Wrapper ?? ob.externalAssetAddr,
324
+ };
325
+ (0, context_1.printLog)(ctx, `[waitForOutboundTx] FOUND on poll #${pollCount} | elapsed: ${Date.now() - startTime}ms | externalTxHash: ${displayTxHash}`);
326
+ progressHook?.({
327
+ status: 'found',
328
+ elapsed: Date.now() - startTime,
329
+ });
330
+ return details;
243
331
  }
244
- // Fail fast on per-outbound REVERTED status
245
- if (ob.outboundStatus === types_2.OutboundStatus.REVERTED) {
246
- (0, context_1.printLog)(ctx, `[waitForOutboundTx] Outbound to ${ob.destinationChain} REVERTED`);
247
- progressHook === null || progressHook === void 0 ? void 0 : progressHook({ status: 'failed', elapsed: Date.now() - startTime });
248
- throw new OutboundFailedError(`Outbound to ${ob.destinationChain} reverted: ${((_k = ob.observedTx) === null || _k === void 0 ? void 0 : _k.errorMsg) || 'Unknown'}. Push Chain TX: ${pushChainTxHash}.`, pushChainTxHash, ob.destinationChain, (_l = ob.observedTx) === null || _l === void 0 ? void 0 : _l.txHash);
332
+ }
333
+ }
334
+ }
335
+ catch (error) {
336
+ // Re-throw typed terminal failure so the caller can classify it as
337
+ // 299-02. Transient errors (RPC hiccups, deserialization) are logged
338
+ // and the loop continues polling until timeout.
339
+ if (error instanceof OutboundFailedError) {
340
+ throw error;
341
+ }
342
+ (0, context_1.printLog)(ctx, `[waitForOutboundTx] Poll #${pollCount} ERROR: ${error instanceof Error ? error.message : String(error)}`);
343
+ }
344
+ (0, context_1.printLog)(ctx, `[waitForOutboundTx] Poll #${pollCount} not ready yet (${Date.now() - pollStart}ms). Waiting ${pollingIntervalMs}ms...`);
345
+ // Wait before next poll
346
+ await new Promise((resolve) => setTimeout(resolve, pollingIntervalMs));
347
+ }
348
+ const elapsedMs = Date.now() - startTime;
349
+ (0, context_1.printLog)(ctx, `[waitForOutboundTx] TIMEOUT after ${pollCount} polls | elapsed: ${elapsedMs}ms`);
350
+ progressHook?.({ status: 'timeout', elapsed: elapsedMs });
351
+ throw new OutboundTimeoutError(pushChainTxHash, elapsedMs, timeout);
352
+ }
353
+ // ============================================================================
354
+ // waitForAllOutboundTxsV2
355
+ // ============================================================================
356
+ /**
357
+ * Tracks ALL outbound transactions for a cascade with multiple outbound hops
358
+ * (e.g., BNB + Solana). Uses V2 API which returns outboundTx[] with per-outbound
359
+ * status tracking, matching each outbound to the correct hop by destination chain.
360
+ */
361
+ async function waitForAllOutboundTxsV2(ctx, pushChainTxHash, outboundHops, options) {
362
+ const { initialWaitMs, pollingIntervalMs, timeout, progressHook, _resolvedSubTxId, _outboundIndexByHop, } = options;
363
+ const startTime = Date.now();
364
+ // Build maps for matching outbound entries. When the cascade composer merged
365
+ // multiple logical hops into one outbound request, `_outboundIndexByHop` maps
366
+ // those hops to the same outbound ordinal.
367
+ const chainToHops = new Map();
368
+ for (const hop of outboundHops) {
369
+ const chainInfo = chain_1.CHAIN_INFO[hop.executionChain];
370
+ if (chainInfo) {
371
+ const namespace = `${chain_1.VM_NAMESPACE[chainInfo.vm]}:${chainInfo.chainId}`;
372
+ const existingByOrdinal = chainToHops.get(namespace) || new Map();
373
+ const fallbackOrdinal = [...existingByOrdinal.values()].reduce((count, hopsForOrdinal) => count + hopsForOrdinal.length, 0);
374
+ const outboundOrdinal = _outboundIndexByHop?.get(hop.hopIndex) ?? fallbackOrdinal;
375
+ const existing = existingByOrdinal.get(outboundOrdinal) || [];
376
+ existing.push(hop);
377
+ existingByOrdinal.set(outboundOrdinal, existing);
378
+ chainToHops.set(namespace, existingByOrdinal);
379
+ }
380
+ }
381
+ const expectedChains = [...chainToHops.keys()];
382
+ (0, context_1.printLog)(ctx, `[waitForAllOutboundTxsV2] Starting | txHash: ${pushChainTxHash} | expectedChains: ${expectedChains.join(', ')} | timeout: ${timeout}ms`);
383
+ // Emit initial waiting status for all outbound hops
384
+ for (const hop of outboundHops) {
385
+ progressHook?.({
386
+ hopIndex: hop.hopIndex,
387
+ route: hop.route,
388
+ chain: hop.executionChain,
389
+ status: 'waiting',
390
+ });
391
+ }
392
+ // Initial wait before first poll
393
+ const waitMs = Math.min(initialWaitMs, timeout);
394
+ (0, context_1.printLog)(ctx, `[waitForAllOutboundTxsV2] Initial wait of ${waitMs}ms...`);
395
+ await new Promise((resolve) => setTimeout(resolve, waitMs));
396
+ // Emit polling status for all hops
397
+ for (const hop of outboundHops) {
398
+ progressHook?.({
399
+ hopIndex: hop.hopIndex,
400
+ route: hop.route,
401
+ chain: hop.executionChain,
402
+ status: 'polling',
403
+ });
404
+ }
405
+ // Extract sub-tx ID for V2 query
406
+ let cachedQueryId = _resolvedSubTxId
407
+ ? _resolvedSubTxId.startsWith('0x')
408
+ ? _resolvedSubTxId.slice(2)
409
+ : _resolvedSubTxId
410
+ : undefined;
411
+ let pollCount = 0;
412
+ let consecutiveErrors = 0;
413
+ const MAX_CONSECUTIVE_ERRORS = 10;
414
+ while (Date.now() - startTime < timeout) {
415
+ pollCount++;
416
+ const elapsed = Date.now() - startTime;
417
+ (0, context_1.printLog)(ctx, `[waitForAllOutboundTxsV2] Poll #${pollCount} | Elapsed: ${elapsed}ms / ${timeout}ms`);
418
+ // Resolve query ID on first poll
419
+ if (!cachedQueryId) {
420
+ const allSubTxIds = await (0, outbound_tracker_1.extractAllUniversalSubTxIds)(ctx, pushChainTxHash);
421
+ const subTxId = allSubTxIds.length > 0
422
+ ? allSubTxIds[0]
423
+ : (0, outbound_tracker_1.computeUniversalTxId)(ctx.pushNetwork, pushChainTxHash);
424
+ cachedQueryId = subTxId.startsWith('0x') ? subTxId.slice(2) : subTxId;
425
+ (0, context_1.printLog)(ctx, `[waitForAllOutboundTxsV2] Resolved queryId: ${cachedQueryId}`);
426
+ }
427
+ try {
428
+ const v2Response = await ctx.pushClient.getUniversalTxByIdV2(cachedQueryId);
429
+ consecutiveErrors = 0; // Reset on successful RPC call
430
+ const utx = v2Response?.universalTx;
431
+ const statusNum = utx?.universalStatus;
432
+ const statusName = types_1.UniversalTxStatus[statusNum] ?? statusNum;
433
+ (0, context_1.printLog)(ctx, `[waitForAllOutboundTxsV2] Poll #${pollCount} | status: ${statusNum} (${statusName}) | outboundTx count: ${utx?.outboundTx?.length ?? 0}`);
434
+ if (utx?.outboundTx?.length) {
435
+ const outboundOrdinalByChain = new Map();
436
+ for (const ob of utx.outboundTx) {
437
+ const destChain = ob.destinationChain;
438
+ const hopsByOrdinal = chainToHops.get(destChain);
439
+ if (!hopsByOrdinal)
440
+ continue;
441
+ const outboundOrdinal = outboundOrdinalByChain.get(destChain) ?? 0;
442
+ outboundOrdinalByChain.set(destChain, outboundOrdinal + 1);
443
+ const targetHops = hopsByOrdinal.get(outboundOrdinal) || [];
444
+ const pendingTargetHops = targetHops.filter((hop) => hop.status !== 'confirmed' && hop.status !== 'failed');
445
+ if (pendingTargetHops.length === 0)
446
+ continue;
447
+ const primaryTargetHop = pendingTargetHops[0];
448
+ const externalTxHash = ob.observedTx?.txHash;
449
+ const hasObservedTxHash = !!externalTxHash && externalTxHash !== 'EMPTY';
450
+ const observedFailure = hasObservedTxHash &&
451
+ ob.observedTx?.success === false &&
452
+ Boolean(ob.observedTx?.errorMsg ||
453
+ ob.abortReason ||
454
+ ob.outboundStatus >= types_2.OutboundStatus.REVERTED);
455
+ // Fail fast on per-outbound REVERTED or observed destination failure.
456
+ if (ob.outboundStatus === types_2.OutboundStatus.REVERTED ||
457
+ observedFailure) {
458
+ for (const targetHop of pendingTargetHops) {
459
+ targetHop.status = 'failed';
460
+ progressHook?.({
461
+ hopIndex: targetHop.hopIndex,
462
+ route: targetHop.route,
463
+ chain: targetHop.executionChain,
464
+ status: 'failed',
465
+ });
466
+ }
467
+ (0, context_1.printLog)(ctx, `[waitForAllOutboundTxsV2] Outbound to ${destChain} REVERTED | outboundIndex ${outboundOrdinal} | hops ${pendingTargetHops
468
+ .map((hop) => hop.hopIndex)
469
+ .join(', ')} | error: ${ob.abortReason || ob.observedTx?.errorMsg || 'Unknown'}`);
470
+ return { success: false, failedAt: primaryTargetHop.hopIndex };
249
471
  }
250
- // FOUND gating logic:
251
- // 1. Primary path — cosmos has transitioned to OBSERVED (the
252
- // authoritative success signal from the indexer).
253
- // 2. Tiebreaker — cosmos has the txHash but outboundStatus is
254
- // still UNSPECIFIED/PENDING (indexer lag). We ask the
255
- // destination chain's own RPC: if its receipt is success,
256
- // treat as FOUND; if reverted, throw OutboundFailedError.
257
- // Pending receipts keep polling.
258
- if ((_m = ob.observedTx) === null || _m === void 0 ? void 0 : _m.txHash) {
259
- const chain = (0, helpers_1.chainFromNamespace)(ob.destinationChain);
472
+ // Check for observed tx hash. Cosmos may lag the status transition,
473
+ // so EVM destinations are verified against the destination receipt.
474
+ if (hasObservedTxHash && externalTxHash) {
475
+ const chain = (0, helpers_1.chainFromNamespace)(destChain);
260
476
  if (!chain)
261
477
  continue;
262
478
  const isCosmosObserved = ob.outboundStatus === types_2.OutboundStatus.OBSERVED;
263
479
  if (!isCosmosObserved) {
264
- // Tiebreaker: ask the destination-chain RPC directly.
265
- const verdict = yield verifyExternalEvmReceipt(ctx, chain, ob.observedTx.txHash);
480
+ const verdict = await verifyExternalEvmReceipt(ctx, chain, externalTxHash);
266
481
  if (verdict === 'reverted') {
267
- (0, context_1.printLog)(ctx, `[waitForOutboundTx] Source-chain RPC reports REVERTED for ${ob.destinationChain} tx ${ob.observedTx.txHash} (cosmos still ${(_o = types_1.UniversalTxStatus[statusNum]) !== null && _o !== void 0 ? _o : statusNum})`);
268
- progressHook === null || progressHook === void 0 ? void 0 : progressHook({
269
- status: 'failed',
270
- elapsed: Date.now() - startTime,
271
- });
272
- throw new OutboundFailedError(`Outbound to ${ob.destinationChain} reverted on source-chain RPC (tx: ${ob.observedTx.txHash}). Push Chain TX: ${pushChainTxHash}.`, pushChainTxHash, ob.destinationChain, ob.observedTx.txHash);
482
+ for (const targetHop of pendingTargetHops) {
483
+ targetHop.status = 'failed';
484
+ progressHook?.({
485
+ hopIndex: targetHop.hopIndex,
486
+ route: targetHop.route,
487
+ chain: targetHop.executionChain,
488
+ status: 'failed',
489
+ });
490
+ }
491
+ (0, context_1.printLog)(ctx, `[waitForAllOutboundTxsV2] Source-chain RPC reports REVERTED for ${destChain} tx ${externalTxHash} | outboundIndex ${outboundOrdinal} | hops ${pendingTargetHops
492
+ .map((hop) => hop.hopIndex)
493
+ .join(', ')}`);
494
+ return { success: false, failedAt: primaryTargetHop.hopIndex };
273
495
  }
274
496
  if (verdict !== 'success') {
275
- // 'pending' or 'unsupported' — keep polling, cosmos will catch up.
276
497
  continue;
277
498
  }
278
- (0, context_1.printLog)(ctx, `[waitForOutboundTx] Source-chain RPC tiebreaker: ${chain} tx ${ob.observedTx.txHash} succeeded; treating as FOUND despite cosmos status ${(_p = types_1.UniversalTxStatus[statusNum]) !== null && _p !== void 0 ? _p : statusNum}`);
279
499
  }
280
- {
281
- const explorerBaseUrl = (_q = chain_1.CHAIN_INFO[chain]) === null || _q === void 0 ? void 0 : _q.explorerUrl;
282
- const isSvm = ((_r = chain_1.CHAIN_INFO[chain]) === null || _r === void 0 ? void 0 : _r.vm) === enums_1.VM.SVM;
283
- const displayTxHash = (_s = (0, helpers_1.toExternalTxHashDisplay)(chain, ob.observedTx.txHash)) !== null && _s !== void 0 ? _s : ob.observedTx.txHash;
284
- let explorerUrl = '';
500
+ // Build the display form for SVM (base58) used in `hop.txHash` /
501
+ // `hop.outboundDetails.externalTxHash` / the progressHook event —
502
+ // these are USER-FACING. The internal raw-hex form stays only
503
+ // inside this function; it's not exposed to consumers from the
504
+ // cascade path (cascade.ts doesn't call findChildUtxIdFromExternalTx
505
+ // for the cascade outbound hash).
506
+ const displayTxHash = (0, helpers_1.toExternalTxHashDisplay)(chain ?? undefined, externalTxHash) ??
507
+ externalTxHash;
508
+ let explorerUrl = '';
509
+ if (chain && externalTxHash) {
510
+ const explorerBaseUrl = chain_1.CHAIN_INFO[chain]?.explorerUrl;
511
+ const isSvm = chain_1.CHAIN_INFO[chain]?.vm === enums_1.VM.SVM;
285
512
  if (isSvm) {
286
513
  const cluster = chain === enums_1.CHAIN.SOLANA_DEVNET
287
514
  ? '?cluster=devnet'
@@ -297,310 +524,94 @@ function waitForOutboundTx(ctx_1, pushChainTxHash_1) {
297
524
  ? `${explorerBaseUrl}/tx/${displayTxHash}`
298
525
  : '';
299
526
  }
300
- // OutboundTxDetails carries the raw `0x`-hex form so internal
301
- // consumers (R3 inbound tracker in response-builder.ts:786 →
302
- // findChildUtxIdFromExternalTx → Cosmos query) keep working.
303
- // The base58 display form is applied at every user-facing
304
- // boundary via `toExternalTxHashDisplay` (helpers.ts).
305
- const details = {
306
- externalTxHash: ob.observedTx.txHash,
307
- destinationChain: chain,
527
+ }
528
+ // Same PC20 fallback as the single-outbound path above.
529
+ const hopPc20Wrapper = await (0, tracking_1.resolvePC20WrapperForReceipt)(ctx, ob, {
530
+ network: ctx.pushNetwork,
531
+ rpcUrls: ctx.rpcUrls,
532
+ });
533
+ for (const targetHop of pendingTargetHops) {
534
+ targetHop.status = 'confirmed';
535
+ targetHop.txHash = displayTxHash;
536
+ targetHop.outboundDetails = {
537
+ externalTxHash: displayTxHash,
538
+ destinationChain: chain || targetHop.executionChain,
308
539
  explorerUrl,
309
540
  recipient: ob.recipient,
310
541
  amount: ob.amount,
311
- assetAddr: ob.externalAssetAddr,
542
+ assetAddr: hopPc20Wrapper ?? ob.externalAssetAddr,
312
543
  };
313
- (0, context_1.printLog)(ctx, `[waitForOutboundTx] FOUND on poll #${pollCount} | elapsed: ${Date.now() - startTime}ms | externalTxHash: ${displayTxHash}`);
314
- progressHook === null || progressHook === void 0 ? void 0 : progressHook({
315
- status: 'found',
316
- elapsed: Date.now() - startTime,
544
+ progressHook?.({
545
+ hopIndex: targetHop.hopIndex,
546
+ route: targetHop.route,
547
+ chain: targetHop.executionChain,
548
+ status: 'confirmed',
549
+ txHash: displayTxHash,
317
550
  });
318
- return details;
319
551
  }
552
+ (0, context_1.printLog)(ctx, `[waitForAllOutboundTxsV2] FOUND outbound for ${destChain} | outboundIndex ${outboundOrdinal} | hops ${pendingTargetHops
553
+ .map((hop) => hop.hopIndex)
554
+ .join(', ')} | externalTxHash: ${displayTxHash}`);
320
555
  }
321
556
  }
322
557
  }
323
- catch (error) {
324
- // Re-throw typed terminal failure so the caller can classify it as
325
- // 299-02. Transient errors (RPC hiccups, deserialization) are logged
326
- // and the loop continues polling until timeout.
327
- if (error instanceof OutboundFailedError) {
328
- throw error;
558
+ // Check if all hops are now confirmed
559
+ if (outboundHops.every((h) => h.status === 'confirmed')) {
560
+ (0, context_1.printLog)(ctx, `[waitForAllOutboundTxsV2] All ${outboundHops.length} hops confirmed via V2`);
561
+ return { success: true };
562
+ }
563
+ // If PC_EXECUTED_SUCCESS but some hops still unresolved, check outbound status.
564
+ // Only auto-confirm if there are NO pending outbound txs (status=1 with empty hash).
565
+ // Pending outbounds are still in flight on the relay — keep polling for their hashes.
566
+ if (statusNum === types_1.UniversalTxStatus.PC_EXECUTED_SUCCESS) {
567
+ const stillUnresolved = outboundHops.filter((h) => h.status !== 'confirmed');
568
+ if (stillUnresolved.length > 0) {
569
+ const hasPendingOutbound = utx?.outboundTx?.some((ob) => ob.outboundStatus === types_2.OutboundStatus.PENDING &&
570
+ (!ob.observedTx?.txHash || ob.observedTx.txHash === 'EMPTY'));
571
+ if (!hasPendingOutbound) {
572
+ // No pending outbounds — safe to auto-confirm remaining hops
573
+ for (const hop of stillUnresolved) {
574
+ hop.status = 'confirmed';
575
+ (0, context_1.printLog)(ctx, `[waitForAllOutboundTxsV2] Auto-confirmed hop ${hop.hopIndex} (${hop.executionChain}) based on PC_EXECUTED_SUCCESS (no pending outbounds)`);
576
+ progressHook?.({
577
+ hopIndex: hop.hopIndex,
578
+ route: hop.route,
579
+ chain: hop.executionChain,
580
+ status: 'confirmed',
581
+ });
582
+ }
583
+ return { success: true };
584
+ }
585
+ // Pending outbound txs still in flight — continue polling
586
+ (0, context_1.printLog)(ctx, `[waitForAllOutboundTxsV2] ${stillUnresolved.length} hop(s) unresolved, pending outbound txs in flight — continuing to poll`);
329
587
  }
330
- (0, context_1.printLog)(ctx, `[waitForOutboundTx] Poll #${pollCount} ERROR: ${error instanceof Error ? error.message : String(error)}`);
331
588
  }
332
- (0, context_1.printLog)(ctx, `[waitForOutboundTx] Poll #${pollCount} not ready yet (${Date.now() - pollStart}ms). Waiting ${pollingIntervalMs}ms...`);
333
- // Wait before next poll
334
- yield new Promise((resolve) => setTimeout(resolve, pollingIntervalMs));
335
589
  }
336
- const elapsedMs = Date.now() - startTime;
337
- (0, context_1.printLog)(ctx, `[waitForOutboundTx] TIMEOUT after ${pollCount} polls | elapsed: ${elapsedMs}ms`);
338
- progressHook === null || progressHook === void 0 ? void 0 : progressHook({ status: 'timeout', elapsed: elapsedMs });
339
- throw new OutboundTimeoutError(pushChainTxHash, elapsedMs, timeout);
340
- });
341
- }
342
- // ============================================================================
343
- // waitForAllOutboundTxsV2
344
- // ============================================================================
345
- /**
346
- * Tracks ALL outbound transactions for a cascade with multiple outbound hops
347
- * (e.g., BNB + Solana). Uses V2 API which returns outboundTx[] with per-outbound
348
- * status tracking, matching each outbound to the correct hop by destination chain.
349
- */
350
- function waitForAllOutboundTxsV2(ctx, pushChainTxHash, outboundHops, options) {
351
- return tslib_1.__awaiter(this, void 0, void 0, function* () {
352
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
353
- const { initialWaitMs, pollingIntervalMs, timeout, progressHook, _resolvedSubTxId, _outboundIndexByHop, } = options;
354
- const startTime = Date.now();
355
- // Build maps for matching outbound entries. When the cascade composer merged
356
- // multiple logical hops into one outbound request, `_outboundIndexByHop` maps
357
- // those hops to the same outbound ordinal.
358
- const chainToHops = new Map();
359
- for (const hop of outboundHops) {
360
- const chainInfo = chain_1.CHAIN_INFO[hop.executionChain];
361
- if (chainInfo) {
362
- const namespace = `${chain_1.VM_NAMESPACE[chainInfo.vm]}:${chainInfo.chainId}`;
363
- const existingByOrdinal = chainToHops.get(namespace) || new Map();
364
- const fallbackOrdinal = [...existingByOrdinal.values()].reduce((count, hopsForOrdinal) => count + hopsForOrdinal.length, 0);
365
- const outboundOrdinal = (_a = _outboundIndexByHop === null || _outboundIndexByHop === void 0 ? void 0 : _outboundIndexByHop.get(hop.hopIndex)) !== null && _a !== void 0 ? _a : fallbackOrdinal;
366
- const existing = existingByOrdinal.get(outboundOrdinal) || [];
367
- existing.push(hop);
368
- existingByOrdinal.set(outboundOrdinal, existing);
369
- chainToHops.set(namespace, existingByOrdinal);
590
+ catch (error) {
591
+ consecutiveErrors++;
592
+ (0, context_1.printLog)(ctx, `[waitForAllOutboundTxsV2] Poll #${pollCount} ERROR (${consecutiveErrors}/${MAX_CONSECUTIVE_ERRORS}): ${error instanceof Error ? error.message : String(error)}`);
593
+ if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
594
+ (0, context_1.printLog)(ctx, `[waitForAllOutboundTxsV2] Aborting — ${MAX_CONSECUTIVE_ERRORS} consecutive RPC errors`);
595
+ break;
370
596
  }
371
597
  }
372
- const expectedChains = [...chainToHops.keys()];
373
- (0, context_1.printLog)(ctx, `[waitForAllOutboundTxsV2] Starting | txHash: ${pushChainTxHash} | expectedChains: ${expectedChains.join(', ')} | timeout: ${timeout}ms`);
374
- // Emit initial waiting status for all outbound hops
375
- for (const hop of outboundHops) {
376
- progressHook === null || progressHook === void 0 ? void 0 : progressHook({
377
- hopIndex: hop.hopIndex,
378
- route: hop.route,
379
- chain: hop.executionChain,
380
- status: 'waiting',
381
- });
382
- }
383
- // Initial wait before first poll
384
- const waitMs = Math.min(initialWaitMs, timeout);
385
- (0, context_1.printLog)(ctx, `[waitForAllOutboundTxsV2] Initial wait of ${waitMs}ms...`);
386
- yield new Promise((resolve) => setTimeout(resolve, waitMs));
387
- // Emit polling status for all hops
388
- for (const hop of outboundHops) {
389
- progressHook === null || progressHook === void 0 ? void 0 : progressHook({
598
+ await new Promise((resolve) => setTimeout(resolve, pollingIntervalMs));
599
+ }
600
+ // Timeout: fail any unresolved hops
601
+ const timedOutHops = outboundHops.filter((h) => h.status !== 'confirmed');
602
+ if (timedOutHops.length > 0) {
603
+ (0, context_1.printLog)(ctx, `[waitForAllOutboundTxsV2] TIMEOUT after ${pollCount} polls | ${timedOutHops.length} hop(s) unresolved`);
604
+ for (const hop of timedOutHops) {
605
+ hop.status = 'failed';
606
+ progressHook?.({
390
607
  hopIndex: hop.hopIndex,
391
608
  route: hop.route,
392
609
  chain: hop.executionChain,
393
- status: 'polling',
610
+ status: 'failed',
394
611
  });
395
612
  }
396
- // Extract sub-tx ID for V2 query
397
- let cachedQueryId = _resolvedSubTxId
398
- ? _resolvedSubTxId.startsWith('0x')
399
- ? _resolvedSubTxId.slice(2)
400
- : _resolvedSubTxId
401
- : undefined;
402
- let pollCount = 0;
403
- let consecutiveErrors = 0;
404
- const MAX_CONSECUTIVE_ERRORS = 10;
405
- while (Date.now() - startTime < timeout) {
406
- pollCount++;
407
- const elapsed = Date.now() - startTime;
408
- (0, context_1.printLog)(ctx, `[waitForAllOutboundTxsV2] Poll #${pollCount} | Elapsed: ${elapsed}ms / ${timeout}ms`);
409
- // Resolve query ID on first poll
410
- if (!cachedQueryId) {
411
- const allSubTxIds = yield (0, outbound_tracker_1.extractAllUniversalSubTxIds)(ctx, pushChainTxHash);
412
- const subTxId = allSubTxIds.length > 0
413
- ? allSubTxIds[0]
414
- : (0, outbound_tracker_1.computeUniversalTxId)(ctx.pushNetwork, pushChainTxHash);
415
- cachedQueryId = subTxId.startsWith('0x') ? subTxId.slice(2) : subTxId;
416
- (0, context_1.printLog)(ctx, `[waitForAllOutboundTxsV2] Resolved queryId: ${cachedQueryId}`);
417
- }
418
- try {
419
- const v2Response = yield ctx.pushClient.getUniversalTxByIdV2(cachedQueryId);
420
- consecutiveErrors = 0; // Reset on successful RPC call
421
- const utx = v2Response === null || v2Response === void 0 ? void 0 : v2Response.universalTx;
422
- const statusNum = utx === null || utx === void 0 ? void 0 : utx.universalStatus;
423
- const statusName = (_b = types_1.UniversalTxStatus[statusNum]) !== null && _b !== void 0 ? _b : statusNum;
424
- (0, context_1.printLog)(ctx, `[waitForAllOutboundTxsV2] Poll #${pollCount} | status: ${statusNum} (${statusName}) | outboundTx count: ${(_d = (_c = utx === null || utx === void 0 ? void 0 : utx.outboundTx) === null || _c === void 0 ? void 0 : _c.length) !== null && _d !== void 0 ? _d : 0}`);
425
- if ((_e = utx === null || utx === void 0 ? void 0 : utx.outboundTx) === null || _e === void 0 ? void 0 : _e.length) {
426
- const outboundOrdinalByChain = new Map();
427
- for (const ob of utx.outboundTx) {
428
- const destChain = ob.destinationChain;
429
- const hopsByOrdinal = chainToHops.get(destChain);
430
- if (!hopsByOrdinal)
431
- continue;
432
- const outboundOrdinal = (_f = outboundOrdinalByChain.get(destChain)) !== null && _f !== void 0 ? _f : 0;
433
- outboundOrdinalByChain.set(destChain, outboundOrdinal + 1);
434
- const targetHops = hopsByOrdinal.get(outboundOrdinal) || [];
435
- const pendingTargetHops = targetHops.filter((hop) => hop.status !== 'confirmed' && hop.status !== 'failed');
436
- if (pendingTargetHops.length === 0)
437
- continue;
438
- const primaryTargetHop = pendingTargetHops[0];
439
- const externalTxHash = (_g = ob.observedTx) === null || _g === void 0 ? void 0 : _g.txHash;
440
- const hasObservedTxHash = !!externalTxHash && externalTxHash !== 'EMPTY';
441
- const observedFailure = hasObservedTxHash &&
442
- ((_h = ob.observedTx) === null || _h === void 0 ? void 0 : _h.success) === false &&
443
- Boolean(((_j = ob.observedTx) === null || _j === void 0 ? void 0 : _j.errorMsg) ||
444
- ob.abortReason ||
445
- ob.outboundStatus >= types_2.OutboundStatus.REVERTED);
446
- // Fail fast on per-outbound REVERTED or observed destination failure.
447
- if (ob.outboundStatus === types_2.OutboundStatus.REVERTED ||
448
- observedFailure) {
449
- for (const targetHop of pendingTargetHops) {
450
- targetHop.status = 'failed';
451
- progressHook === null || progressHook === void 0 ? void 0 : progressHook({
452
- hopIndex: targetHop.hopIndex,
453
- route: targetHop.route,
454
- chain: targetHop.executionChain,
455
- status: 'failed',
456
- });
457
- }
458
- (0, context_1.printLog)(ctx, `[waitForAllOutboundTxsV2] Outbound to ${destChain} REVERTED | outboundIndex ${outboundOrdinal} | hops ${pendingTargetHops
459
- .map((hop) => hop.hopIndex)
460
- .join(', ')} | error: ${ob.abortReason || ((_k = ob.observedTx) === null || _k === void 0 ? void 0 : _k.errorMsg) || 'Unknown'}`);
461
- return { success: false, failedAt: primaryTargetHop.hopIndex };
462
- }
463
- // Check for observed tx hash. Cosmos may lag the status transition,
464
- // so EVM destinations are verified against the destination receipt.
465
- if (hasObservedTxHash && externalTxHash) {
466
- const chain = (0, helpers_1.chainFromNamespace)(destChain);
467
- if (!chain)
468
- continue;
469
- const isCosmosObserved = ob.outboundStatus === types_2.OutboundStatus.OBSERVED;
470
- if (!isCosmosObserved) {
471
- const verdict = yield verifyExternalEvmReceipt(ctx, chain, externalTxHash);
472
- if (verdict === 'reverted') {
473
- for (const targetHop of pendingTargetHops) {
474
- targetHop.status = 'failed';
475
- progressHook === null || progressHook === void 0 ? void 0 : progressHook({
476
- hopIndex: targetHop.hopIndex,
477
- route: targetHop.route,
478
- chain: targetHop.executionChain,
479
- status: 'failed',
480
- });
481
- }
482
- (0, context_1.printLog)(ctx, `[waitForAllOutboundTxsV2] Source-chain RPC reports REVERTED for ${destChain} tx ${externalTxHash} | outboundIndex ${outboundOrdinal} | hops ${pendingTargetHops
483
- .map((hop) => hop.hopIndex)
484
- .join(', ')}`);
485
- return { success: false, failedAt: primaryTargetHop.hopIndex };
486
- }
487
- if (verdict !== 'success') {
488
- continue;
489
- }
490
- }
491
- // Build the display form for SVM (base58) used in `hop.txHash` /
492
- // `hop.outboundDetails.externalTxHash` / the progressHook event —
493
- // these are USER-FACING. The internal raw-hex form stays only
494
- // inside this function; it's not exposed to consumers from the
495
- // cascade path (cascade.ts doesn't call findChildUtxIdFromExternalTx
496
- // for the cascade outbound hash).
497
- const displayTxHash = (_l = (0, helpers_1.toExternalTxHashDisplay)(chain !== null && chain !== void 0 ? chain : undefined, externalTxHash)) !== null && _l !== void 0 ? _l : externalTxHash;
498
- let explorerUrl = '';
499
- if (chain && externalTxHash) {
500
- const explorerBaseUrl = (_m = chain_1.CHAIN_INFO[chain]) === null || _m === void 0 ? void 0 : _m.explorerUrl;
501
- const isSvm = ((_o = chain_1.CHAIN_INFO[chain]) === null || _o === void 0 ? void 0 : _o.vm) === enums_1.VM.SVM;
502
- if (isSvm) {
503
- const cluster = chain === enums_1.CHAIN.SOLANA_DEVNET
504
- ? '?cluster=devnet'
505
- : chain === enums_1.CHAIN.SOLANA_TESTNET
506
- ? '?cluster=testnet'
507
- : '';
508
- explorerUrl = explorerBaseUrl
509
- ? `${explorerBaseUrl}/tx/${displayTxHash}${cluster}`
510
- : '';
511
- }
512
- else {
513
- explorerUrl = explorerBaseUrl
514
- ? `${explorerBaseUrl}/tx/${displayTxHash}`
515
- : '';
516
- }
517
- }
518
- for (const targetHop of pendingTargetHops) {
519
- targetHop.status = 'confirmed';
520
- targetHop.txHash = displayTxHash;
521
- targetHop.outboundDetails = {
522
- externalTxHash: displayTxHash,
523
- destinationChain: chain || targetHop.executionChain,
524
- explorerUrl,
525
- recipient: ob.recipient,
526
- amount: ob.amount,
527
- assetAddr: ob.externalAssetAddr,
528
- };
529
- progressHook === null || progressHook === void 0 ? void 0 : progressHook({
530
- hopIndex: targetHop.hopIndex,
531
- route: targetHop.route,
532
- chain: targetHop.executionChain,
533
- status: 'confirmed',
534
- txHash: displayTxHash,
535
- });
536
- }
537
- (0, context_1.printLog)(ctx, `[waitForAllOutboundTxsV2] FOUND outbound for ${destChain} | outboundIndex ${outboundOrdinal} | hops ${pendingTargetHops
538
- .map((hop) => hop.hopIndex)
539
- .join(', ')} | externalTxHash: ${displayTxHash}`);
540
- }
541
- }
542
- }
543
- // Check if all hops are now confirmed
544
- if (outboundHops.every((h) => h.status === 'confirmed')) {
545
- (0, context_1.printLog)(ctx, `[waitForAllOutboundTxsV2] All ${outboundHops.length} hops confirmed via V2`);
546
- return { success: true };
547
- }
548
- // If PC_EXECUTED_SUCCESS but some hops still unresolved, check outbound status.
549
- // Only auto-confirm if there are NO pending outbound txs (status=1 with empty hash).
550
- // Pending outbounds are still in flight on the relay — keep polling for their hashes.
551
- if (statusNum === types_1.UniversalTxStatus.PC_EXECUTED_SUCCESS) {
552
- const stillUnresolved = outboundHops.filter((h) => h.status !== 'confirmed');
553
- if (stillUnresolved.length > 0) {
554
- const hasPendingOutbound = (_p = utx === null || utx === void 0 ? void 0 : utx.outboundTx) === null || _p === void 0 ? void 0 : _p.some((ob) => {
555
- var _a;
556
- return ob.outboundStatus === types_2.OutboundStatus.PENDING &&
557
- (!((_a = ob.observedTx) === null || _a === void 0 ? void 0 : _a.txHash) || ob.observedTx.txHash === 'EMPTY');
558
- });
559
- if (!hasPendingOutbound) {
560
- // No pending outbounds — safe to auto-confirm remaining hops
561
- for (const hop of stillUnresolved) {
562
- hop.status = 'confirmed';
563
- (0, context_1.printLog)(ctx, `[waitForAllOutboundTxsV2] Auto-confirmed hop ${hop.hopIndex} (${hop.executionChain}) based on PC_EXECUTED_SUCCESS (no pending outbounds)`);
564
- progressHook === null || progressHook === void 0 ? void 0 : progressHook({
565
- hopIndex: hop.hopIndex,
566
- route: hop.route,
567
- chain: hop.executionChain,
568
- status: 'confirmed',
569
- });
570
- }
571
- return { success: true };
572
- }
573
- // Pending outbound txs still in flight — continue polling
574
- (0, context_1.printLog)(ctx, `[waitForAllOutboundTxsV2] ${stillUnresolved.length} hop(s) unresolved, pending outbound txs in flight — continuing to poll`);
575
- }
576
- }
577
- }
578
- catch (error) {
579
- consecutiveErrors++;
580
- (0, context_1.printLog)(ctx, `[waitForAllOutboundTxsV2] Poll #${pollCount} ERROR (${consecutiveErrors}/${MAX_CONSECUTIVE_ERRORS}): ${error instanceof Error ? error.message : String(error)}`);
581
- if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
582
- (0, context_1.printLog)(ctx, `[waitForAllOutboundTxsV2] Aborting — ${MAX_CONSECUTIVE_ERRORS} consecutive RPC errors`);
583
- break;
584
- }
585
- }
586
- yield new Promise((resolve) => setTimeout(resolve, pollingIntervalMs));
587
- }
588
- // Timeout: fail any unresolved hops
589
- const timedOutHops = outboundHops.filter((h) => h.status !== 'confirmed');
590
- if (timedOutHops.length > 0) {
591
- (0, context_1.printLog)(ctx, `[waitForAllOutboundTxsV2] TIMEOUT after ${pollCount} polls | ${timedOutHops.length} hop(s) unresolved`);
592
- for (const hop of timedOutHops) {
593
- hop.status = 'failed';
594
- progressHook === null || progressHook === void 0 ? void 0 : progressHook({
595
- hopIndex: hop.hopIndex,
596
- route: hop.route,
597
- chain: hop.executionChain,
598
- status: 'failed',
599
- });
600
- }
601
- return { success: false, failedAt: timedOutHops[0].hopIndex };
602
- }
603
- return { success: true };
604
- });
613
+ return { success: false, failedAt: timedOutHops[0].hopIndex };
614
+ }
615
+ return { success: true };
605
616
  }
606
617
  //# sourceMappingURL=outbound-sync.js.map