@pushchain/core 5.1.17 → 6.0.0

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 (68) hide show
  1. package/package.json +1 -1
  2. package/src/lib/constants/abi/cea.evm.d.ts +52 -0
  3. package/src/lib/constants/abi/cea.evm.js +66 -1
  4. package/src/lib/constants/abi/prc20.evm.d.ts +117 -11
  5. package/src/lib/constants/abi/prc20.evm.js +38 -11
  6. package/src/lib/constants/abi/uea.evm.js +41 -0
  7. package/src/lib/constants/abi/universalGatewayPC.evm.d.ts +117 -1
  8. package/src/lib/constants/abi/universalGatewayPC.evm.js +40 -1
  9. package/src/lib/constants/abi/universalGatewayV0.json +1 -1
  10. package/src/lib/constants/chain.js +14 -14
  11. package/src/lib/constants/index.d.ts +1 -1
  12. package/src/lib/constants/index.js +2 -1
  13. package/src/lib/constants/selectors.d.ts +8 -0
  14. package/src/lib/constants/selectors.js +9 -1
  15. package/src/lib/constants/tokens.js +1 -0
  16. package/src/lib/formatters.d.ts +7 -0
  17. package/src/lib/formatters.js +46 -0
  18. package/src/lib/index.d.ts +2 -0
  19. package/src/lib/index.js +7 -1
  20. package/src/lib/orchestrator/internals/cascade.d.ts +8 -1
  21. package/src/lib/orchestrator/internals/cascade.js +593 -192
  22. package/src/lib/orchestrator/internals/error-decoder.d.ts +2 -6
  23. package/src/lib/orchestrator/internals/error-decoder.js +10 -10
  24. package/src/lib/orchestrator/internals/errors.d.ts +1 -1
  25. package/src/lib/orchestrator/internals/errors.js +8 -5
  26. package/src/lib/orchestrator/internals/execute-funds-only.js +3 -2
  27. package/src/lib/orchestrator/internals/execute-funds-payload.js +2 -1
  28. package/src/lib/orchestrator/internals/execute-standard.js +11 -10
  29. package/src/lib/orchestrator/internals/gas-calculator.d.ts +1 -0
  30. package/src/lib/orchestrator/internals/gas-calculator.js +91 -22
  31. package/src/lib/orchestrator/internals/helpers.d.ts +2 -1
  32. package/src/lib/orchestrator/internals/helpers.js +14 -11
  33. package/src/lib/orchestrator/internals/inbound-tracker.js +4 -2
  34. package/src/lib/orchestrator/internals/outbound-sync.d.ts +1 -0
  35. package/src/lib/orchestrator/internals/outbound-sync.js +140 -71
  36. package/src/lib/orchestrator/internals/pc-usd-oracle.d.ts +1 -1
  37. package/src/lib/orchestrator/internals/pc-usd-oracle.js +4 -3
  38. package/src/lib/orchestrator/internals/preflight.d.ts +2 -16
  39. package/src/lib/orchestrator/internals/preflight.js +2 -36
  40. package/src/lib/orchestrator/internals/push-chain-tx.js +5 -1
  41. package/src/lib/orchestrator/internals/response-builder.js +23 -7
  42. package/src/lib/orchestrator/internals/route-handlers.js +182 -227
  43. package/src/lib/orchestrator/internals/signing.js +7 -2
  44. package/src/lib/orchestrator/internals/svm-helpers.js +3 -3
  45. package/src/lib/orchestrator/internals/svm-rent.d.ts +42 -9
  46. package/src/lib/orchestrator/internals/svm-rent.js +148 -14
  47. package/src/lib/orchestrator/internals/tx-transformer.js +5 -2
  48. package/src/lib/orchestrator/max-pc-for-gas.d.ts +60 -0
  49. package/src/lib/orchestrator/max-pc-for-gas.js +66 -0
  50. package/src/lib/orchestrator/orchestrator.d.ts +8 -1
  51. package/src/lib/orchestrator/orchestrator.js +40 -2
  52. package/src/lib/orchestrator/orchestrator.types.d.ts +35 -23
  53. package/src/lib/orchestrator/payload-builders.d.ts +7 -6
  54. package/src/lib/orchestrator/payload-builders.js +18 -6
  55. package/src/lib/progress-hook/progress-hook.js +39 -38
  56. package/src/lib/push-chain/push-chain.d.ts +11 -1
  57. package/src/lib/push-chain/push-chain.js +2 -2
  58. package/src/lib/universal-tx-detector/cascade.js +14 -4
  59. package/src/lib/universal-tx-detector/child-inbounds.js +9 -1
  60. package/src/lib/universal-tx-detector/detector.js +9 -3
  61. package/src/lib/universal-tx-detector/events.d.ts +3 -1
  62. package/src/lib/universal-tx-detector/events.js +3 -1
  63. package/src/lib/universal-tx-detector/normalize.d.ts +42 -0
  64. package/src/lib/universal-tx-detector/normalize.js +127 -0
  65. package/src/lib/universal-tx-detector/svm-events.d.ts +1 -1
  66. package/src/lib/universal-tx-detector/svm-events.js +1 -1
  67. package/src/lib/utils/external-tx-hash.d.ts +37 -0
  68. package/src/lib/utils/external-tx-hash.js +62 -0
@@ -50,6 +50,12 @@ export type ExecuteParams = {
50
50
  * @reason Prevents runaway gas consumption and lets users enforce limits.
51
51
  */
52
52
  gasLimit?: bigint;
53
+ /**
54
+ * Optional cap on native PC used for outbound gas swap.
55
+ * Applies only to Push -> external-chain outbound legs. Omit or set 0n for
56
+ * uncapped legacy behavior.
57
+ */
58
+ maxPCForGas?: bigint;
53
59
  /**
54
60
  * Optional override for the EIP-1559 max fee per gas (in wei).
55
61
  * @reason Gives callers direct control over total gas price to speed up or save cost.
@@ -397,20 +403,6 @@ export type UniversalExecuteParams = Omit<ExecuteParams, 'to'> & {
397
403
  * Only applicable for Route 2 (UOA_TO_CEA) on EVM chains.
398
404
  */
399
405
  migration?: boolean;
400
- /**
401
- * Per-call SDK behaviour overrides. Reserved for opt-out flags that must
402
- * not affect the global orchestrator config.
403
- */
404
- options?: {
405
- /**
406
- * Restore the legacy clamp-and-refund behaviour for outbound paths whose
407
- * UEA balance is below the buffered pool quote. Default `false` — the
408
- * SDK throws `InsufficientUEABalanceError` (see `runPreflight`) and does
409
- * NOT submit a cosmos tx. Set `true` only when the caller intentionally
410
- * relies on `swapAndBurnGas` refunds to power through low balances.
411
- */
412
- allowUnderfundedSwap?: boolean;
413
- };
414
406
  };
415
407
  /**
416
408
  * Account metadata for SVM CPI execution (Solana Gateway pattern).
@@ -440,25 +432,26 @@ export interface SvmExecutePayloadFields {
440
432
  * Request structure for sendUniversalTxOutbound on Push Chain
441
433
  * Used for Routes 2, 3, 4 (outbound from Push)
442
434
  *
443
- * NOTE: The `target` field is a LEGACY parameter for contract compatibility.
444
- * The deployed UniversalGatewayPC contract still expects this field, but it will
445
- * be removed in future contract upgrades. Pass any non-zero address (e.g., CEA address).
446
- * The actual destination is determined by the relay from the token's SOURCE_CHAIN_NAMESPACE.
435
+ * NOTE: The `target` field is raw recipient bytes for the destination chain.
436
+ * For EVM CEA funds-parking, use the zero recipient when `payload` is empty.
447
437
  */
448
438
  export interface UniversalOutboundTxRequest {
449
439
  /**
450
- * LEGACY/DUMMY: Raw destination address bytes for contract compatibility.
451
- * Pass any non-zero address - this value is NOT used by the relay to determine
452
- * the actual transaction destination. Will be removed in future contract upgrades.
440
+ * Raw destination recipient bytes. EVM routes usually pass a 20-byte address;
441
+ * SVM routes pass a 32-byte program/account address.
453
442
  */
454
443
  target: `0x${string}`;
455
444
  /** PRC20 token address on Push Chain to burn */
456
445
  token: `0x${string}`;
457
446
  /** Amount to burn (0 for no-burn, use existing CEA balance) */
458
447
  amount: bigint;
459
- /** Gas limit for fee quote (0 = default BASE_GAS_LIMIT) */
448
+ /** Gas limit for fee quote (0 = per-chain default resolved by UniversalCore) */
460
449
  gasLimit: bigint;
461
- /** Raw ABI-encoded Multicall[] (no selector prefix) */
450
+ /** Gas price override for destination-chain gas (0 = UniversalCore default) */
451
+ gasPrice: bigint;
452
+ /** Max native PC that may be used for gas swap (0 = uncapped legacy behavior) */
453
+ maxPCForGas: bigint;
454
+ /** Destination execution payload; empty only for explicit funds parking */
462
455
  payload: `0x${string}`;
463
456
  /** Address to receive funds on revert */
464
457
  revertRecipient: `0x${string}`;
@@ -502,8 +495,12 @@ export interface HopDescriptor {
502
495
  gasToken?: `0x${string}`;
503
496
  /** Gas fee amount in gas token */
504
497
  gasFee?: bigint;
498
+ /** Destination-chain gas price quoted by UniversalCore */
499
+ gasPrice?: bigint;
505
500
  /** Gas limit for outbound relay */
506
501
  gasLimit: bigint;
502
+ /** Max native PC used for outbound gas swap (0 = uncapped) */
503
+ maxPCForGas: bigint;
507
504
  /** UEA address */
508
505
  ueaAddress: `0x${string}`;
509
506
  /** Address to receive funds on revert */
@@ -514,6 +511,10 @@ export interface HopDescriptor {
514
511
  svmPayload?: `0x${string}`;
515
512
  /** Whether this hop is a CEA migration (raw MIGRATION_SELECTOR payload) */
516
513
  isMigration?: boolean;
514
+ /** Whether this Route 3 hop only seeds native PC into the UEA. @internal */
515
+ nativeSeedOnly?: boolean;
516
+ /** Native PC amount seeded by a value-only Route 3 hop. @internal */
517
+ nativeSeedAmount?: bigint;
517
518
  /** SDK 5.2 gas-abstraction sizing decision for this hop. @internal */
518
519
  sizing?: import('./internals/gas-usd-sizer').GasSizingDecision;
519
520
  }
@@ -548,8 +549,12 @@ export interface CascadeSegment {
548
549
  gasToken?: `0x${string}`;
549
550
  /** Total gas fee for this segment */
550
551
  gasFee?: bigint;
552
+ /** Destination-chain gas price quoted by UniversalCore */
553
+ gasPrice?: bigint;
551
554
  /** Gas limit for this segment */
552
555
  gasLimit?: bigint;
556
+ /** Max native PC used for outbound gas swap (0 = uncapped) */
557
+ maxPCForGas?: bigint;
553
558
  /**
554
559
  * SDK 5.2 gas-abstraction sizing decision for this segment. When the
555
560
  * segment merges multiple hops, the strictest (highest category) sizing
@@ -766,6 +771,13 @@ export interface WaitForOutboundOptions {
766
771
  * has multiple outbound operations to different chains.
767
772
  */
768
773
  _expectedDestinationChain?: string;
774
+ /**
775
+ * @internal Zero-based index among outbound entries that match
776
+ * `_expectedDestinationChain`. Used when a composed cascade emits multiple
777
+ * outbound operations to the same chain and a later hop must track the later
778
+ * observed tx rather than the first one.
779
+ */
780
+ _outboundIndex?: number;
769
781
  }
770
782
  /**
771
783
  * UEA deployment and version status.
@@ -70,18 +70,19 @@ export declare function buildSendUniversalTxToUEA(ceaAddress: `0x${string}`, tok
70
70
  /**
71
71
  * Build UniversalOutboundTxRequest for Push Chain outbound
72
72
  *
73
- * @param target - LEGACY/DUMMY: Any non-zero address for contract compatibility.
74
- * This value is NOT used by the relay to determine the actual destination.
75
- * The relay determines the destination from the PRC-20 token's SOURCE_CHAIN_NAMESPACE.
76
- * Will be removed in future contract upgrades.
73
+ * @param target - Raw recipient bytes for the destination chain. For EVM CEA
74
+ * funds-parking, use the zero recipient when payload is empty.
77
75
  * @param prc20Token - PRC20 token address to burn (or address(0) for native)
78
76
  * @param amount - Amount to burn
79
77
  * @param gasLimit - Gas limit for fee calculation
80
- * @param payload - CEA multicall payload
78
+ * @param payload - Destination execution payload
81
79
  * @param revertRecipient - Address to receive funds on revert
80
+ * @param maxPCForGas - Max native PC used for gas swap (0 = uncapped)
81
+ * @param gasPrice - Destination-chain gas price override (0 = UniversalCore default)
82
82
  * @returns UniversalOutboundTxRequest object
83
83
  */
84
- export declare function buildOutboundRequest(target: `0x${string}`, prc20Token: `0x${string}`, amount: bigint, gasLimit: bigint, payload: `0x${string}`, revertRecipient: `0x${string}`): UniversalOutboundTxRequest;
84
+ export declare function buildOutboundRequest(target: `0x${string}`, prc20Token: `0x${string}`, amount: bigint, gasLimit: bigint, payload: `0x${string}`, revertRecipient: `0x${string}`, maxPCForGas?: bigint, gasPrice?: bigint): UniversalOutboundTxRequest;
85
+ export declare function assertCeaFundsParkingInvariant(recipient: `0x${string}`, payload: `0x${string}`): void;
85
86
  /**
86
87
  * Build ERC20 transfer multicall
87
88
  *
@@ -6,6 +6,7 @@ exports.buildCeaMulticallPayload = buildCeaMulticallPayload;
6
6
  exports.buildInboundUniversalPayload = buildInboundUniversalPayload;
7
7
  exports.buildSendUniversalTxToUEA = buildSendUniversalTxToUEA;
8
8
  exports.buildOutboundRequest = buildOutboundRequest;
9
+ exports.assertCeaFundsParkingInvariant = assertCeaFundsParkingInvariant;
9
10
  exports.buildErc20Transfer = buildErc20Transfer;
10
11
  exports.buildErc20WithdrawalMulticall = buildErc20WithdrawalMulticall;
11
12
  exports.buildMigrationPayload = buildMigrationPayload;
@@ -225,27 +226,38 @@ function buildSendUniversalTxToUEA(ceaAddress, token, amount, payload, revertRec
225
226
  /**
226
227
  * Build UniversalOutboundTxRequest for Push Chain outbound
227
228
  *
228
- * @param target - LEGACY/DUMMY: Any non-zero address for contract compatibility.
229
- * This value is NOT used by the relay to determine the actual destination.
230
- * The relay determines the destination from the PRC-20 token's SOURCE_CHAIN_NAMESPACE.
231
- * Will be removed in future contract upgrades.
229
+ * @param target - Raw recipient bytes for the destination chain. For EVM CEA
230
+ * funds-parking, use the zero recipient when payload is empty.
232
231
  * @param prc20Token - PRC20 token address to burn (or address(0) for native)
233
232
  * @param amount - Amount to burn
234
233
  * @param gasLimit - Gas limit for fee calculation
235
- * @param payload - CEA multicall payload
234
+ * @param payload - Destination execution payload
236
235
  * @param revertRecipient - Address to receive funds on revert
236
+ * @param maxPCForGas - Max native PC used for gas swap (0 = uncapped)
237
+ * @param gasPrice - Destination-chain gas price override (0 = UniversalCore default)
237
238
  * @returns UniversalOutboundTxRequest object
238
239
  */
239
- function buildOutboundRequest(target, prc20Token, amount, gasLimit, payload, revertRecipient) {
240
+ function buildOutboundRequest(target, prc20Token, amount, gasLimit, payload, revertRecipient, maxPCForGas = BigInt(0), gasPrice = BigInt(0)) {
240
241
  return {
241
242
  target,
242
243
  token: prc20Token,
243
244
  amount,
244
245
  gasLimit,
246
+ gasPrice,
247
+ maxPCForGas,
245
248
  payload,
246
249
  revertRecipient,
247
250
  };
248
251
  }
252
+ function assertCeaFundsParkingInvariant(recipient, payload) {
253
+ if (payload !== '0x')
254
+ return;
255
+ if (recipient === '0x')
256
+ return;
257
+ if (recipient.toLowerCase() === selectors_1.ZERO_ADDRESS.toLowerCase())
258
+ return;
259
+ throw new Error('Empty CEA payloads must use the zero recipient to park funds');
260
+ }
249
261
  /**
250
262
  * Build ERC20 transfer multicall
251
263
  *
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.classifyDeclineError = classifyDeclineError;
4
+ const formatters_1 = require("../formatters");
4
5
  const utils_1 = require("../utils");
5
6
  const progress_hook_types_1 = require("./progress-hook.types");
6
7
  // Helper to wrap a hook function with timestamp
@@ -22,14 +23,6 @@ const friendlyChain = (chainOrNs) => {
22
23
  const key = utils_1.Utils.chains.getChainName(asString);
23
24
  return key ? toTitleCase(key) : asString;
24
25
  };
25
- /**
26
- * Format a wei-denominated UPC bigint as a human-readable PC string for use
27
- * in progress-hook `message` fields. Used by the pre-flight hooks
28
- * (203-03/04/05 + 003-03/04/05) so consumers see "956.7834 PC" instead of
29
- * "956783432238079848707 wei UPC". Response payload bigints are NOT
30
- * transformed — programmatic consumers still get raw wei.
31
- */
32
- const formatPC = (wei, precision = 4) => `${utils_1.Utils.helpers.formatUnits(wei, { decimals: 18, precision })} PC`;
33
26
  /**
34
27
  * Classify a sign-time error as either a true wallet user-decline
35
28
  * (viem `UserRejectedRequestError`, ethers `ACTION_REJECTED`, EIP-1193 4001,
@@ -258,13 +251,16 @@ const RAW_HOOKS_R1 = {
258
251
  level: 'SUCCESS',
259
252
  };
260
253
  },
261
- [progress_hook_types_1.PROGRESS_HOOK.SEND_TX_199_02]: (errMessage, decodedError) => ({
262
- id: progress_hook_types_1.PROGRESS_HOOK.SEND_TX_199_02,
263
- title: 'Push Chain Tx Failed',
264
- message: errMessage,
265
- response: Object.assign({ error: errMessage }, (decodedError ? { decodedError } : {})),
266
- level: 'ERROR',
267
- }),
254
+ [progress_hook_types_1.PROGRESS_HOOK.SEND_TX_199_02]: (errMessage, decodedError) => {
255
+ const normalizedErrMessage = (0, formatters_1.normalizePcInsufficientFundsError)(errMessage);
256
+ return {
257
+ id: progress_hook_types_1.PROGRESS_HOOK.SEND_TX_199_02,
258
+ title: 'Push Chain Tx Failed',
259
+ message: normalizedErrMessage,
260
+ response: Object.assign({ error: normalizedErrMessage }, (decodedError ? { decodedError } : {})),
261
+ level: 'ERROR',
262
+ };
263
+ },
268
264
  };
269
265
  // =============================================================================
270
266
  // Route 2 — UEA → UGPC → CEA on target chain (201–299, signature-only)
@@ -313,8 +309,8 @@ const RAW_HOOKS_R2 = {
313
309
  [progress_hook_types_1.PROGRESS_HOOK.SEND_TX_203_03]: (required, available, sufficient, ueaAddress, pathTag, extra) => {
314
310
  var _a, _b, _c;
315
311
  const kind = (_a = extra === null || extra === void 0 ? void 0 : extra.kind) !== null && _a !== void 0 ? _a : 'NATIVE';
316
- const availableStr = kind === 'PRC20' ? `${available} units` : formatPC(available);
317
- const requiredStr = kind === 'PRC20' ? `${required} units` : formatPC(required);
312
+ const availableStr = kind === 'PRC20' ? `${available} units` : (0, formatters_1.formatPc)(available, 4);
313
+ const requiredStr = kind === 'PRC20' ? `${required} units` : (0, formatters_1.formatPc)(required, 4);
318
314
  return {
319
315
  id: progress_hook_types_1.PROGRESS_HOOK.SEND_TX_203_03,
320
316
  title: 'Pre-flight Balance Check',
@@ -336,7 +332,7 @@ const RAW_HOOKS_R2 = {
336
332
  [progress_hook_types_1.PROGRESS_HOOK.SEND_TX_203_04]: (required, available, shortfall, ueaAddress, pathTag, extra) => {
337
333
  var _a, _b, _c, _d;
338
334
  const kind = (_a = extra === null || extra === void 0 ? void 0 : extra.kind) !== null && _a !== void 0 ? _a : 'NATIVE';
339
- const shortfallStr = kind === 'PRC20' ? `${shortfall} units` : formatPC(shortfall);
335
+ const shortfallStr = kind === 'PRC20' ? `${shortfall} units` : (0, formatters_1.formatPc)(shortfall, 4);
340
336
  const remediation = kind === 'PRC20'
341
337
  ? `Bridge the burn token (${(_b = extra === null || extra === void 0 ? void 0 : extra.burnToken) !== null && _b !== void 0 ? _b : 'PRC-20'}) to UEA ${ueaAddress} before retrying.`
342
338
  : `Bridge >=${shortfallStr} to UEA ${ueaAddress} before retrying.`;
@@ -360,7 +356,7 @@ const RAW_HOOKS_R2 = {
360
356
  [progress_hook_types_1.PROGRESS_HOOK.SEND_TX_203_05]: (quoted, threshold, gasToken, pathTag) => ({
361
357
  id: progress_hook_types_1.PROGRESS_HOOK.SEND_TX_203_05,
362
358
  title: 'SVM Native-Value Warn Threshold',
363
- message: `Buffered pool quote ${formatPC(quoted)} exceeds warn threshold ${formatPC(threshold)} ` +
359
+ message: `Buffered pool quote ${(0, formatters_1.formatPc)(quoted, 4)} exceeds warn threshold ${(0, formatters_1.formatPc)(threshold, 4)} ` +
364
360
  `for ${gasToken} (${pathTag}). Pool may be skewed or quote is unusually large. ` +
365
361
  `No action taken — pre-flight will determine if balance covers it.`,
366
362
  response: { quoted, threshold, gasToken, pathTag },
@@ -527,8 +523,8 @@ const RAW_HOOKS_R3 = {
527
523
  [progress_hook_types_1.PROGRESS_HOOK.SEND_TX_303_04]: (required, available, sufficient, ueaAddress, pathTag, extra) => {
528
524
  var _a, _b, _c;
529
525
  const kind = (_a = extra === null || extra === void 0 ? void 0 : extra.kind) !== null && _a !== void 0 ? _a : 'NATIVE';
530
- const availableStr = kind === 'PRC20' ? `${available} units` : formatPC(available);
531
- const requiredStr = kind === 'PRC20' ? `${required} units` : formatPC(required);
526
+ const availableStr = kind === 'PRC20' ? `${available} units` : (0, formatters_1.formatPc)(available, 4);
527
+ const requiredStr = kind === 'PRC20' ? `${required} units` : (0, formatters_1.formatPc)(required, 4);
532
528
  return {
533
529
  id: progress_hook_types_1.PROGRESS_HOOK.SEND_TX_303_04,
534
530
  title: 'Pre-flight Balance Check',
@@ -550,7 +546,7 @@ const RAW_HOOKS_R3 = {
550
546
  [progress_hook_types_1.PROGRESS_HOOK.SEND_TX_303_05]: (required, available, shortfall, ueaAddress, pathTag, extra) => {
551
547
  var _a, _b, _c, _d;
552
548
  const kind = (_a = extra === null || extra === void 0 ? void 0 : extra.kind) !== null && _a !== void 0 ? _a : 'NATIVE';
553
- const shortfallStr = kind === 'PRC20' ? `${shortfall} units` : formatPC(shortfall);
549
+ const shortfallStr = kind === 'PRC20' ? `${shortfall} units` : (0, formatters_1.formatPc)(shortfall, 4);
554
550
  const remediation = kind === 'PRC20'
555
551
  ? `Bridge the burn token (${(_b = extra === null || extra === void 0 ? void 0 : extra.burnToken) !== null && _b !== void 0 ? _b : 'PRC-20'}) to UEA ${ueaAddress} before retrying.`
556
552
  : `Bridge >=${shortfallStr} to UEA ${ueaAddress} before retrying.`;
@@ -574,7 +570,7 @@ const RAW_HOOKS_R3 = {
574
570
  [progress_hook_types_1.PROGRESS_HOOK.SEND_TX_303_06]: (quoted, threshold, gasToken, pathTag) => ({
575
571
  id: progress_hook_types_1.PROGRESS_HOOK.SEND_TX_303_06,
576
572
  title: 'SVM Native-Value Warn Threshold',
577
- message: `Buffered pool quote ${formatPC(quoted)} exceeds warn threshold ${formatPC(threshold)} ` +
573
+ message: `Buffered pool quote ${(0, formatters_1.formatPc)(quoted, 4)} exceeds warn threshold ${(0, formatters_1.formatPc)(threshold, 4)} ` +
578
574
  `for ${gasToken} (${pathTag}). Pool may be skewed or quote is unusually large. ` +
579
575
  `No action taken — pre-flight will determine if balance covers it.`,
580
576
  response: { quoted, threshold, gasToken, pathTag },
@@ -671,17 +667,22 @@ const RAW_HOOKS_R3 = {
671
667
  // Phase is passed by the emission site so the title matches the leg that
672
668
  // actually failed. Default stays 'inbound' for callers that don't specify
673
669
  // (backwards-compat with fixtures + response-builder's inbound block).
674
- [progress_hook_types_1.PROGRESS_HOOK.SEND_TX_399_02]: (errorMessage, phase = 'inbound', chain, decodedError) => ({
675
- id: progress_hook_types_1.PROGRESS_HOOK.SEND_TX_399_02,
676
- title: phase === 'outbound'
677
- ? `${friendlyChain(chain)} Tx Failed`
678
- : phase === 'push'
679
- ? 'Push Chain Tx Failed'
680
- : 'Push Chain Inbound Tx Failed',
681
- message: errorMessage,
682
- response: Object.assign({ error: errorMessage, phase, chain: chain !== null && chain !== void 0 ? chain : null }, (decodedError ? { decodedError } : {})),
683
- level: 'ERROR',
684
- }),
670
+ [progress_hook_types_1.PROGRESS_HOOK.SEND_TX_399_02]: (errorMessage, phase = 'inbound', chain, decodedError) => {
671
+ const normalizedErrorMessage = phase === 'push'
672
+ ? (0, formatters_1.normalizePcInsufficientFundsError)(errorMessage)
673
+ : errorMessage;
674
+ return {
675
+ id: progress_hook_types_1.PROGRESS_HOOK.SEND_TX_399_02,
676
+ title: phase === 'outbound'
677
+ ? `${friendlyChain(chain)} Tx Failed`
678
+ : phase === 'push'
679
+ ? 'Push Chain Tx Failed'
680
+ : 'Push Chain Inbound Tx Failed',
681
+ message: normalizedErrorMessage,
682
+ response: Object.assign({ error: normalizedErrorMessage, phase, chain: chain !== null && chain !== void 0 ? chain : null }, (decodedError ? { decodedError } : {})),
683
+ level: 'ERROR',
684
+ };
685
+ },
685
686
  [progress_hook_types_1.PROGRESS_HOOK.SEND_TX_399_03]: (sourceChain, elapsedMs, phase = 'inbound') => ({
686
687
  id: progress_hook_types_1.PROGRESS_HOOK.SEND_TX_399_03,
687
688
  title: phase === 'outbound'
@@ -786,8 +787,8 @@ const RAW_HOOKS_MULTICHAIN = {
786
787
  [progress_hook_types_1.PROGRESS_HOOK.SEND_TX_003_03]: (required, available, sufficient, ueaAddress, pathTag, extra) => {
787
788
  var _a, _b, _c;
788
789
  const kind = (_a = extra === null || extra === void 0 ? void 0 : extra.kind) !== null && _a !== void 0 ? _a : 'NATIVE';
789
- const availableStr = kind === 'PRC20' ? `${available} units` : formatPC(available);
790
- const requiredStr = kind === 'PRC20' ? `${required} units` : formatPC(required);
790
+ const availableStr = kind === 'PRC20' ? `${available} units` : (0, formatters_1.formatPc)(available, 4);
791
+ const requiredStr = kind === 'PRC20' ? `${required} units` : (0, formatters_1.formatPc)(required, 4);
791
792
  return {
792
793
  id: progress_hook_types_1.PROGRESS_HOOK.SEND_TX_003_03,
793
794
  title: 'Cascade Pre-flight Balance Check',
@@ -809,7 +810,7 @@ const RAW_HOOKS_MULTICHAIN = {
809
810
  [progress_hook_types_1.PROGRESS_HOOK.SEND_TX_003_04]: (required, available, shortfall, ueaAddress, pathTag, extra) => {
810
811
  var _a, _b, _c, _d;
811
812
  const kind = (_a = extra === null || extra === void 0 ? void 0 : extra.kind) !== null && _a !== void 0 ? _a : 'NATIVE';
812
- const shortfallStr = kind === 'PRC20' ? `${shortfall} units` : formatPC(shortfall);
813
+ const shortfallStr = kind === 'PRC20' ? `${shortfall} units` : (0, formatters_1.formatPc)(shortfall, 4);
813
814
  const remediation = kind === 'PRC20'
814
815
  ? `Bridge the burn token (${(_b = extra === null || extra === void 0 ? void 0 : extra.burnToken) !== null && _b !== void 0 ? _b : 'PRC-20'}) to UEA ${ueaAddress} before retrying.`
815
816
  : `Bridge >=${shortfallStr} to UEA ${ueaAddress} before retrying.`;
@@ -833,7 +834,7 @@ const RAW_HOOKS_MULTICHAIN = {
833
834
  [progress_hook_types_1.PROGRESS_HOOK.SEND_TX_003_05]: (quoted, threshold, gasToken, pathTag) => ({
834
835
  id: progress_hook_types_1.PROGRESS_HOOK.SEND_TX_003_05,
835
836
  title: 'Cascade SVM Native-Value Warn Threshold',
836
- message: `Buffered pool quote ${formatPC(quoted)} exceeds warn threshold ${formatPC(threshold)} ` +
837
+ message: `Buffered pool quote ${(0, formatters_1.formatPc)(quoted, 4)} exceeds warn threshold ${(0, formatters_1.formatPc)(threshold, 4)} ` +
837
838
  `for ${gasToken} (${pathTag}). Pool may be skewed or quote is unusually large. ` +
838
839
  `No action taken — pre-flight will determine if balance covers it.`,
839
840
  response: { quoted, threshold, gasToken, pathTag },
@@ -77,8 +77,18 @@ export declare class PushChain {
77
77
  /**
78
78
  * Execute one or more prepared transactions as a cascade across chains.
79
79
  * Accepts an array of PreparedUniversalTx (from prepareTransaction).
80
+ *
81
+ * @param txs - Prepared transactions to execute as a cascade.
82
+ * @param options.progressHook - Optional per-call progress callback.
83
+ * Additive with the init-time `progressHook` passed to `initialize`:
84
+ * both hooks receive every `ProgressEvent` emitted during this call
85
+ * (pre-flight, broadcast, and cascade tracking). Dedups by reference
86
+ * if it matches the init-time hook. Note: concurrent calls on the
87
+ * same instance share the hook channel during the broadcast phase.
80
88
  */
81
- executeTransactions: (txs: PreparedUniversalTx[]) => Promise<CascadedTxResponse>;
89
+ executeTransactions: (txs: PreparedUniversalTx[], options?: {
90
+ progressHook?: (event: ProgressEvent) => void;
91
+ }) => Promise<CascadedTxResponse>;
82
92
  /**
83
93
  * Tracks a transaction by hash on Push Chain
84
94
  */
@@ -84,11 +84,11 @@ class PushChain {
84
84
  prepareTransaction: (params) => {
85
85
  return orchestrator.prepareTransaction.bind(orchestrator)(params);
86
86
  },
87
- executeTransactions: (txs) => tslib_1.__awaiter(this, void 0, void 0, function* () {
87
+ executeTransactions: (txs, options) => tslib_1.__awaiter(this, void 0, void 0, function* () {
88
88
  if (this.isReadMode) {
89
89
  throw new Error('Read only mode cannot call executeTransactions function');
90
90
  }
91
- return orchestrator.createCascadedBuilder(txs).send();
91
+ return orchestrator.createCascadedBuilder(txs, options).send();
92
92
  }),
93
93
  trackTransaction: (txHash, options) => {
94
94
  return orchestrator.trackTransaction.bind(orchestrator)(txHash, options);
@@ -25,6 +25,7 @@ const enums_1 = require("../constants/enums");
25
25
  Object.defineProperty(exports, "PUSH_NETWORK", { enumerable: true, get: function () { return enums_1.PUSH_NETWORK; } });
26
26
  const child_inbounds_1 = require("./child-inbounds");
27
27
  const detector_1 = require("./detector");
28
+ const normalize_1 = require("./normalize");
28
29
  const PUSH_CHAIN_CAIPS = new Set([
29
30
  enums_1.CHAIN.PUSH_MAINNET,
30
31
  enums_1.CHAIN.PUSH_TESTNET_DONUT,
@@ -46,8 +47,10 @@ function traceUniversalTxCascade(startHash, startChain, opts) {
46
47
  const root = yield walk(startHash, startChain, 0, state, opts);
47
48
  if (!root) {
48
49
  // Should never happen for depth=0; return an empty detection node so the
49
- // caller has something to render.
50
- return {
50
+ // caller has something to render. No outbound summaries → nothing to
51
+ // normalize, but we still pass through `normalizeCascadeNodeForUser`
52
+ // for symmetry with the happy path below.
53
+ return (0, normalize_1.normalizeCascadeNodeForUser)({
51
54
  detection: {
52
55
  txHash: startHash,
53
56
  chain: startChain,
@@ -60,9 +63,16 @@ function traceUniversalTxCascade(startHash, startChain, opts) {
60
63
  },
61
64
  children: [],
62
65
  depth: 0,
63
- };
66
+ });
64
67
  }
65
- return root;
68
+ // User-facing normalization pass — recursively rewrites every
69
+ // `PushChainOutboundSummary.externalTxHash` in the tree to base58 for SVM
70
+ // destinations. Each internal `detectUniversalTx` call (in `walk`) already
71
+ // produced a normalized detection; this final pass also normalizes the
72
+ // edge-level `relation` summaries inside `expandPushOutbounds` /
73
+ // `expandExternalChildInbounds` which build summaries DIRECTLY (without
74
+ // going through `detectUniversalTx`).
75
+ return (0, normalize_1.normalizeCascadeNodeForUser)(root);
66
76
  });
67
77
  }
68
78
  // ── Core recursion ────────────────────────────────────────────────────
@@ -22,6 +22,7 @@ const tslib_1 = require("tslib");
22
22
  * `pushClient.getUniversalTxByIdV2`, no event-attribute predicate needed.
23
23
  */
24
24
  const viem_1 = require("viem");
25
+ const normalize_1 = require("./normalize");
25
26
  const STATUS_NAMES = {
26
27
  0: 'UNIVERSAL_TX_STATUS_UNSPECIFIED',
27
28
  1: 'INBOUND_SUCCESS',
@@ -75,7 +76,14 @@ function resolveChildInboundsFromDetection(pushClient, detection, diagnostics) {
75
76
  const inboundLogs = detection.detections
76
77
  .filter((d) => INBOUND_EVENT_NAMES.has(d.log.eventName))
77
78
  .map((d) => d.log);
78
- return resolveChildInboundsFromLogs(pushClient, detection.chain, detection.txHash, inboundLogs, diagnostics);
79
+ const resolutions = yield resolveChildInboundsFromLogs(pushClient, detection.chain, detection.txHash, inboundLogs, diagnostics);
80
+ // User-facing normalization pass: rewrites nested
81
+ // `outboundHashes[n].externalTxHash` to base58 for SVM destinations.
82
+ // `pcTxHashes` is Push Chain (EVM) and stays hex by chain type. The lower-
83
+ // level `resolveChildInboundsFromLogs` is left producing raw hex so
84
+ // internal callers (`findChildUtxIdFromExternalTx` via the detector path)
85
+ // that go through it directly still see the canonical form.
86
+ return resolutions.map(normalize_1.normalizeChildInboundResolutionForUser);
79
87
  });
80
88
  }
81
89
  /**
@@ -25,6 +25,7 @@ const events_1 = require("./events");
25
25
  const classify_1 = require("./classify");
26
26
  const child_inbounds_1 = require("./child-inbounds");
27
27
  const detector_svm_1 = require("./detector-svm");
28
+ const normalize_1 = require("./normalize");
28
29
  // ── Status name table (copied from scripts/poll-outbound.ts:33-44) ────
29
30
  const UNIVERSAL_TX_STATUS_NAMES = {
30
31
  0: 'UNIVERSAL_TX_STATUS_UNSPECIFIED',
@@ -51,7 +52,7 @@ function detectUniversalTx(txHash_1, chain_2) {
51
52
  throw new Error(`detectUniversalTx: unknown chain ${chain}`);
52
53
  }
53
54
  if (chainInfo.vm === enums_1.VM.SVM) {
54
- return (0, detector_svm_1.detectUniversalTxSvm)(txHash, chain, opts);
55
+ return (0, normalize_1.normalizeDetectionForUser)(yield (0, detector_svm_1.detectUniversalTxSvm)(txHash, chain, opts));
55
56
  }
56
57
  if (chainInfo.vm !== enums_1.VM.EVM) {
57
58
  throw new Error(`detectUniversalTx: unsupported vm=${chainInfo.vm} for ${chain}`);
@@ -132,7 +133,12 @@ function detectUniversalTx(txHash_1, chain_2) {
132
133
  notes.push('push cross-reference skipped (no pushClient provided)');
133
134
  }
134
135
  const detections = (0, classify_1.classifyAll)(classified.matchingLogs);
135
- return {
136
+ // User-facing normalization pass: rewrites `pushChainTx.outboundHashes[n]
137
+ // .externalTxHash` to base58 for SVM destinations. The raw `0x`-hex form
138
+ // never leaves this function — internal consumers (findChildUtxIdFromExternalTx,
139
+ // Cosmos `inbound_tx_hash=` queries) work off their own input parameters,
140
+ // not the detection result's outbound summaries.
141
+ return (0, normalize_1.normalizeDetectionForUser)({
136
142
  txHash,
137
143
  chain,
138
144
  kind: classified.kind,
@@ -142,7 +148,7 @@ function detectUniversalTx(txHash_1, chain_2) {
142
148
  detections,
143
149
  pushChainTx,
144
150
  notes,
145
- };
151
+ });
146
152
  });
147
153
  }
148
154
  // ── Helpers ───────────────────────────────────────────────────────────
@@ -2,8 +2,10 @@
2
2
  * Event ABI fragments used by the universal-tx detector.
3
3
  *
4
4
  * Sourced from push-chain-gateway-contracts and push-chain-core-contracts:
5
- * - UniversalTx, UniversalTxExecuted, RevertUniversalTx, FundsRescued
5
+ * - UniversalTx, RevertUniversalTx, FundsRescued
6
6
  * → contracts/evm-gateway/src/interfaces/IUniversalGateway.sol
7
+ * - UniversalTxExecuted
8
+ * → push-chain-core-contracts/src/Interfaces/ICEA.sol
7
9
  * - UniversalTxFinalized, UniversalTxReverted, FundsRescued (vault)
8
10
  * → contracts/evm-gateway/src/interfaces/IVault.sol
9
11
  * - UniversalTxOutbound
@@ -3,8 +3,10 @@
3
3
  * Event ABI fragments used by the universal-tx detector.
4
4
  *
5
5
  * Sourced from push-chain-gateway-contracts and push-chain-core-contracts:
6
- * - UniversalTx, UniversalTxExecuted, RevertUniversalTx, FundsRescued
6
+ * - UniversalTx, RevertUniversalTx, FundsRescued
7
7
  * → contracts/evm-gateway/src/interfaces/IUniversalGateway.sol
8
+ * - UniversalTxExecuted
9
+ * → push-chain-core-contracts/src/Interfaces/ICEA.sol
8
10
  * - UniversalTxFinalized, UniversalTxReverted, FundsRescued (vault)
9
11
  * → contracts/evm-gateway/src/interfaces/IVault.sol
10
12
  * - UniversalTxOutbound
@@ -0,0 +1,42 @@
1
+ /**
2
+ * User-facing normalization pass for detector outputs.
3
+ *
4
+ * The detector module's internal pipeline produces `PushChainOutboundSummary`
5
+ * objects with raw `0x`-hex `externalTxHash` for SVM destinations — that's
6
+ * the form Cosmos delivers and the form `findChildUtxIdFromExternalTx` /
7
+ * `inbound_tx_hash='…'` queries require. For USER-facing entry points
8
+ * (`detectUniversalTx`, `traceUniversalTxCascade`,
9
+ * `resolveChildInboundsFromDetection`), the SVM hash should be base58 so
10
+ * consumers can paste it straight into a Solana explorer or
11
+ * `connection.getTransaction(sig)`.
12
+ *
13
+ * These helpers are applied at the three public-API return points (and
14
+ * NOWHERE else) — they walk the result tree and rewrite
15
+ * `PushChainOutboundSummary.externalTxHash` for SVM destinations. The
16
+ * internal pipeline never sees the normalized form.
17
+ *
18
+ * Helpers are idempotent: a value not starting with `0x` or whose
19
+ * `destinationChain` isn't SVM passes through unchanged. New objects are
20
+ * returned only when a field actually changed; otherwise the same reference
21
+ * is reused so EVM-only graphs incur near-zero allocation.
22
+ */
23
+ import type { PushChainOutboundSummary, UniversalTxDetection } from './types';
24
+ import type { CascadeNode } from './cascade';
25
+ import type { ChildInboundResolution } from './child-inbounds';
26
+ /**
27
+ * Normalize a single outbound summary's `externalTxHash` for user display.
28
+ * Returns the same reference when no change is needed.
29
+ */
30
+ export declare function normalizeOutboundSummaryForUser(s: PushChainOutboundSummary): PushChainOutboundSummary;
31
+ /**
32
+ * Normalize the outbound summaries embedded in `detection.pushChainTx`.
33
+ * The root `detection.txHash` is left untouched — per agreed scope.
34
+ */
35
+ export declare function normalizeDetectionForUser(d: UniversalTxDetection): UniversalTxDetection;
36
+ /**
37
+ * Normalize the outbound summaries inside a `ChildInboundResolution`.
38
+ * `pcTxHashes` (Push Chain — always EVM) is left untouched.
39
+ */
40
+ export declare function normalizeChildInboundResolutionForUser(r: ChildInboundResolution): ChildInboundResolution;
41
+ /** Recursively normalize a cascade tree. */
42
+ export declare function normalizeCascadeNodeForUser(n: CascadeNode): CascadeNode;