@pushchain/core 6.0.17 → 6.0.19

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 (31) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/package.json +1 -1
  3. package/src/lib/constants/chain.d.ts +16 -0
  4. package/src/lib/constants/chain.js +32 -4
  5. package/src/lib/formatters.d.ts +1 -0
  6. package/src/lib/formatters.js +66 -1
  7. package/src/lib/orchestrator/internals/cascade.js +65 -47
  8. package/src/lib/orchestrator/internals/confirmation.js +8 -8
  9. package/src/lib/orchestrator/internals/error-decoder.d.ts +1 -0
  10. package/src/lib/orchestrator/internals/error-decoder.js +10 -0
  11. package/src/lib/orchestrator/internals/errors.js +1 -1
  12. package/src/lib/orchestrator/internals/execute-funds-only.js +8 -6
  13. package/src/lib/orchestrator/internals/execute-funds-payload.js +7 -5
  14. package/src/lib/orchestrator/internals/execute-standard.js +10 -8
  15. package/src/lib/orchestrator/internals/gateway-client.js +34 -1
  16. package/src/lib/orchestrator/internals/inbound-tracker.js +4 -3
  17. package/src/lib/orchestrator/internals/progress-route-hooks.d.ts +8 -4
  18. package/src/lib/orchestrator/internals/progress-route-hooks.js +8 -8
  19. package/src/lib/orchestrator/internals/push-chain-tx.js +71 -15
  20. package/src/lib/orchestrator/internals/response-builder.js +25 -16
  21. package/src/lib/orchestrator/internals/tx-transformer.js +11 -6
  22. package/src/lib/orchestrator/orchestrator.js +3 -17
  23. package/src/lib/orchestrator/orchestrator.types.d.ts +7 -0
  24. package/src/lib/progress-hook/progress-hook.js +37 -40
  25. package/src/lib/push-client/push-client.d.ts +39 -0
  26. package/src/lib/push-client/push-client.js +130 -12
  27. package/src/lib/universal/signer/signer.d.ts +1 -1
  28. package/src/lib/universal/signer/signer.js +83 -3
  29. package/src/lib/universal/universal.types.d.ts +37 -0
  30. package/src/lib/vm-client/evm-client.d.ts +43 -0
  31. package/src/lib/vm-client/evm-client.js +154 -1
@@ -182,13 +182,15 @@ function executeFundsOnlyEvm(ctx, execute, eventBuffer, transformFn, chain, rpcU
182
182
  txHash = yield (0, gateway_client_1.sendGatewayTxWithFallback)(ctx, evmClient, gatewayAddress, req, ctx.universalSigner, isNative ? nativeAmount + bridgeAmount : nativeAmount);
183
183
  }
184
184
  catch (err) {
185
- (0, context_2.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_104_04);
186
- throw err;
185
+ const errMsg = (0, formatters_1.normalizePublicErrorMessage)(err);
186
+ (0, context_2.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_104_04, errMsg);
187
+ ctx._routeTerminalEmitted = true;
188
+ throw new Error(errMsg);
187
189
  }
188
190
  const originTx = yield (0, helpers_2.fetchOriginChainTransactionForProgress)(ctx, chain, txHash, txHash);
189
191
  (0, context_2.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_106_02, txHash, bridgeAmount, execute.funds.token.decimals, symbol, originTx);
190
192
  yield (0, confirmation_1.waitForEvmConfirmationsWithCountdown)(ctx, evmClient, txHash, chain_1.CHAIN_INFO[chain].confirmations, chain_1.CHAIN_INFO[chain].timeout);
191
- (0, context_2.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_106_04);
193
+ (0, context_2.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_106_04, txHash);
192
194
  (0, context_2.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_106_05);
193
195
  (0, context_2.printLog)(ctx, 'sendFunds — querying Push Chain status: ' + JSON.stringify({
194
196
  txHash, evmGatewayMethod: execute.to === ueaAddress ? 'sendFunds' : 'sendTxWithFunds',
@@ -202,7 +204,7 @@ function executeFundsOnlyEvm(ctx, execute, eventBuffer, transformFn, chain, rpcU
202
204
  }
203
205
  catch (err) {
204
206
  if (!(err instanceof push_chain_tx_1.PushChainExecutionError)) {
205
- const errMsg = (0, formatters_1.normalizePcInsufficientFundsError)(err instanceof Error ? err.message : String(err));
207
+ const errMsg = (0, formatters_1.normalizePublicErrorMessage)(err);
206
208
  (0, context_2.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_199_02, errMsg);
207
209
  throw new push_chain_tx_1.PushChainExecutionError(errMsg, { gatewayTxHash: txHash });
208
210
  }
@@ -284,7 +286,7 @@ function executeFundsOnlySvm(ctx, execute, eventBuffer, transformFn, chain, rpcU
284
286
  const originTx = yield (0, helpers_2.fetchOriginChainTransactionForProgress)(ctx, chain, '0x', txSignature);
285
287
  (0, context_2.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_106_02, txSignature, bridgeAmount, execute.funds.token.decimals, symbol, originTx);
286
288
  yield (0, confirmation_1.waitForSvmConfirmationsWithCountdown)(ctx, svmClient, txSignature, chain_1.CHAIN_INFO[chain].confirmations, chain_1.CHAIN_INFO[chain].timeout);
287
- (0, context_2.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_106_04);
289
+ (0, context_2.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_106_04, txSignature);
288
290
  (0, context_2.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_106_05);
289
291
  let response;
290
292
  try {
@@ -293,7 +295,7 @@ function executeFundsOnlySvm(ctx, execute, eventBuffer, transformFn, chain, rpcU
293
295
  }
294
296
  catch (err) {
295
297
  if (!(err instanceof push_chain_tx_1.PushChainExecutionError)) {
296
- const errMsg = (0, formatters_1.normalizePcInsufficientFundsError)(err instanceof Error ? err.message : String(err));
298
+ const errMsg = (0, formatters_1.normalizePublicErrorMessage)(err);
297
299
  (0, context_2.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_199_02, errMsg);
298
300
  throw new push_chain_tx_1.PushChainExecutionError(errMsg, { gatewayTxHash: txSignature });
299
301
  }
@@ -158,7 +158,7 @@ function executeFundsWithPayload(ctx, execute, eventBuffer, getResponseCallbacks
158
158
  }
159
159
  }
160
160
  // Final USD(8) -> native conversion reusing the cached price fetched above.
161
- let nativeAmount = usdToNative(depositUsd);
161
+ const nativeAmount = usdToNative(depositUsd);
162
162
  (0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_103_03_04, nativeAmount, depositUsd, chain);
163
163
  const bridgeAmount = execute.funds.amount;
164
164
  const symbol = execute.funds.token.symbol;
@@ -222,8 +222,10 @@ function executeFundsWithPayload(ctx, execute, eventBuffer, getResponseCallbacks
222
222
  }
223
223
  }
224
224
  catch (err) {
225
- (0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_104_04);
226
- throw err;
225
+ const errMsg = (0, formatters_1.normalizePublicErrorMessage)(err);
226
+ (0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_104_04, errMsg);
227
+ ctx._routeTerminalEmitted = true;
228
+ throw new Error(errMsg);
227
229
  }
228
230
  (0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_104_03);
229
231
  (0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_106_02, txHash, bridgeAmount, execute.funds.token.decimals, symbol);
@@ -246,7 +248,7 @@ function executeFundsWithPayload(ctx, execute, eventBuffer, getResponseCallbacks
246
248
  feeLockTxHash = (0, viem_1.bytesToHex)(new Uint8Array(decoded));
247
249
  }
248
250
  }
249
- (0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_106_04);
251
+ (0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_106_04, txHash);
250
252
  (0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_106_05);
251
253
  let response;
252
254
  try {
@@ -261,7 +263,7 @@ function executeFundsWithPayload(ctx, execute, eventBuffer, getResponseCallbacks
261
263
  }
262
264
  catch (err) {
263
265
  if (!(err instanceof push_chain_tx_1.PushChainExecutionError)) {
264
- const errMsg = (0, formatters_1.normalizePcInsufficientFundsError)(err instanceof Error ? err.message : String(err));
266
+ const errMsg = (0, formatters_1.normalizePublicErrorMessage)(err);
265
267
  (0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_199_02, errMsg);
266
268
  throw new push_chain_tx_1.PushChainExecutionError(errMsg, { gatewayTxHash: txHash });
267
269
  }
@@ -111,7 +111,7 @@ function executeStandardPayload(ctx, execute, eventBuffer, getResponseCallbacks)
111
111
  catch (err) {
112
112
  // Push Chain broadcast failed. Wallet rejection surfaces as 104-04;
113
113
  // everything else (RPC fail, on-chain revert) as 199-02.
114
- const errMsg = (0, formatters_1.normalizePcInsufficientFundsError)(err instanceof Error ? err.message : String(err));
114
+ const errMsg = (0, formatters_1.normalizePublicErrorMessage)(err);
115
115
  const { isUserDecline } = (0, progress_hook_1.classifyDeclineError)(errMsg);
116
116
  (0, context_1.fireProgressHook)(ctx, isUserDecline ? progress_hook_types_1.PROGRESS_HOOK.SEND_TX_104_04 : progress_hook_types_1.PROGRESS_HOOK.SEND_TX_199_02, errMsg);
117
117
  // Mark that a terminal-ish error hook has already fired so the
@@ -274,9 +274,10 @@ function executeStandardPayload(ctx, execute, eventBuffer, getResponseCallbacks)
274
274
  // Wallet decline, contract revert during sign, or RPC failure — the
275
275
  // 104-04 builder's classifyDeclineError heuristic picks the right
276
276
  // copy (real decline → "Verification Declined"; else "Signature Failed").
277
- const errMsg = (0, formatters_1.normalizePcInsufficientFundsError)(err instanceof Error ? err.message : String(err));
277
+ const errMsg = (0, formatters_1.normalizePublicErrorMessage)(err);
278
278
  (0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_104_04, errMsg);
279
- throw err;
279
+ ctx._routeTerminalEmitted = true;
280
+ throw new Error(errMsg);
280
281
  }
281
282
  verificationData = (0, viem_1.bytesToHex)(signature);
282
283
  (0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_104_03);
@@ -393,9 +394,10 @@ function executeStandardPayload(ctx, execute, eventBuffer, getResponseCallbacks)
393
394
  catch (err) {
394
395
  // User declined the fee-lock tx submission or the origin-chain RPC
395
396
  // rejected it — classifier picks decline vs generic signature failure.
396
- const errMsg = err instanceof Error ? err.message : String(err);
397
+ const errMsg = (0, formatters_1.normalizePublicErrorMessage)(err);
397
398
  (0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_104_04, errMsg);
398
- throw err;
399
+ ctx._routeTerminalEmitted = true;
400
+ throw new Error(errMsg);
399
401
  }
400
402
  feeLockTxHash = (0, viem_1.bytesToHex)(feeLockTxHashBytes);
401
403
  verificationData = (0, viem_1.bytesToHex)(feeLockTxHashBytes);
@@ -406,7 +408,7 @@ function executeStandardPayload(ctx, execute, eventBuffer, getResponseCallbacks)
406
408
  const originTx = yield (0, helpers_1.fetchOriginChainTransactionForProgress)(ctx, chain, feeLockTxHash, feeLockTxHashDisplay);
407
409
  (0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_105_01, feeLockTxHashDisplay, originTx);
408
410
  yield (0, confirmation_1.waitForLockerFeeConfirmation)(ctx, feeLockTxHashBytes);
409
- (0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_105_02);
411
+ (0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_105_02, feeLockTxHashDisplay);
410
412
  const { defaultRPC, lockerContract } = chain_1.CHAIN_INFO[chain];
411
413
  (0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_107);
412
414
  let response;
@@ -420,7 +422,7 @@ function executeStandardPayload(ctx, execute, eventBuffer, getResponseCallbacks)
420
422
  }
421
423
  catch (err) {
422
424
  if (!(err instanceof push_chain_tx_1.PushChainExecutionError)) {
423
- const errMsg = (0, formatters_1.normalizePcInsufficientFundsError)(err instanceof Error ? err.message : String(err));
425
+ const errMsg = (0, formatters_1.normalizePublicErrorMessage)(err);
424
426
  (0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_199_02, errMsg);
425
427
  throw new push_chain_tx_1.PushChainExecutionError(errMsg, { gatewayTxHash: feeLockTxHash });
426
428
  }
@@ -454,7 +456,7 @@ function executeStandardPayload(ctx, execute, eventBuffer, getResponseCallbacks)
454
456
  // exhaustion). Surface terminal 199-02 before re-throwing so the stream
455
457
  // ends with the spec'd error hook. Typed PushChainExecutionError lets
456
458
  // callers classify via instanceof without message sniffing.
457
- const errMsg = (0, formatters_1.normalizePcInsufficientFundsError)(err instanceof Error ? err.message : String(err));
459
+ const errMsg = (0, formatters_1.normalizePublicErrorMessage)(err);
458
460
  (0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_199_02, errMsg);
459
461
  if (err instanceof push_chain_tx_1.PushChainExecutionError)
460
462
  throw err;
@@ -30,6 +30,27 @@ const svm_helpers_2 = require("./svm-helpers");
30
30
  // ABI
31
31
  // ============================================================================
32
32
  const GATEWAY_ABI = abi_1.UNIVERSAL_GATEWAY_V1_SEND;
33
+ const SOURCE_NATIVE_SYMBOL = {
34
+ [enums_1.CHAIN.ETHEREUM_MAINNET]: 'ETH',
35
+ [enums_1.CHAIN.ETHEREUM_SEPOLIA]: 'ETH',
36
+ [enums_1.CHAIN.ARBITRUM_SEPOLIA]: 'ETH',
37
+ [enums_1.CHAIN.BASE_SEPOLIA]: 'ETH',
38
+ [enums_1.CHAIN.BNB_TESTNET]: 'BNB',
39
+ [enums_1.CHAIN.SOLANA_MAINNET]: 'SOL',
40
+ [enums_1.CHAIN.SOLANA_TESTNET]: 'SOL',
41
+ [enums_1.CHAIN.SOLANA_DEVNET]: 'SOL',
42
+ };
43
+ function formatSourceNative(chain, amount) {
44
+ var _a;
45
+ const decimals = chain_1.CHAIN_INFO[chain].vm === enums_1.VM.SVM ? 9 : 18;
46
+ const symbol = (_a = SOURCE_NATIVE_SYMBOL[chain]) !== null && _a !== void 0 ? _a : 'native token';
47
+ return `${utils_1.Utils.helpers.formatUnits(amount, decimals)} ${symbol}`;
48
+ }
49
+ function insufficientSourceBalanceMessage(chain, available, required) {
50
+ return (`Insufficient source balance for transaction gas/value: ` +
51
+ `have ${formatSourceNative(chain, available)}, ` +
52
+ `need at least ${formatSourceNative(chain, required)}.`);
53
+ }
33
54
  // ============================================================================
34
55
  // Request Conversion (V0 struct → V1 flat format)
35
56
  // ============================================================================
@@ -104,6 +125,13 @@ req) {
104
125
  (nativeTokenUsdPrice - BigInt(1))) /
105
126
  nativeTokenUsdPrice;
106
127
  nativeAmount = nativeAmount + BigInt(1);
128
+ const sourceAddress = ctx.universalSigner.account.address;
129
+ const sourceBalance = yield evmClient.publicClient.getBalance({
130
+ address: sourceAddress,
131
+ });
132
+ if (sourceBalance < nativeAmount) {
133
+ throw new Error(insufficientSourceBalanceMessage(chain, sourceBalance, nativeAmount));
134
+ }
107
135
  const txHash = yield sendGatewayTxWithFallback(ctx, evmClient, lockerContract, req, ctx.universalSigner, nativeAmount);
108
136
  return (0, viem_1.hexToBytes)(txHash);
109
137
  }
@@ -130,12 +158,17 @@ req) {
130
158
  const userPk = new web3_js_1.PublicKey(ctx.universalSigner.account.address);
131
159
  const { feeVaultPda, protocolFeeLamports } = yield (0, svm_helpers_2.getSvmProtocolFee)(svmClient, programId);
132
160
  const gasReq = (0, svm_helpers_1.buildSvmUniversalTxRequestFromReq)(req, userPk);
161
+ const requiredLamports = nativeAmount + protocolFeeLamports;
162
+ const sourceBalance = yield svmClient.getBalance(userPk.toBase58());
163
+ if (sourceBalance < requiredLamports) {
164
+ throw new Error(insufficientSourceBalanceMessage(chain, sourceBalance, requiredLamports));
165
+ }
133
166
  try {
134
167
  const txHash = yield svmClient.writeContract({
135
168
  abi: abi_1.SVM_GATEWAY_IDL,
136
169
  address: programId.toBase58(),
137
170
  functionName: 'sendUniversalTx',
138
- args: [gasReq, nativeAmount + protocolFeeLamports],
171
+ args: [gasReq, requiredLamports],
139
172
  signer: ctx.universalSigner,
140
173
  accounts: {
141
174
  config: configPda,
@@ -254,9 +254,10 @@ function waitForInboundPushTx(ctx_1, outboundExternalTxHash_1, sourceChain_1) {
254
254
  // chain, treat as terminal-success; if it reverted, treat as
255
255
  // terminal-failure. Cosmos-indexer lag stops blocking us here.
256
256
  try {
257
- const receipt = yield ctx.pushClient.publicClient.getTransactionReceipt({
258
- hash: pushTxHash,
259
- });
257
+ // Archive-fallback variant: a Push-leg receipt outside the prune
258
+ // window still resolves via the archive node. On chains without an
259
+ // archive endpoint this behaves as a plain prune-only lookup.
260
+ const receipt = yield ctx.pushClient.getTransactionReceiptWithArchiveFallback(pushTxHash);
260
261
  if ((receipt === null || receipt === void 0 ? void 0 : receipt.status) === 'success') {
261
262
  (0, context_1.printLog)(ctx, `[waitForInboundPushTx] Push RPC tiebreaker: ${pushTxHash} confirmed on chain (cosmos still status=${status}); treating as terminal-success`);
262
263
  progressHook === null || progressHook === void 0 ? void 0 : progressHook({
@@ -9,12 +9,16 @@
9
9
  import type { OutboundTxDetails } from '../orchestrator.types';
10
10
  import type { ProgressEvent } from '../../progress-hook/progress-hook.types';
11
11
  import { TransactionRoute } from '../route-detector';
12
+ export type WaitHookMetadata = {
13
+ pushTxHash?: string;
14
+ txHash?: string;
15
+ };
12
16
  export type WaitHookSet = {
13
- awaiting: (chain: string) => ProgressEvent;
14
- polling: (chain: string, elapsedMs: number) => ProgressEvent;
17
+ awaiting: (chain: string, pushTxHash?: string) => ProgressEvent;
18
+ polling: (chain: string, elapsedMs: number, pushTxHash?: string) => ProgressEvent;
15
19
  success: (details: OutboundTxDetails) => ProgressEvent;
16
- timeout: (chain: string, elapsedMs: number) => ProgressEvent;
17
- failed: (chain: string, errMsg: string) => ProgressEvent;
20
+ timeout: (chain: string, elapsedMs: number, pushTxHash?: string) => ProgressEvent;
21
+ failed: (chain: string, errMsg: string, metadata?: WaitHookMetadata) => ProgressEvent;
18
22
  };
19
23
  export declare function pickWaitHooks(route: TransactionRoute | string | undefined): WaitHookSet;
20
24
  /** True when the hook set is the no-op placeholder (route has no spec'd
@@ -17,17 +17,17 @@ const route_detector_1 = require("../route-detector");
17
17
  function pickWaitHooks(route) {
18
18
  if (route === route_detector_1.TransactionRoute.UOA_TO_CEA) {
19
19
  return {
20
- awaiting: (chain) => progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_209_01](chain),
21
- polling: (chain, elapsed) => progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_209_02](chain, elapsed),
20
+ awaiting: (chain, pushTxHash) => progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_209_01](chain, pushTxHash),
21
+ polling: (chain, elapsed, pushTxHash) => progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_209_02](chain, elapsed, pushTxHash),
22
22
  success: (details) => progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_299_01](details),
23
- timeout: (chain, elapsed) => progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_299_03](chain, elapsed),
24
- failed: (chain, msg) => progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_299_02](chain, msg),
23
+ timeout: (chain, elapsed, pushTxHash) => progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_299_03](chain, elapsed, pushTxHash),
24
+ failed: (chain, msg, metadata) => progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_299_02](chain, msg, undefined, metadata === null || metadata === void 0 ? void 0 : metadata.pushTxHash, metadata === null || metadata === void 0 ? void 0 : metadata.txHash),
25
25
  };
26
26
  }
27
27
  if (route === route_detector_1.TransactionRoute.CEA_TO_PUSH) {
28
28
  return {
29
- awaiting: (chain) => progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_309_01](chain),
30
- polling: (chain, elapsed) => progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_309_02](chain, elapsed),
29
+ awaiting: (chain, pushTxHash) => progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_309_01](chain, pushTxHash),
30
+ polling: (chain, elapsed, pushTxHash) => progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_309_02](chain, elapsed, pushTxHash),
31
31
  // For R3, the outbound-poll "success" is the source-chain CEA executing
32
32
  // (309-03 INFO). Terminal 399-01 fires after the inbound-to-Push poll
33
33
  // (handled by waitForInboundPushTx in response-builder).
@@ -35,8 +35,8 @@ function pickWaitHooks(route) {
35
35
  // R3 outbound (source-chain CEA) failures share the 399-02/03 IDs with
36
36
  // R3 inbound; pass phase='outbound' so the title reflects the source
37
37
  // chain instead of "Push Chain Inbound …".
38
- timeout: (chain, elapsed) => progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_399_03](chain, elapsed, 'outbound'),
39
- failed: (chain, msg) => progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_399_02](msg, 'outbound', chain),
38
+ timeout: (chain, elapsed, pushTxHash) => progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_399_03](chain, elapsed, 'outbound', pushTxHash),
39
+ failed: (chain, msg, metadata) => progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_399_02](msg, 'outbound', chain, undefined, metadata === null || metadata === void 0 ? void 0 : metadata.pushTxHash, metadata === null || metadata === void 0 ? void 0 : metadata.txHash),
40
40
  };
41
41
  }
42
42
  // Route 4 (CEA_TO_CEA) has no spec'd ID range yet; cascade hops and
@@ -16,6 +16,7 @@ const viem_1 = require("viem");
16
16
  const chain_1 = require("../../constants/chain");
17
17
  const enums_1 = require("../../constants/enums");
18
18
  const progress_hook_types_1 = require("../../progress-hook/progress-hook.types");
19
+ const evm_client_1 = require("../../vm-client/evm-client");
19
20
  const context_1 = require("./context");
20
21
  const error_decoder_1 = require("./error-decoder");
21
22
  const errors_1 = require("./errors");
@@ -69,6 +70,51 @@ function sendPushTx(ctx, execute, eventBuffer, transformFn) {
69
70
  return tslib_1.__awaiter(this, void 0, void 0, function* () {
70
71
  var _a, _b, _c;
71
72
  if (Array.isArray(execute.data)) {
73
+ const calls = execute.data;
74
+ // Reject a zero `to` BEFORE route selection so behaviour is identical
75
+ // regardless of signer capability: ERC-7821 (the 7702 path) reinterprets
76
+ // `target == 0` as the account itself, whereas the legacy loop would send to
77
+ // the zero address. A zero target is almost always a bug — disallow it.
78
+ if (calls.some((c) => /^0x0{40}$/i.test(c.to))) {
79
+ throw new errors_1.PushChainExecutionError('A multicall entry has a zero `to` address. Use an explicit target ' +
80
+ '(the EIP-7702 executor reinterprets a zero target as the account itself).');
81
+ }
82
+ // Preferred path: execute the batch atomically in a single EIP-7702 type-4
83
+ // transaction. Requires (a) a deployed PushBatchExecutor on this chain and
84
+ // (b) a wallet that can sign a 7702 authorization. When either is missing we
85
+ // fall back to the legacy non-atomic per-call loop below.
86
+ const executor = (0, chain_1.getBatchExecutorAddress)(ctx.universalSigner.account.chain);
87
+ if (executor && ctx.universalSigner.signAuthorization) {
88
+ try {
89
+ (0, context_1.printLog)(ctx, `sendPushTx — executing ${calls.length} call(s) atomically via EIP-7702 (executor: ${executor})`);
90
+ const txHash = yield ctx.pushClient.sendBatch7702({
91
+ executor,
92
+ calls,
93
+ signer: ctx.universalSigner,
94
+ });
95
+ const txResponse = yield getIndexedPushTransaction(ctx, txHash, 'sendPushTx');
96
+ return yield transformFn(txResponse, eventBuffer);
97
+ }
98
+ catch (err) {
99
+ // Only fall back when the wallet genuinely can't sign a 7702
100
+ // authorization. This is raised before any broadcast, so falling back
101
+ // to sequential execution cannot double-execute. Any other error
102
+ // (e.g. a real revert) must surface.
103
+ if (!(err instanceof evm_client_1.EIP7702NotSupportedError))
104
+ throw err;
105
+ (0, context_1.printLog)(ctx, `sendPushTx — wallet cannot sign EIP-7702 authorization; falling back to sequential execution`);
106
+ // eslint-disable-next-line no-console
107
+ console.warn('[push-chain] Wallet cannot produce an EIP-7702 authorization — batch ' +
108
+ 'will execute as separate, non-atomic transactions (if one call reverts, ' +
109
+ 'earlier calls remain committed).');
110
+ }
111
+ }
112
+ else if (!ctx.universalSigner.signAuthorization) {
113
+ // eslint-disable-next-line no-console
114
+ console.warn('[push-chain] Signer does not support EIP-7702 — batch will execute as ' +
115
+ 'separate, non-atomic transactions (if one call reverts, earlier calls ' +
116
+ 'remain committed).');
117
+ }
72
118
  const PUSH_CHAIN_GAS_LIMIT = BigInt(500000);
73
119
  const MAX_NONCE_RETRIES = 3;
74
120
  let nonce = yield ctx.pushClient.publicClient.getTransactionCount({
@@ -76,7 +122,6 @@ function sendPushTx(ctx, execute, eventBuffer, transformFn) {
76
122
  blockTag: 'pending',
77
123
  });
78
124
  let lastTxHash = '0x';
79
- const calls = execute.data;
80
125
  for (let i = 0; i < calls.length; i++) {
81
126
  const call = calls[i];
82
127
  let txSent = false;
@@ -142,7 +187,11 @@ function sendPushTx(ctx, execute, eventBuffer, transformFn) {
142
187
  nonce++;
143
188
  }
144
189
  const txResponse = yield getIndexedPushTransaction(ctx, lastTxHash, 'sendPushTx');
145
- return yield transformFn(txResponse, eventBuffer);
190
+ // Sequential fallback: each call was a separate tx, so the batch is NOT
191
+ // atomic (an earlier call can be committed while a later one reverts).
192
+ const resp = yield transformFn(txResponse, eventBuffer);
193
+ resp.atomic = false;
194
+ return resp;
146
195
  }
147
196
  const txHash = yield ctx.pushClient.sendTransaction({
148
197
  to: execute.to,
@@ -263,11 +312,12 @@ function sendUniversalTx(ctx, isUEADeployed, feeLockTxHash, universalPayload, ve
263
312
  // wiring in `extractPcTxAndTransform` for the post-success `pcTx FAILED`
264
313
  // path. Also feeds the structured `decodedError` payload through to the
265
314
  // terminal hook (199-02 / 299-02 / 399-02 / 999-02).
266
- let augmentedMessage = (_c = tx.rawLog) !== null && _c !== void 0 ? _c : '';
315
+ const rawMessage = (_c = tx.rawLog) !== null && _c !== void 0 ? _c : '';
316
+ let publicMessage = (0, formatters_1.normalizePublicErrorMessage)(rawMessage);
267
317
  let decodedForPayload;
268
- const decoded = (0, error_decoder_1.decodeRevert)(augmentedMessage);
318
+ const decoded = (0, error_decoder_1.decodeRevert)(rawMessage);
269
319
  if ((decoded === null || decoded === void 0 ? void 0 : decoded.kind) === 'custom') {
270
- augmentedMessage = `${augmentedMessage} [${decoded.name}: ${decoded.hint}]`;
320
+ publicMessage = (0, error_decoder_1.formatDecodedRevertForUser)(decoded);
271
321
  decodedForPayload = {
272
322
  name: decoded.name,
273
323
  hint: decoded.hint,
@@ -275,7 +325,7 @@ function sendUniversalTx(ctx, isUEADeployed, feeLockTxHash, universalPayload, ve
275
325
  };
276
326
  }
277
327
  else if ((decoded === null || decoded === void 0 ? void 0 : decoded.kind) === 'string') {
278
- augmentedMessage = `${augmentedMessage} [Error(string): "${decoded.decoded}"]`;
328
+ publicMessage = (0, error_decoder_1.formatDecodedRevertForUser)(decoded);
279
329
  decodedForPayload = {
280
330
  decoded: decoded.decoded,
281
331
  selector: decoded.selector,
@@ -290,12 +340,12 @@ function sendUniversalTx(ctx, isUEADeployed, feeLockTxHash, universalPayload, ve
290
340
  `Block Height: ${tx.height}, TX Code: ${tx.code} (error)`,
291
341
  `Gas Used: ${tx.gasUsed}, Gas Wanted: ${tx.gasWanted}`,
292
342
  ...(failedEthTxHashes.length > 0 ? [`Ethereum TX Hash(es): ${failedEthTxHashes.join(', ')}`] : []),
293
- `Error: ${augmentedMessage}`,
343
+ `Error: ${publicMessage}`,
294
344
  ].join(' | ');
295
345
  (0, context_1.printLog)(ctx, failureSummary);
296
346
  // Throw a typed error so the orchestrator's outer catch can lift the
297
347
  // structured `decodedError` straight onto the terminal-hook payload.
298
- throw new errors_1.PushChainExecutionError(augmentedMessage, {
348
+ throw new errors_1.PushChainExecutionError(publicMessage, {
299
349
  decodedError: decodedForPayload,
300
350
  });
301
351
  }
@@ -335,8 +385,9 @@ function extractPcTxAndTransform(ctx, pushChainUniversalTx, gatewayTxHash, event
335
385
  const failedPcTx = (lastPcTransaction === null || lastPcTransaction === void 0 ? void 0 : lastPcTransaction.status) === 'FAILED'
336
386
  ? lastPcTransaction
337
387
  : pushChainUniversalTx.pcTx.find((pcTx) => pcTx.status === 'FAILED' && pcTx.errorMsg);
338
- const failedPcTxErrorMsg = (failedPcTx === null || failedPcTx === void 0 ? void 0 : failedPcTx.errorMsg)
339
- ? (0, formatters_1.normalizePcInsufficientFundsError)(failedPcTx.errorMsg)
388
+ const rawFailedPcTxErrorMsg = (_b = failedPcTx === null || failedPcTx === void 0 ? void 0 : failedPcTx.errorMsg) !== null && _b !== void 0 ? _b : '';
389
+ let failedPcTxErrorMsg = rawFailedPcTxErrorMsg
390
+ ? (0, formatters_1.normalizePublicErrorMessage)(rawFailedPcTxErrorMsg)
340
391
  : '';
341
392
  const errorDetails = failedPcTxErrorMsg ? `: ${failedPcTxErrorMsg}` : '';
342
393
  // Decode known revert selectors and ABI-encoded `Error(string)` reverts
@@ -344,9 +395,10 @@ function extractPcTxAndTransform(ctx, pushChainUniversalTx, gatewayTxHash, event
344
395
  // table can grow from real reverts.
345
396
  let hint = '';
346
397
  let decodedForPayload;
347
- const decoded = (0, error_decoder_1.decodeRevert)((_b = failedPcTx === null || failedPcTx === void 0 ? void 0 : failedPcTx.errorMsg) !== null && _b !== void 0 ? _b : '');
398
+ const decoded = (0, error_decoder_1.decodeRevert)(rawFailedPcTxErrorMsg);
348
399
  if ((decoded === null || decoded === void 0 ? void 0 : decoded.kind) === 'custom') {
349
- hint = ` [${decoded.name}: ${decoded.hint}]`;
400
+ failedPcTxErrorMsg = (0, error_decoder_1.formatDecodedRevertForUser)(decoded);
401
+ hint = '';
350
402
  decodedForPayload = {
351
403
  name: decoded.name,
352
404
  hint: decoded.hint,
@@ -354,7 +406,8 @@ function extractPcTxAndTransform(ctx, pushChainUniversalTx, gatewayTxHash, event
354
406
  };
355
407
  }
356
408
  else if ((decoded === null || decoded === void 0 ? void 0 : decoded.kind) === 'string') {
357
- hint = ` [Error(string): "${decoded.decoded}"]`;
409
+ failedPcTxErrorMsg = (0, error_decoder_1.formatDecodedRevertForUser)(decoded);
410
+ hint = '';
358
411
  decodedForPayload = {
359
412
  decoded: decoded.decoded,
360
413
  selector: decoded.selector,
@@ -363,11 +416,14 @@ function extractPcTxAndTransform(ctx, pushChainUniversalTx, gatewayTxHash, event
363
416
  else if ((decoded === null || decoded === void 0 ? void 0 : decoded.kind) === 'unknown') {
364
417
  (0, context_1.printLog)(ctx, `[error-decoder] unknown selector ${decoded.selector} — consider adding to KNOWN_ERROR_SELECTORS`);
365
418
  }
366
- const fullMessage = `Push Chain transaction failed for gateway tx: ${gatewayTxHash}${errorDetails}${hint}`;
419
+ const decodedErrorDetails = failedPcTxErrorMsg
420
+ ? `: ${failedPcTxErrorMsg}`
421
+ : errorDetails;
422
+ const fullMessage = `Push Chain transaction failed for gateway tx: ${gatewayTxHash}${decodedErrorDetails}${hint}`;
367
423
  // Live-emit 199-02 before throwing so the terminal failure hook reaches
368
424
  // any caller-registered progress stream (parity with reconstructProgressEvents
369
425
  // which emits 199-02 on replay).
370
- (0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_199_02, fullMessage, decodedForPayload);
426
+ (0, context_1.fireProgressHook)(ctx, progress_hook_types_1.PROGRESS_HOOK.SEND_TX_199_02, fullMessage, decodedForPayload, failedPcTx === null || failedPcTx === void 0 ? void 0 : failedPcTx.txHash);
371
427
  throw new errors_1.PushChainExecutionError(fullMessage, {
372
428
  gatewayTxHash,
373
429
  decodedError: decodedForPayload,
@@ -687,12 +687,12 @@ function transformToUniversalTxResponse(ctx_1, tx_1) {
687
687
  fanOut(event, registeredProgressHook, ctx.progressHook);
688
688
  };
689
689
  // Awaiting relay
690
- emit(hooks.awaiting(targetChain));
690
+ emit(hooks.awaiting(targetChain, universalTxResponse.hash));
691
691
  let lastEmittedStatus;
692
692
  const outboundTranslator = (event) => {
693
693
  if (event.status === 'polling' && lastEmittedStatus !== 'polling') {
694
694
  lastEmittedStatus = 'polling';
695
- emit(hooks.polling(targetChain, event.elapsed));
695
+ emit(hooks.polling(targetChain, event.elapsed, universalTxResponse.hash));
696
696
  }
697
697
  };
698
698
  try {
@@ -721,7 +721,7 @@ function transformToUniversalTxResponse(ctx_1, tx_1) {
721
721
  // otherwise time out after 300s.
722
722
  if (route === route_detector_1.TransactionRoute.CEA_TO_PUSH &&
723
723
  universalTxResponse._expectsInboundRoundTrip === true) {
724
- emit(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_310_01](targetChain));
724
+ emit(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_310_01](targetChain, universalTxResponse.hash));
725
725
  let inboundLastEmitted;
726
726
  try {
727
727
  const inbound = yield (0, inbound_tracker_1.waitForInboundPushTx)(ctx, outboundDetails.externalTxHash, targetChain, {
@@ -732,7 +732,7 @@ function transformToUniversalTxResponse(ctx_1, tx_1) {
732
732
  if (event.status === 'polling' &&
733
733
  inboundLastEmitted !== 'polling') {
734
734
  inboundLastEmitted = 'polling';
735
- emit(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_310_02](targetChain, event.elapsedMs));
735
+ emit(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_310_02](targetChain, event.elapsedMs, universalTxResponse.hash));
736
736
  }
737
737
  },
738
738
  });
@@ -743,7 +743,7 @@ function transformToUniversalTxResponse(ctx_1, tx_1) {
743
743
  else {
744
744
  // status === 'failed' (terminal failure status from chain)
745
745
  const failMsg = (_g = inbound.errorMessage) !== null && _g !== void 0 ? _g : 'inbound execution failed on Push Chain';
746
- emit(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_399_02](failMsg, 'inbound', targetChain));
746
+ emit(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_399_02](failMsg, 'inbound', targetChain, undefined, universalTxResponse.hash, inbound.pushTxHash || undefined));
747
747
  baseReceipt = Object.assign(Object.assign({}, baseReceipt), { pushInboundUtxId: inbound.childUtxId, pushInboundTxHash: inbound.pushTxHash || undefined, externalStatus: 'failed', externalError: failMsg });
748
748
  }
749
749
  }
@@ -755,8 +755,8 @@ function transformToUniversalTxResponse(ctx_1, tx_1) {
755
755
  // Annotate receipt for inbound-leg outcome too.
756
756
  baseReceipt = Object.assign(Object.assign({}, baseReceipt), { externalStatus: isTimeout ? 'timeout' : 'failed', externalError: errMsg });
757
757
  emit(isTimeout
758
- ? progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_399_03](targetChain, inboundTimeoutMs, 'inbound')
759
- : progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_399_02](errMsg, 'inbound', targetChain));
758
+ ? progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_399_03](targetChain, inboundTimeoutMs, 'inbound', universalTxResponse.hash)
759
+ : progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_399_02](errMsg, 'inbound', targetChain, undefined, universalTxResponse.hash));
760
760
  callbacks.printLog(`[wait] R3 inbound tracking failed: ${errMsg}`);
761
761
  }
762
762
  }
@@ -764,15 +764,6 @@ function transformToUniversalTxResponse(ctx_1, tx_1) {
764
764
  catch (error) {
765
765
  const errMsg = error instanceof Error ? error.message : String(error);
766
766
  const isTimeout = error instanceof outbound_sync_1.OutboundTimeoutError;
767
- emit(isTimeout
768
- ? hooks.timeout(targetChain, outboundTimeoutMs)
769
- : hooks.failed(targetChain, errMsg));
770
- // Annotate the receipt so callers can distinguish external-leg
771
- // outcomes without sniffing errMsg. Push Chain leg still succeeded
772
- // (status stays 1); inspect `externalStatus` / `externalError` for
773
- // the external/inbound leg outcome. On a reverted outbound the
774
- // observed tx hash is carried by OutboundFailedError — expose it
775
- // as externalTxHash so consumers can link to the explorer.
776
767
  const failedExternalTxHash = error instanceof outbound_sync_1.OutboundFailedError ? error.externalTxHash : undefined;
777
768
  // Normalize to chain-native display form for SVM (base58); EVM hex
778
769
  // passes through unchanged. `OutboundFailedError.destinationChain`
@@ -782,6 +773,18 @@ function transformToUniversalTxResponse(ctx_1, tx_1) {
782
773
  ? (_h = (0, helpers_1.chainFromNamespace)(error.destinationChain)) !== null && _h !== void 0 ? _h : undefined
783
774
  : undefined;
784
775
  const failedExternalTxHashDisplay = (_j = (0, helpers_1.toExternalTxHashDisplay)(failedChain, failedExternalTxHash)) !== null && _j !== void 0 ? _j : failedExternalTxHash;
776
+ emit(isTimeout
777
+ ? hooks.timeout(targetChain, outboundTimeoutMs, universalTxResponse.hash)
778
+ : hooks.failed(targetChain, errMsg, {
779
+ pushTxHash: universalTxResponse.hash,
780
+ txHash: failedExternalTxHashDisplay,
781
+ }));
782
+ // Annotate the receipt so callers can distinguish external-leg
783
+ // outcomes without sniffing errMsg. Push Chain leg still succeeded
784
+ // (status stays 1); inspect `externalStatus` / `externalError` for
785
+ // the external/inbound leg outcome. On a reverted outbound the
786
+ // observed tx hash is carried by OutboundFailedError — expose it
787
+ // as externalTxHash so consumers can link to the explorer.
785
788
  baseReceipt = Object.assign(Object.assign(Object.assign({}, baseReceipt), { externalStatus: isTimeout ? 'timeout' : 'failed', externalError: errMsg }), (failedExternalTxHashDisplay
786
789
  ? { externalTxHash: failedExternalTxHashDisplay }
787
790
  : {}));
@@ -825,6 +828,12 @@ function transformToUniversalTxResponse(ctx_1, tx_1) {
825
828
  // 7. Metadata
826
829
  type,
827
830
  typeVerbose,
831
+ // Atomic by default — a single tx, an EIP-7702 batch, and a UEA multicall
832
+ // are all all-or-nothing. Only the native sequential fallback loop overrides
833
+ // this to `false` (see sendPushTx in push-chain-tx.ts). Historical lookups
834
+ // via trackTransaction also keep the default, since the past execution mode
835
+ // isn't always recoverable.
836
+ atomic: true,
828
837
  signature,
829
838
  // 8. Raw Universal Fields
830
839
  raw: rawTransactionData,
@@ -124,7 +124,7 @@ function reconstructProgressEvents(universalTxResponse, universalTxData) {
124
124
  events.push(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_103_03_04](universalTxResponse.gasLimit, BigInt(0)));
125
125
  events.push(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_107]());
126
126
  if (isFailed) {
127
- events.push(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_199_02](errorMsg));
127
+ events.push(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_199_02](errorMsg, undefined, universalTxResponse.hash));
128
128
  }
129
129
  else {
130
130
  events.push(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_199_01]([universalTxResponse]));
@@ -161,7 +161,7 @@ function reconstructR2(universalTxResponse, universalTxData, isFailed, errorMsg)
161
161
  events.push(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_204_03]());
162
162
  events.push(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_207](targetChain));
163
163
  if (isFailed) {
164
- events.push(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_299_02](errorMsg));
164
+ events.push(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_299_02](targetChain, errorMsg, undefined, universalTxResponse.hash));
165
165
  }
166
166
  return events;
167
167
  }
@@ -187,7 +187,7 @@ function reconstructR3(universalTxResponse, universalTxData, isFailed, errorMsg)
187
187
  if (isFailed) {
188
188
  // Reconstructed Push-chain-tx failure — phase='push' so the title reads
189
189
  // "Push Chain Tx Failed" rather than the inbound copy.
190
- events.push(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_399_02](errorMsg, 'push'));
190
+ events.push(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_399_02](errorMsg, 'push', undefined, undefined, undefined, universalTxResponse.hash));
191
191
  }
192
192
  return events;
193
193
  }
@@ -267,6 +267,7 @@ function decodeR3MergedInnerCount(outboundPayload) {
267
267
  return 0;
268
268
  }
269
269
  function reconstructMultichain(universalTxResponse, universalTxData, isFailed, errorMsg) {
270
+ var _a, _b, _c, _d, _e;
270
271
  const events = [];
271
272
  const outbounds = universalTxData.outboundTx;
272
273
  // Merged-R1 recovery: each R3 outbound's payload embeds the
@@ -343,14 +344,18 @@ function reconstructMultichain(universalTxResponse, universalTxData, isFailed, e
343
344
  failureMsg = legError;
344
345
  }
345
346
  });
347
+ const finalObservedTxHash = (_c = (_b = (_a = [...outbounds].reverse().find((ob) => { var _a; return (_a = ob.observedTx) === null || _a === void 0 ? void 0 : _a.txHash; })) === null || _a === void 0 ? void 0 : _a.observedTx) === null || _b === void 0 ? void 0 : _b.txHash) !== null && _c !== void 0 ? _c : universalTxResponse.hash;
348
+ const failedObservedTxHash = failedAt !== null
349
+ ? (_e = (_d = outbounds[failedAt - 1]) === null || _d === void 0 ? void 0 : _d.observedTx) === null || _e === void 0 ? void 0 : _e.txHash
350
+ : undefined;
346
351
  if (failedAt !== null) {
347
- events.push(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_999_02](failedAt, hopCount, failureMsg));
352
+ events.push(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_999_02](failedAt, hopCount, failureMsg, failedObservedTxHash, universalTxResponse.hash));
348
353
  }
349
354
  else if (isFailed) {
350
- events.push(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_999_02](hopCount, hopCount, errorMsg));
355
+ events.push(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_999_02](hopCount, hopCount, errorMsg, undefined, universalTxResponse.hash));
351
356
  }
352
357
  else {
353
- events.push(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_999_01](hopCount));
358
+ events.push(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_999_01](hopCount, finalObservedTxHash));
354
359
  }
355
360
  return events;
356
361
  }