@pushchain/core 6.0.19 → 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 (112) hide show
  1. package/CHANGELOG.md +54 -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 +4 -1
  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 -331
  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.d.ts +2 -1
  98. package/src/lib/universal/signer/signer.js +315 -299
  99. package/src/lib/universal-tx-detector/auto-detect.js +55 -60
  100. package/src/lib/universal-tx-detector/cascade.js +160 -172
  101. package/src/lib/universal-tx-detector/child-inbounds.js +67 -75
  102. package/src/lib/universal-tx-detector/classify.js +2 -3
  103. package/src/lib/universal-tx-detector/detector-svm.js +139 -147
  104. package/src/lib/universal-tx-detector/detector.js +145 -157
  105. package/src/lib/universal-tx-detector/normalize.js +7 -7
  106. package/src/lib/utils/external-tx-hash.js +1 -2
  107. package/src/lib/utils.d.ts +44 -0
  108. package/src/lib/utils.js +762 -683
  109. package/src/lib/vm-client/evm-client.js +186 -202
  110. package/src/lib/vm-client/svm-client.d.ts +1 -1
  111. package/src/lib/vm-client/svm-client.js +220 -224
  112. package/src/lib/vm-client/vm-client.types.d.ts +14 -2
@@ -6,6 +6,7 @@
6
6
  * transformToUniversalTxResponse, and the ResponseBuilderCallbacks interface.
7
7
  */
8
8
  Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.findPC20GatewayLogIndex = findPC20GatewayLogIndex;
9
10
  exports.queryUniversalTxStatusFromGatewayTx = queryUniversalTxStatusFromGatewayTx;
10
11
  exports.resolveUniversalTxFromSourceHash = resolveUniversalTxFromSourceHash;
11
12
  exports.trackTransaction = trackTransaction;
@@ -14,6 +15,8 @@ const tslib_1 = require("tslib");
14
15
  const bs58_1 = require("../../internal/bs58");
15
16
  const web3_js_1 = require("@solana/web3.js");
16
17
  const viem_1 = require("viem");
18
+ const events_1 = require("../../universal-tx-detector/events");
19
+ const export_1 = require("./pc20/export");
17
20
  const uea_evm_1 = require("../../constants/abi/uea.evm");
18
21
  const chain_1 = require("../../constants/chain");
19
22
  const enums_1 = require("../../constants/enums");
@@ -47,119 +50,166 @@ function fanOut(event, primary, secondary) {
47
50
  // ============================================================================
48
51
  // queryUniversalTxStatusFromGatewayTx
49
52
  // ============================================================================
50
- function queryUniversalTxStatusFromGatewayTx(ctx, evmClient, gatewayAddress,
51
- // DEBUG: section marker added
52
- txHash, evmGatewayMethod) {
53
- return tslib_1.__awaiter(this, void 0, void 0, function* () {
54
- var _a, _b;
53
+ /**
54
+ * Pick the gateway log whose event is the PC20 burn — the leg that carries the
55
+ * user's transfer.
56
+ *
57
+ * A PC20 return with a prepaid gas deposit makes the gateway emit TWO
58
+ * `UniversalTx` events from one user tx: the PC20 burn (payload prefixed with
59
+ * the `PC20` selector) and, second, a plain FUNDS event for the attached
60
+ * native value. Each becomes its own UTX on Push. The default "last gateway
61
+ * log" heuristic therefore selects the *fee-credit* leg, so a failure there
62
+ * (see the known fee-credit bug) is reported as the whole transfer failing —
63
+ * even though the tokens arrived via the first leg.
64
+ *
65
+ * Identification is by payload prefix rather than log order: the selector is
66
+ * what the gateway itself stamps on the PC20 leg, so it stays correct if the
67
+ * emission order ever changes. Returns null when no PC20 leg is present, and
68
+ * the caller keeps its existing behaviour.
69
+ */
70
+ function findPC20GatewayLogIndex(ctx, gatewayLogs) {
71
+ for (let i = 0; i < gatewayLogs.length; i++) {
72
+ const log = gatewayLogs[i];
55
73
  try {
56
- const chain = ctx.universalSigner.account.chain;
57
- const { vm } = chain_1.CHAIN_INFO[chain];
58
- let logIndexStr = '0';
59
- let txHashHex = txHash;
60
- if (vm === enums_1.VM.EVM) {
61
- if (!evmClient || !gatewayAddress)
62
- throw new Error('Missing EVM context');
63
- let receipt;
64
- try {
65
- receipt = yield evmClient.publicClient.getTransactionReceipt({
66
- hash: txHash,
67
- });
68
- }
69
- catch (_c) {
70
- // Receipt might not be indexed yet on this RPC; wait briefly for it
71
- receipt = yield evmClient.publicClient.waitForTransactionReceipt({
72
- hash: txHash,
73
- confirmations: 0,
74
- timeout: chain_1.CHAIN_INFO[chain].timeout,
75
- });
76
- }
77
- const gatewayLogs = (receipt.logs || []).filter((l) => (l.address || '').toLowerCase() === gatewayAddress.toLowerCase());
78
- (0, context_1.printLog)(ctx, `queryUniversalTxStatus — receipt logs count: ${(_a = receipt.logs) === null || _a === void 0 ? void 0 : _a.length}, gateway logs count: ${gatewayLogs.length}, evmGatewayMethod: ${evmGatewayMethod}`);
79
- (0, context_1.printLog)(ctx, 'queryUniversalTxStatus — gatewayLogs: ' + JSON.stringify(gatewayLogs.map((l) => { var _a; return ({ address: l.address, logIndex: l.logIndex, topics: (_a = l.topics) === null || _a === void 0 ? void 0 : _a[0] }); }), null, 2));
80
- // Use the last gateway log to derive the log index
81
- const logIndexToUse = gatewayLogs.length - 1;
82
- const firstLog = (gatewayLogs[logIndexToUse] ||
83
- (receipt.logs || []).at(-1));
84
- const logIndexVal = (_b = firstLog === null || firstLog === void 0 ? void 0 : firstLog.logIndex) !== null && _b !== void 0 ? _b : 0;
85
- (0, context_1.printLog)(ctx, `queryUniversalTxStatus — logIndexToUse: ${logIndexToUse}, firstLog.logIndex: ${firstLog === null || firstLog === void 0 ? void 0 : firstLog.logIndex}, logIndexVal: ${logIndexVal}`);
86
- logIndexStr =
87
- typeof logIndexVal === 'bigint'
88
- ? logIndexVal.toString()
89
- : String(logIndexVal);
74
+ const decoded = (0, viem_1.decodeEventLog)({
75
+ abi: [events_1.EVENT_UNIVERSAL_TX],
76
+ data: log.data,
77
+ topics: log.topics,
78
+ });
79
+ const payload = decoded.args?.payload;
80
+ if (typeof payload === 'string' &&
81
+ payload.toLowerCase().startsWith(export_1.PC20_SELECTOR.toLowerCase())) {
82
+ (0, context_1.printLog)(ctx, `queryUniversalTxStatus — PC20 leg found at gateway log ${i} ` +
83
+ `(logIndex ${String(log.logIndex)}); tracking it instead of the last log`);
84
+ return i;
90
85
  }
91
- else if (vm === enums_1.VM.SVM) {
92
- // Normalize Solana signature to 0x-hex for ID composition
93
- let txSignature = txHash;
94
- if (!txHash.startsWith('0x')) {
95
- const decoded = bs58_1.bs58.decode(txHash);
96
- txHashHex = (0, viem_1.bytesToHex)(new Uint8Array(decoded));
97
- }
98
- else {
99
- // When provided as hex, convert to base58 for RPC
100
- const hex = txHash.slice(2);
101
- const bytes = Uint8Array.from(Buffer.from(hex, 'hex'));
102
- txSignature = bs58_1.bs58.encode(Buffer.from(bytes));
103
- }
104
- // Fetch transaction by initializing a Connection and calling Solana RPC
105
- const rpcUrls = ctx.rpcUrls[chain] || chain_1.CHAIN_INFO[chain].defaultRPC;
106
- const connection = new web3_js_1.Connection(rpcUrls[0], 'confirmed');
107
- const txResp = yield connection.getTransaction(txSignature, {
108
- maxSupportedTransactionVersion: 0,
109
- commitment: 'confirmed',
86
+ }
87
+ catch {
88
+ // Not a UniversalTx event (or an ABI we do not model) — skip it.
89
+ }
90
+ }
91
+ return null;
92
+ }
93
+ async function queryUniversalTxStatusFromGatewayTx(ctx, evmClient, gatewayAddress,
94
+ // DEBUG: section marker added
95
+ txHash, evmGatewayMethod,
96
+ /**
97
+ * Set for PC20 imports (wrapper burns). Selects the PC20 burn leg rather
98
+ * than the last gateway log — see {@link findPC20GatewayLogIndex}.
99
+ */
100
+ preferPC20Log = false) {
101
+ try {
102
+ const chain = ctx.universalSigner.account.chain;
103
+ const { vm } = chain_1.CHAIN_INFO[chain];
104
+ let logIndexStr = '0';
105
+ let txHashHex = txHash;
106
+ if (vm === enums_1.VM.EVM) {
107
+ if (!evmClient || !gatewayAddress)
108
+ throw new Error('Missing EVM context');
109
+ let receipt;
110
+ try {
111
+ receipt = await evmClient.publicClient.getTransactionReceipt({
112
+ hash: txHash,
110
113
  });
111
- // Derive proper log index using discriminator matching
112
- const svmLogIndex = (0, svm_helpers_1.getSvmGatewayLogIndexFromTx)(txResp);
113
- logIndexStr = String(svmLogIndex);
114
114
  }
115
- const sourceChain = `${chain_1.VM_NAMESPACE[vm]}:${chain_1.CHAIN_INFO[chain].chainId}`;
116
- // ID = sha256("${sourceChain}:${txHash}:${logIndex}") as hex string (no 0x)
117
- const idInput = `${sourceChain}:${txHashHex}:${logIndexStr}`;
118
- const idHex = (0, viem_1.sha256)((0, viem_1.stringToBytes)(idInput)).slice(2);
119
- (0, context_1.printLog)(ctx, 'Query ID extraction: ' + JSON.stringify({
120
- sourceChain,
121
- txHashHex,
122
- logIndexStr,
123
- idInput,
124
- idHex,
125
- }, null, 2));
126
- // Fetch UniversalTx via gRPC with linear-then-exponential retry
127
- const LINEAR_ATTEMPTS = 10;
128
- const LINEAR_DELAY_MS = 3600;
129
- const EXPONENTIAL_BASE_MS = 4800;
130
- const MAX_ATTEMPTS = 15;
131
- let universalTxObj;
132
- for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
133
- (0, context_1.printLog)(ctx, `[Sync] Attempt ${attempt + 1}/${MAX_ATTEMPTS} | Query ID: ${idHex}`);
134
- try {
135
- const universalTxResp = yield ctx.pushClient.getUniversalTxById(idHex);
136
- universalTxObj = universalTxResp === null || universalTxResp === void 0 ? void 0 : universalTxResp.universalTx;
137
- if (universalTxObj) {
138
- break;
139
- }
140
- }
141
- catch (error) {
142
- // ignore and retry
143
- }
144
- // Linear delay for first N attempts, then exponential backoff
145
- let delay;
146
- if (attempt < LINEAR_ATTEMPTS) {
147
- delay = LINEAR_DELAY_MS;
148
- }
149
- else {
150
- // Exponential: 2000, 4000, 8000, 16000, ...
151
- const exponentialAttempt = attempt - LINEAR_ATTEMPTS;
152
- delay = EXPONENTIAL_BASE_MS * Math.pow(2, exponentialAttempt);
153
- }
154
- yield new Promise((r) => setTimeout(r, delay));
115
+ catch {
116
+ // Receipt might not be indexed yet on this RPC; wait briefly for it
117
+ receipt = await evmClient.publicClient.waitForTransactionReceipt({
118
+ hash: txHash,
119
+ confirmations: 0,
120
+ timeout: chain_1.CHAIN_INFO[chain].timeout,
121
+ });
155
122
  }
156
- return universalTxObj;
123
+ const gatewayLogs = (receipt.logs || []).filter((l) => (l.address || '').toLowerCase() === gatewayAddress.toLowerCase());
124
+ (0, context_1.printLog)(ctx, `queryUniversalTxStatus — receipt logs count: ${receipt.logs?.length}, gateway logs count: ${gatewayLogs.length}, evmGatewayMethod: ${evmGatewayMethod}`);
125
+ (0, context_1.printLog)(ctx, 'queryUniversalTxStatus — gatewayLogs: ' + JSON.stringify(gatewayLogs.map((l) => ({ address: l.address, logIndex: l.logIndex, topics: l.topics?.[0] })), null, 2));
126
+ // Use the last gateway log to derive the log index, except for a PC20
127
+ // import, where the last log is the fee-credit leg rather than the
128
+ // user's transfer (see findPC20GatewayLogIndex).
129
+ const pc20LogIndex = preferPC20Log
130
+ ? findPC20GatewayLogIndex(ctx, gatewayLogs)
131
+ : null;
132
+ const logIndexToUse = pc20LogIndex ?? gatewayLogs.length - 1;
133
+ const firstLog = (gatewayLogs[logIndexToUse] ||
134
+ (receipt.logs || []).at(-1));
135
+ const logIndexVal = firstLog?.logIndex ?? 0;
136
+ (0, context_1.printLog)(ctx, `queryUniversalTxStatus — logIndexToUse: ${logIndexToUse}, firstLog.logIndex: ${firstLog?.logIndex}, logIndexVal: ${logIndexVal}`);
137
+ logIndexStr =
138
+ typeof logIndexVal === 'bigint'
139
+ ? logIndexVal.toString()
140
+ : String(logIndexVal);
157
141
  }
158
- catch (err) {
159
- (0, context_1.printLog)(ctx, `queryUniversalTxStatusFromGatewayTx failed: ${err instanceof Error ? err.message : String(err)}`);
160
- return undefined;
142
+ else if (vm === enums_1.VM.SVM) {
143
+ // Normalize Solana signature to 0x-hex for ID composition
144
+ let txSignature = txHash;
145
+ if (!txHash.startsWith('0x')) {
146
+ const decoded = bs58_1.bs58.decode(txHash);
147
+ txHashHex = (0, viem_1.bytesToHex)(new Uint8Array(decoded));
148
+ }
149
+ else {
150
+ // When provided as hex, convert to base58 for RPC
151
+ const hex = txHash.slice(2);
152
+ const bytes = Uint8Array.from(Buffer.from(hex, 'hex'));
153
+ txSignature = bs58_1.bs58.encode(Buffer.from(bytes));
154
+ }
155
+ // Fetch transaction by initializing a Connection and calling Solana RPC
156
+ const rpcUrls = ctx.rpcUrls[chain] || chain_1.CHAIN_INFO[chain].defaultRPC;
157
+ const connection = new web3_js_1.Connection(rpcUrls[0], 'confirmed');
158
+ const txResp = await connection.getTransaction(txSignature, {
159
+ maxSupportedTransactionVersion: 0,
160
+ commitment: 'confirmed',
161
+ });
162
+ // Derive proper log index using discriminator matching
163
+ const svmLogIndex = (0, svm_helpers_1.getSvmGatewayLogIndexFromTx)(txResp, preferPC20Log);
164
+ logIndexStr = String(svmLogIndex);
161
165
  }
162
- });
166
+ const sourceChain = `${chain_1.VM_NAMESPACE[vm]}:${chain_1.CHAIN_INFO[chain].chainId}`;
167
+ // ID = sha256("${sourceChain}:${txHash}:${logIndex}") as hex string (no 0x)
168
+ const idInput = `${sourceChain}:${txHashHex}:${logIndexStr}`;
169
+ const idHex = (0, viem_1.sha256)((0, viem_1.stringToBytes)(idInput)).slice(2);
170
+ (0, context_1.printLog)(ctx, 'Query ID extraction: ' + JSON.stringify({
171
+ sourceChain,
172
+ txHashHex,
173
+ logIndexStr,
174
+ idInput,
175
+ idHex,
176
+ }, null, 2));
177
+ // Fetch UniversalTx via gRPC with linear-then-exponential retry
178
+ const LINEAR_ATTEMPTS = 10;
179
+ const LINEAR_DELAY_MS = 3600;
180
+ const EXPONENTIAL_BASE_MS = 4800;
181
+ const MAX_ATTEMPTS = 15;
182
+ let universalTxObj;
183
+ for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
184
+ (0, context_1.printLog)(ctx, `[Sync] Attempt ${attempt + 1}/${MAX_ATTEMPTS} | Query ID: ${idHex}`);
185
+ try {
186
+ const universalTxResp = await ctx.pushClient.getUniversalTxById(idHex);
187
+ universalTxObj = universalTxResp?.universalTx;
188
+ if (universalTxObj) {
189
+ break;
190
+ }
191
+ }
192
+ catch (error) {
193
+ // ignore and retry
194
+ }
195
+ // Linear delay for first N attempts, then exponential backoff
196
+ let delay;
197
+ if (attempt < LINEAR_ATTEMPTS) {
198
+ delay = LINEAR_DELAY_MS;
199
+ }
200
+ else {
201
+ // Exponential: 2000, 4000, 8000, 16000, ...
202
+ const exponentialAttempt = attempt - LINEAR_ATTEMPTS;
203
+ delay = EXPONENTIAL_BASE_MS * Math.pow(2, exponentialAttempt);
204
+ }
205
+ await new Promise((r) => setTimeout(r, delay));
206
+ }
207
+ return universalTxObj;
208
+ }
209
+ catch (err) {
210
+ (0, context_1.printLog)(ctx, `queryUniversalTxStatusFromGatewayTx failed: ${err instanceof Error ? err.message : String(err)}`);
211
+ return undefined;
212
+ }
163
213
  }
164
214
  // ============================================================================
165
215
  // trackTransaction
@@ -182,342 +232,320 @@ const PUSH_CHAINS = new Set([
182
232
  * the universal record and its first Push pcTx so the caller can reconstruct
183
233
  * from the Push side.
184
234
  */
185
- function resolveUniversalTxFromSourceHash(ctx, sourceHash, sourceChain, rpcUrls) {
186
- return tslib_1.__awaiter(this, void 0, void 0, function* () {
187
- var _a, _b;
188
- const detection = yield (0, detector_1.detectUniversalTx)(sourceHash, sourceChain, {
189
- pushClient: ctx.pushClient,
190
- rpcUrls: Object.assign(Object.assign({}, ctx.rpcUrls), rpcUrls),
191
- // We do our own getUniversalTxByIdV2 below; the detector's cosmos
192
- // cross-ref can come back empty for SVM even when the record exists.
193
- skipPushChainLookup: true,
194
- });
195
- const utxId = detection.decoded.universalTxId;
196
- if (!utxId) {
197
- throw new Error(`trackTransaction: could not resolve a universal transaction from hash ` +
198
- `"${sourceHash}" on ${sourceChain} (detected kind: ${detection.kind}). ` +
199
- `Pass the Push Chain root hash, or a source hash that emitted a ` +
200
- `UniversalTx event.`);
201
- }
202
- const utxResponse = yield ctx.pushClient.getUniversalTxByIdV2(utxId);
203
- const universalTxData = utxResponse === null || utxResponse === void 0 ? void 0 : utxResponse.universalTx;
204
- if (!universalTxData) {
205
- throw new Error(`trackTransaction: resolved universalTxId ${utxId} from ${sourceChain} ` +
206
- `hash "${sourceHash}", but no universal transaction record exists on ` +
207
- `Push Chain yet (it may still be relaying).`);
208
- }
209
- const pushTxHash = (_b = (_a = universalTxData.pcTx) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.txHash;
210
- if (!pushTxHash) {
211
- throw new Error(`trackTransaction: universal tx ${utxId} (from ${sourceChain} hash ` +
212
- `"${sourceHash}") has no Push Chain tx recorded yet.`);
213
- }
214
- return { universalTxData, pushTxHash, sourceSender: detection.decoded.sender };
235
+ async function resolveUniversalTxFromSourceHash(ctx, sourceHash, sourceChain, rpcUrls) {
236
+ const detection = await (0, detector_1.detectUniversalTx)(sourceHash, sourceChain, {
237
+ pushClient: ctx.pushClient,
238
+ rpcUrls: { ...ctx.rpcUrls, ...rpcUrls },
239
+ // We do our own getUniversalTxByIdV2 below; the detector's cosmos
240
+ // cross-ref can come back empty for SVM even when the record exists.
241
+ skipPushChainLookup: true,
215
242
  });
243
+ const utxId = detection.decoded.universalTxId;
244
+ if (!utxId) {
245
+ throw new Error(`trackTransaction: could not resolve a universal transaction from hash ` +
246
+ `"${sourceHash}" on ${sourceChain} (detected kind: ${detection.kind}). ` +
247
+ `Pass the Push Chain root hash, or a source hash that emitted a ` +
248
+ `UniversalTx event.`);
249
+ }
250
+ const utxResponse = await ctx.pushClient.getUniversalTxByIdV2(utxId);
251
+ const universalTxData = utxResponse?.universalTx;
252
+ if (!universalTxData) {
253
+ throw new Error(`trackTransaction: resolved universalTxId ${utxId} from ${sourceChain} ` +
254
+ `hash "${sourceHash}", but no universal transaction record exists on ` +
255
+ `Push Chain yet (it may still be relaying).`);
256
+ }
257
+ const pushTxHash = universalTxData.pcTx?.[0]?.txHash;
258
+ if (!pushTxHash) {
259
+ throw new Error(`trackTransaction: universal tx ${utxId} (from ${sourceChain} hash ` +
260
+ `"${sourceHash}") has no Push Chain tx recorded yet.`);
261
+ }
262
+ return { universalTxData, pushTxHash, sourceSender: detection.decoded.sender };
216
263
  }
217
- function trackTransaction(ctx, txHash, options, callbacks) {
218
- return tslib_1.__awaiter(this, void 0, void 0, function* () {
219
- var _a, _b;
220
- if (!callbacks) {
221
- throw new Error('trackTransaction requires ResponseBuilderCallbacks');
222
- }
223
- const { chain = (0, helpers_1.getPushChainForNetwork)(ctx.pushNetwork), progressHook, waitForCompletion = true, advanced = {}, } = options !== null && options !== void 0 ? options : {};
224
- // Fail fast on an unsupported `chain`. It defaults to Push Chain when the
225
- // caller omits it; an explicitly-passed value that isn't a known CHAIN_INFO
226
- // entry (a raw numeric chainId, a typo, or a chain from a plain-JS caller
227
- // that skips the CHAIN type) would otherwise throw an opaque "Cannot read
228
- // properties of undefined (reading 'defaultRPC')" at the RPC lookup below.
229
- if (!chain_1.CHAIN_INFO[chain]) {
230
- throw new Error(`trackTransaction: unsupported chain "${String(chain)}". Pass a ` +
231
- `PushChain.CONSTANTS.CHAIN value. Supported: ${Object.keys(chain_1.CHAIN_INFO).join(', ')}`);
232
- }
233
- const { timeout = 300000, pollingIntervalMs = 1000, rpcUrls = {} } = advanced;
234
- // Source-hash mode: a non-Push `chain` means `txHash` is an origin/source-leg
235
- // hash (a Sepolia tx hash, a Solana signature, …), not a Push Chain hash.
236
- // Resolve it to the universal record + its Push pcTx via the detector, then
237
- // reconstruct from the Push side. Push-native tracking (below) is unchanged.
238
- let preResolvedUtxData;
239
- let sourceOriginChain;
240
- let sourceSender;
241
- let trackChain = chain;
242
- let trackHash = txHash;
243
- if (!PUSH_CHAINS.has(chain)) {
244
- const resolved = yield resolveUniversalTxFromSourceHash(ctx, txHash, chain, rpcUrls);
245
- preResolvedUtxData = resolved.universalTxData;
246
- sourceOriginChain = chain;
247
- sourceSender = resolved.sourceSender;
248
- trackHash = resolved.pushTxHash;
249
- trackChain = (0, helpers_1.getPushChainForNetwork)(ctx.pushNetwork);
264
+ async function trackTransaction(ctx, txHash, options, callbacks) {
265
+ if (!callbacks) {
266
+ throw new Error('trackTransaction requires ResponseBuilderCallbacks');
267
+ }
268
+ const { chain = (0, helpers_1.getPushChainForNetwork)(ctx.pushNetwork), progressHook, waitForCompletion = true, advanced = {}, } = options ?? {};
269
+ // Fail fast on an unsupported `chain`. It defaults to Push Chain when the
270
+ // caller omits it; an explicitly-passed value that isn't a known CHAIN_INFO
271
+ // entry (a raw numeric chainId, a typo, or a chain from a plain-JS caller
272
+ // that skips the CHAIN type) would otherwise throw an opaque "Cannot read
273
+ // properties of undefined (reading 'defaultRPC')" at the RPC lookup below.
274
+ if (!chain_1.CHAIN_INFO[chain]) {
275
+ throw new Error(`trackTransaction: unsupported chain "${String(chain)}". Pass a ` +
276
+ `PushChain.CONSTANTS.CHAIN value. Supported: ${Object.keys(chain_1.CHAIN_INFO).join(', ')}`);
277
+ }
278
+ const { timeout = 300000, pollingIntervalMs = 1000, rpcUrls = {} } = advanced;
279
+ // Source-hash mode: a non-Push `chain` means `txHash` is an origin/source-leg
280
+ // hash (a Sepolia tx hash, a Solana signature, …), not a Push Chain hash.
281
+ // Resolve it to the universal record + its Push pcTx via the detector, then
282
+ // reconstruct from the Push side. Push-native tracking (below) is unchanged.
283
+ let preResolvedUtxData;
284
+ let sourceOriginChain;
285
+ let sourceSender;
286
+ let trackChain = chain;
287
+ let trackHash = txHash;
288
+ if (!PUSH_CHAINS.has(chain)) {
289
+ const resolved = await resolveUniversalTxFromSourceHash(ctx, txHash, chain, rpcUrls);
290
+ preResolvedUtxData = resolved.universalTxData;
291
+ sourceOriginChain = chain;
292
+ sourceSender = resolved.sourceSender;
293
+ trackHash = resolved.pushTxHash;
294
+ trackChain = (0, helpers_1.getPushChainForNetwork)(ctx.pushNetwork);
295
+ }
296
+ // Event buffer for replay via response.progressHook()
297
+ const eventBuffer = [];
298
+ // Helper to emit progress events. Per-tx hook (passed to trackTransaction)
299
+ // fires first, then the orchestrator-level hook — deduped via `fanOut` so
300
+ // a caller that wires the same callback at both levels isn't double-fired.
301
+ const emitProgress = (event) => {
302
+ eventBuffer.push(event);
303
+ (0, context_1.printLog)(ctx, event.message);
304
+ fanOut(event, progressHook, ctx.progressHook);
305
+ };
306
+ // Create client for target chain with optional RPC override. In source-hash
307
+ // mode `trackChain` is Push (the resolved pcTx lives on Push Chain).
308
+ const chainRPCs = rpcUrls[trackChain] ||
309
+ ctx.rpcUrls[trackChain] ||
310
+ chain_1.CHAIN_INFO[trackChain].defaultRPC;
311
+ const client = new push_client_1.PushClient({
312
+ rpcUrls: chainRPCs,
313
+ network: ctx.pushNetwork,
314
+ });
315
+ // Poll for transaction
316
+ const start = Date.now();
317
+ let tx;
318
+ // eslint-disable-next-line no-constant-condition
319
+ while (true) {
320
+ try {
321
+ tx = await client.getTransaction(trackHash);
322
+ break; // Found transaction
250
323
  }
251
- // Event buffer for replay via response.progressHook()
252
- const eventBuffer = [];
253
- // Helper to emit progress events. Per-tx hook (passed to trackTransaction)
254
- // fires first, then the orchestrator-level hook — deduped via `fanOut` so
255
- // a caller that wires the same callback at both levels isn't double-fired.
256
- const emitProgress = (event) => {
257
- eventBuffer.push(event);
258
- (0, context_1.printLog)(ctx, event.message);
259
- fanOut(event, progressHook, ctx.progressHook);
260
- };
261
- // Create client for target chain with optional RPC override. In source-hash
262
- // mode `trackChain` is Push (the resolved pcTx lives on Push Chain).
263
- const chainRPCs = rpcUrls[trackChain] ||
264
- ctx.rpcUrls[trackChain] ||
265
- chain_1.CHAIN_INFO[trackChain].defaultRPC;
266
- const client = new push_client_1.PushClient({
267
- rpcUrls: chainRPCs,
268
- network: ctx.pushNetwork,
269
- });
270
- // Poll for transaction
271
- const start = Date.now();
272
- let tx;
273
- // eslint-disable-next-line no-constant-condition
274
- while (true) {
275
- try {
276
- tx = yield client.getTransaction(trackHash);
277
- break; // Found transaction
324
+ catch (err) {
325
+ if (!waitForCompletion) {
326
+ throw new Error(`Transaction ${trackHash} not found`);
278
327
  }
279
- catch (err) {
280
- if (!waitForCompletion) {
281
- throw new Error(`Transaction ${trackHash} not found`);
282
- }
283
- // Check timeout
284
- if (Date.now() - start > timeout) {
285
- throw new Error(`Timeout: transaction ${trackHash} not confirmed within ${timeout}ms`);
286
- }
287
- // Brief delay before retry
288
- yield new Promise((r) => setTimeout(r, pollingIntervalMs));
328
+ // Check timeout
329
+ if (Date.now() - start > timeout) {
330
+ throw new Error(`Timeout: transaction ${trackHash} not confirmed within ${timeout}ms`);
289
331
  }
332
+ // Brief delay before retry
333
+ await new Promise((r) => setTimeout(r, pollingIntervalMs));
290
334
  }
291
- // Try to get UniversalTx data for richer progress reconstruction.
292
- // In source-hash mode we already resolved the record above; otherwise run the
293
- // three-tier lookup: direct hash → computed keccak ID → Cosmos extraction.
294
- let universalTxData = preResolvedUtxData;
295
- if (!universalTxData) {
296
- try {
297
- const utxResponse = yield ctx.pushClient.getUniversalTxByIdV2(trackHash);
298
- if (utxResponse === null || utxResponse === void 0 ? void 0 : utxResponse.universalTx) {
299
- universalTxData = utxResponse.universalTx;
300
- }
301
- }
302
- catch (_c) {
303
- // Ignore - try computed ID next
335
+ }
336
+ // Try to get UniversalTx data for richer progress reconstruction.
337
+ // In source-hash mode we already resolved the record above; otherwise run the
338
+ // three-tier lookup: direct hash → computed keccak ID → Cosmos extraction.
339
+ let universalTxData = preResolvedUtxData;
340
+ if (!universalTxData) {
341
+ try {
342
+ const utxResponse = await ctx.pushClient.getUniversalTxByIdV2(trackHash);
343
+ if (utxResponse?.universalTx) {
344
+ universalTxData = utxResponse.universalTx;
304
345
  }
305
346
  }
306
- if (!universalTxData) {
307
- try {
308
- const computedId = (0, outbound_tracker_1.computeUniversalTxId)(ctx.pushNetwork, trackHash);
309
- const queryId = computedId.startsWith('0x') ? computedId.slice(2) : computedId;
310
- const utxResponse = yield ctx.pushClient.getUniversalTxByIdV2(queryId);
311
- if (utxResponse === null || utxResponse === void 0 ? void 0 : utxResponse.universalTx) {
312
- universalTxData = utxResponse.universalTx;
313
- }
314
- }
315
- catch (_d) {
316
- // Ignore - try Cosmos event extraction next
347
+ catch {
348
+ // Ignore - try computed ID next
349
+ }
350
+ }
351
+ if (!universalTxData) {
352
+ try {
353
+ const computedId = (0, outbound_tracker_1.computeUniversalTxId)(ctx.pushNetwork, trackHash);
354
+ const queryId = computedId.startsWith('0x') ? computedId.slice(2) : computedId;
355
+ const utxResponse = await ctx.pushClient.getUniversalTxByIdV2(queryId);
356
+ if (utxResponse?.universalTx) {
357
+ universalTxData = utxResponse.universalTx;
317
358
  }
318
359
  }
319
- if (!universalTxData) {
320
- try {
321
- const extractedId = yield (0, outbound_tracker_1.extractUniversalSubTxIdFromTx)(ctx, trackHash);
322
- if (extractedId) {
323
- const queryId = extractedId.startsWith('0x') ? extractedId.slice(2) : extractedId;
324
- const utxResponse = yield ctx.pushClient.getUniversalTxByIdV2(queryId);
325
- if (utxResponse === null || utxResponse === void 0 ? void 0 : utxResponse.universalTx) {
326
- universalTxData = utxResponse.universalTx;
327
- }
360
+ catch {
361
+ // Ignore - try Cosmos event extraction next
362
+ }
363
+ }
364
+ if (!universalTxData) {
365
+ try {
366
+ const extractedId = await (0, outbound_tracker_1.extractUniversalSubTxIdFromTx)(ctx, trackHash);
367
+ if (extractedId) {
368
+ const queryId = extractedId.startsWith('0x') ? extractedId.slice(2) : extractedId;
369
+ const utxResponse = await ctx.pushClient.getUniversalTxByIdV2(queryId);
370
+ if (utxResponse?.universalTx) {
371
+ universalTxData = utxResponse.universalTx;
328
372
  }
329
373
  }
330
- catch (_e) {
331
- // Ignore - no universal tx data available
332
- }
333
374
  }
334
- // Transform to UniversalTxResponse
335
- const universalTxResponse = yield transformToUniversalTxResponse(ctx, tx, eventBuffer, callbacks);
336
- // Detect route from UniversalTxV2 data and set on response
337
- // This enables wait() to trigger outbound polling via waitForOutboundTx()
338
- const detectedRoute = (0, tx_transformer_1.detectRouteFromUniversalTxData)(universalTxData);
339
- if (detectedRoute) {
340
- universalTxResponse.route = detectedRoute;
341
- // Mirror the route on the context so emission code in wait()/cascade
342
- // picks the correct ID range — keeps trackTransaction in lockstep with
343
- // sendUniversalTransaction, which sets currentRoute in execute().
344
- ctx.currentRoute = detectedRoute;
345
- // Populate the destination/source chain on the response so
346
- // reconstructR2/R3 (and any downstream emission that reads
347
- // universalTxResponse.chain) emit the proper friendly chain name
348
- // instead of the "external" fallback. The first outbound's destination
349
- // is the target chain for R2 and the source CEA chain for R3.
350
- const firstOutboundChain = (_b = (_a = universalTxData === null || universalTxData === void 0 ? void 0 : universalTxData.outboundTx) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.destinationChain;
351
- if (firstOutboundChain && !universalTxResponse.chain) {
352
- universalTxResponse.chain = firstOutboundChain;
353
- universalTxResponse.chainNamespace = firstOutboundChain;
354
- }
355
- // For R3 replays, enable inbound round-trip tracking so `.wait()` runs
356
- // the 310-01 → 399-01 sequence. Live execute sets this in
357
- // route-handlers.ts via `amount > 0n`; trackTransaction has no
358
- // execute-phase args, so we enable it unconditionally for CEA_TO_PUSH.
359
- // The inbound block tolerates no-inbound (payload-only R3) by timing
360
- // out — same as live behaviour when the source tx has no round-trip.
361
- if (detectedRoute === route_detector_1.TransactionRoute.CEA_TO_PUSH) {
362
- universalTxResponse._expectsInboundRoundTrip = true;
363
- }
375
+ catch {
376
+ // Ignore - no universal tx data available
364
377
  }
365
- // Source-hash mode: the pcTx we fetched lives on Push Chain, so the
366
- // transform resolves chain to the Push side. Surface the true source chain
367
- // the caller tracked so `chain`/`chainNamespace` reflect where the universal
368
- // tx actually originated. (An outbound-derived chain above, if any, wins.)
369
- if (sourceOriginChain && !universalTxResponse.chain) {
370
- universalTxResponse.chain = sourceOriginChain;
371
- universalTxResponse.chainNamespace = sourceOriginChain;
378
+ }
379
+ // Transform to UniversalTxResponse
380
+ const universalTxResponse = await transformToUniversalTxResponse(ctx, tx, eventBuffer, callbacks);
381
+ // Detect route from UniversalTxV2 data and set on response
382
+ // This enables wait() to trigger outbound polling via waitForOutboundTx()
383
+ const detectedRoute = (0, tx_transformer_1.detectRouteFromUniversalTxData)(universalTxData);
384
+ if (detectedRoute) {
385
+ universalTxResponse.route = detectedRoute;
386
+ // Mirror the route on the context so emission code in wait()/cascade
387
+ // picks the correct ID range — keeps trackTransaction in lockstep with
388
+ // sendUniversalTransaction, which sets currentRoute in execute().
389
+ ctx.currentRoute = detectedRoute;
390
+ // Populate the destination/source chain on the response so
391
+ // reconstructR2/R3 (and any downstream emission that reads
392
+ // universalTxResponse.chain) emit the proper friendly chain name
393
+ // instead of the "external" fallback. The first outbound's destination
394
+ // is the target chain for R2 and the source CEA chain for R3.
395
+ const firstOutboundChain = universalTxData?.outboundTx?.[0]?.destinationChain;
396
+ if (firstOutboundChain && !universalTxResponse.chain) {
397
+ universalTxResponse.chain = firstOutboundChain;
398
+ universalTxResponse.chainNamespace = firstOutboundChain;
372
399
  }
373
- // Override `origin` with the true source account. The transform derives
374
- // origin from the Push pcTx (whose `from` is the keeper that submitted the
375
- // inbound), so in source-hash mode it would otherwise read as a Push
376
- // address. Rebuild it as `<sourceChainCaip>:<sourceSender>` using the
377
- // chain's native address encoding (0x for EVM, base58 for SVM).
378
- if (sourceOriginChain && sourceSender) {
379
- let originAddr = sourceSender;
380
- try {
381
- originAddr =
382
- chain_1.CHAIN_INFO[sourceOriginChain].vm === enums_1.VM.SVM
383
- ? bs58_1.bs58.encode((0, viem_1.toBytes)(sourceSender))
384
- : (0, viem_1.getAddress)(sourceSender);
385
- }
386
- catch (_f) {
387
- // Keep the raw sender if re-encoding fails.
388
- }
389
- universalTxResponse.origin = `${sourceOriginChain}:${originAddr}`;
390
- universalTxResponse.chainId = chain_1.CHAIN_INFO[sourceOriginChain].chainId;
400
+ // For R3 replays, enable inbound round-trip tracking so `.wait()` runs
401
+ // the 310-01 → 399-01 sequence. Live execute sets this in
402
+ // route-handlers.ts via `amount > 0n`; trackTransaction has no
403
+ // execute-phase args, so we enable it unconditionally for CEA_TO_PUSH.
404
+ // The inbound block tolerates no-inbound (payload-only R3) by timing
405
+ // out — same as live behaviour when the source tx has no round-trip.
406
+ if (detectedRoute === route_detector_1.TransactionRoute.CEA_TO_PUSH) {
407
+ universalTxResponse._expectsInboundRoundTrip = true;
391
408
  }
392
- // Reconstruct and emit SEND-TX-* progress events
393
- const reconstructedEvents = (0, tx_transformer_1.reconstructProgressEvents)(universalTxResponse, universalTxData);
394
- for (const event of reconstructedEvents) {
395
- emitProgress(event);
409
+ }
410
+ // Source-hash mode: the pcTx we fetched lives on Push Chain, so the
411
+ // transform resolves chain to the Push side. Surface the true source chain
412
+ // the caller tracked so `chain`/`chainNamespace` reflect where the universal
413
+ // tx actually originated. (An outbound-derived chain above, if any, wins.)
414
+ if (sourceOriginChain && !universalTxResponse.chain) {
415
+ universalTxResponse.chain = sourceOriginChain;
416
+ universalTxResponse.chainNamespace = sourceOriginChain;
417
+ }
418
+ // Override `origin` with the true source account. The transform derives
419
+ // origin from the Push pcTx (whose `from` is the keeper that submitted the
420
+ // inbound), so in source-hash mode it would otherwise read as a Push
421
+ // address. Rebuild it as `<sourceChainCaip>:<sourceSender>` using the
422
+ // chain's native address encoding (0x for EVM, base58 for SVM).
423
+ if (sourceOriginChain && sourceSender) {
424
+ let originAddr = sourceSender;
425
+ try {
426
+ originAddr =
427
+ chain_1.CHAIN_INFO[sourceOriginChain].vm === enums_1.VM.SVM
428
+ ? bs58_1.bs58.encode((0, viem_1.toBytes)(sourceSender))
429
+ : (0, viem_1.getAddress)(sourceSender);
396
430
  }
397
- // Mark the response so the wait() closure (when later invoked) doesn't
398
- // call trackTransaction a second time and re-emit the same reconstructed
399
- // events to the user's progressHook.
400
- universalTxResponse._eventsReconstructed = true;
401
- // Auto-register the caller's per-call progressHook as the response's
402
- // registeredProgressHook so that tracked.wait() fires wait-phase events
403
- // (209-xx / 299-xx / 399-xx) to the same callback without requiring
404
- // tracked.progressHook(cb) to be called again. Skip replay of buffered
405
- // events because we just fired them above via emitProgress — calling
406
- // response.progressHook(cb) here would re-emit the reconstructed stream.
407
- if (progressHook && universalTxResponse._setProgressHookNoReplay) {
408
- universalTxResponse._setProgressHookNoReplay(progressHook);
431
+ catch {
432
+ // Keep the raw sender if re-encoding fails.
409
433
  }
410
- return universalTxResponse;
411
- });
434
+ universalTxResponse.origin = `${sourceOriginChain}:${originAddr}`;
435
+ universalTxResponse.chainId = chain_1.CHAIN_INFO[sourceOriginChain].chainId;
436
+ }
437
+ // Reconstruct and emit SEND-TX-* progress events
438
+ const reconstructedEvents = (0, tx_transformer_1.reconstructProgressEvents)(universalTxResponse, universalTxData);
439
+ for (const event of reconstructedEvents) {
440
+ emitProgress(event);
441
+ }
442
+ // Mark the response so the wait() closure (when later invoked) doesn't
443
+ // call trackTransaction a second time and re-emit the same reconstructed
444
+ // events to the user's progressHook.
445
+ universalTxResponse._eventsReconstructed = true;
446
+ // Auto-register the caller's per-call progressHook as the response's
447
+ // registeredProgressHook so that tracked.wait() fires wait-phase events
448
+ // (209-xx / 299-xx / 399-xx) to the same callback without requiring
449
+ // tracked.progressHook(cb) to be called again. Skip replay of buffered
450
+ // events because we just fired them above via emitProgress — calling
451
+ // response.progressHook(cb) here would re-emit the reconstructed stream.
452
+ if (progressHook && universalTxResponse._setProgressHookNoReplay) {
453
+ universalTxResponse._setProgressHookNoReplay(progressHook);
454
+ }
455
+ return universalTxResponse;
412
456
  }
413
457
  // ============================================================================
414
458
  // transformToUniversalTxResponse
415
459
  // ============================================================================
416
- function transformToUniversalTxResponse(ctx_1, tx_1) {
417
- return tslib_1.__awaiter(this, arguments, void 0, function* (ctx, tx, eventBuffer = [], callbacks) {
418
- const chain = ctx.universalSigner.account.chain;
419
- let { vm, chainId } = chain_1.CHAIN_INFO[chain];
420
- let from;
421
- let to;
422
- let value;
423
- let data;
424
- let rawTransactionData;
425
- const ueaOrigin = yield (0, account_1.convertExecutorToOrigin)(tx.to, { _internal: true });
426
- let originAddress;
427
- if (ueaOrigin.exists) {
428
- if (!ueaOrigin.account) {
429
- throw new Error('UEA origin account is null');
460
+ async function transformToUniversalTxResponse(ctx, tx, eventBuffer = [], callbacks) {
461
+ const chain = ctx.universalSigner.account.chain;
462
+ let { vm, chainId } = chain_1.CHAIN_INFO[chain];
463
+ let from;
464
+ let to;
465
+ let value;
466
+ let data;
467
+ let rawTransactionData;
468
+ const ueaOrigin = await (0, account_1.convertExecutorToOrigin)(tx.to, { _internal: true });
469
+ let originAddress;
470
+ if (ueaOrigin.exists) {
471
+ if (!ueaOrigin.account) {
472
+ throw new Error('UEA origin account is null');
473
+ }
474
+ originAddress = ueaOrigin.account.address;
475
+ // Use the resolved origin chain for vm/chainId so that the `origin`
476
+ // field reflects the transaction's actual origin, not the tracker's.
477
+ const resolvedChainInfo = chain_1.CHAIN_INFO[ueaOrigin.account.chain];
478
+ if (resolvedChainInfo) {
479
+ vm = resolvedChainInfo.vm;
480
+ chainId = resolvedChainInfo.chainId;
481
+ }
482
+ from = (0, viem_1.getAddress)(tx.to);
483
+ let decoded;
484
+ let decodedPayload = null;
485
+ if (tx.input !== '0x') {
486
+ // Try to decode the input as a known UEA_EVM call. Older txs — or
487
+ // txs sent to a UEA that was upgraded to expose a function not yet
488
+ // reflected in the bundled `uea.evm.ts` ABI — carry a selector that
489
+ // viem can't match. In that case the reconstructor should still
490
+ // produce a usable response (and emit progress hooks) using the raw
491
+ // tx fields, rather than throwing before any hook fires.
492
+ try {
493
+ decoded = (0, viem_1.decodeFunctionData)({ abi: uea_evm_1.UEA_EVM, data: tx.input });
494
+ if (decoded?.args) {
495
+ decodedPayload = decoded.args[0];
496
+ }
430
497
  }
431
- originAddress = ueaOrigin.account.address;
432
- // Use the resolved origin chain for vm/chainId so that the `origin`
433
- // field reflects the transaction's actual origin, not the tracker's.
434
- const resolvedChainInfo = chain_1.CHAIN_INFO[ueaOrigin.account.chain];
435
- if (resolvedChainInfo) {
436
- vm = resolvedChainInfo.vm;
437
- chainId = resolvedChainInfo.chainId;
498
+ catch {
499
+ decodedPayload = null;
438
500
  }
439
- from = (0, viem_1.getAddress)(tx.to);
440
- let decoded;
441
- let decodedPayload = null;
442
- if (tx.input !== '0x') {
443
- // Try to decode the input as a known UEA_EVM call. Older txs — or
444
- // txs sent to a UEA that was upgraded to expose a function not yet
445
- // reflected in the bundled `uea.evm.ts` ABI — carry a selector that
446
- // viem can't match. In that case the reconstructor should still
447
- // produce a usable response (and emit progress hooks) using the raw
448
- // tx fields, rather than throwing before any hook fires.
449
- try {
450
- decoded = (0, viem_1.decodeFunctionData)({ abi: uea_evm_1.UEA_EVM, data: tx.input });
451
- if (decoded === null || decoded === void 0 ? void 0 : decoded.args) {
452
- decodedPayload = decoded.args[0];
453
- }
454
- }
455
- catch (_a) {
456
- decodedPayload = null;
457
- }
458
- if (decodedPayload) {
459
- to = decodedPayload.to;
460
- value = BigInt(decodedPayload.value);
461
- data = decodedPayload.data;
462
- }
463
- else {
464
- // Unknown / unsupported UEA function — fall back to raw fields so
465
- // transformToUniversalTxResponse can still return a valid response
466
- // and downstream progress-hook reconstruction can proceed.
467
- to = (0, viem_1.getAddress)(tx.to);
468
- value = tx.value;
469
- data = tx.input;
470
- }
471
- // Extract 'to' from single-element multicall
472
- if (data && data.length >= 10) {
473
- const multicallSelector = (0, viem_1.keccak256)((0, viem_1.toBytes)('UEA_MULTICALL')).slice(0, 10);
474
- if (data.slice(0, 10) === multicallSelector) {
475
- try {
476
- const innerData = ('0x' + data.slice(10));
477
- const [decodedCalls] = (0, viem_1.decodeAbiParameters)([
478
- {
479
- type: 'tuple[]',
480
- components: [
481
- { name: 'to', type: 'address' },
482
- { name: 'value', type: 'uint256' },
483
- { name: 'data', type: 'bytes' },
484
- ],
485
- },
486
- ], innerData);
487
- // If single call, use its 'to' address
488
- if (decodedCalls.length === 1) {
489
- to = (0, viem_1.getAddress)(decodedCalls[0].to);
490
- }
491
- }
492
- catch (_b) {
493
- // Keep original 'to' if decoding fails
494
- }
495
- }
496
- }
497
- rawTransactionData = {
498
- from: (0, viem_1.getAddress)(tx.from),
499
- to: (0, viem_1.getAddress)(tx.to),
500
- nonce: tx.nonce,
501
- data: tx.input,
502
- value: tx.value,
503
- };
501
+ if (decodedPayload) {
502
+ to = decodedPayload.to;
503
+ value = BigInt(decodedPayload.value);
504
+ data = decodedPayload.data;
504
505
  }
505
506
  else {
507
+ // Unknown / unsupported UEA function — fall back to raw fields so
508
+ // transformToUniversalTxResponse can still return a valid response
509
+ // and downstream progress-hook reconstruction can proceed.
506
510
  to = (0, viem_1.getAddress)(tx.to);
507
511
  value = tx.value;
508
512
  data = tx.input;
509
- rawTransactionData = {
510
- from: (0, viem_1.getAddress)(tx.from),
511
- to: (0, viem_1.getAddress)(tx.to),
512
- nonce: tx.nonce,
513
- data: tx.input,
514
- value: tx.value,
515
- };
516
513
  }
514
+ // Extract 'to' from single-element multicall
515
+ if (data && data.length >= 10) {
516
+ const multicallSelector = (0, viem_1.keccak256)((0, viem_1.toBytes)('UEA_MULTICALL')).slice(0, 10);
517
+ if (data.slice(0, 10) === multicallSelector) {
518
+ try {
519
+ const innerData = ('0x' + data.slice(10));
520
+ const [decodedCalls] = (0, viem_1.decodeAbiParameters)([
521
+ {
522
+ type: 'tuple[]',
523
+ components: [
524
+ { name: 'to', type: 'address' },
525
+ { name: 'value', type: 'uint256' },
526
+ { name: 'data', type: 'bytes' },
527
+ ],
528
+ },
529
+ ], innerData);
530
+ // If single call, use its 'to' address
531
+ if (decodedCalls.length === 1) {
532
+ to = (0, viem_1.getAddress)(decodedCalls[0].to);
533
+ }
534
+ }
535
+ catch {
536
+ // Keep original 'to' if decoding fails
537
+ }
538
+ }
539
+ }
540
+ rawTransactionData = {
541
+ from: (0, viem_1.getAddress)(tx.from),
542
+ to: (0, viem_1.getAddress)(tx.to),
543
+ nonce: tx.nonce,
544
+ data: tx.input,
545
+ value: tx.value,
546
+ };
517
547
  }
518
548
  else {
519
- originAddress = (0, viem_1.getAddress)(tx.from);
520
- from = (0, viem_1.getAddress)(tx.from);
521
549
  to = (0, viem_1.getAddress)(tx.to);
522
550
  value = tx.value;
523
551
  data = tx.input;
@@ -529,316 +557,404 @@ function transformToUniversalTxResponse(ctx_1, tx_1) {
529
557
  value: tx.value,
530
558
  };
531
559
  }
532
- // Extract 'to' and 'from' from depositPRC20WithAutoSwap (precompile call)
533
- if (data && data.length >= 10) {
534
- const depositPRC20Selector = '0x780ad827';
535
- if (data.slice(0, 10) === depositPRC20Selector) {
536
- try {
537
- const decoded = (0, viem_1.decodeFunctionData)({
538
- abi: [
539
- {
540
- name: 'depositPRC20WithAutoSwap',
541
- type: 'function',
542
- inputs: [
543
- { name: 'prc20', type: 'address' },
544
- { name: 'amount', type: 'uint256' },
545
- { name: 'target', type: 'address' },
546
- { name: 'fee', type: 'uint24' },
547
- { name: 'minPCOut', type: 'uint256' },
548
- { name: 'deadline', type: 'uint256' },
549
- ],
550
- },
551
- ],
552
- data: data,
553
- });
554
- if (decoded.args) {
555
- const target = decoded.args[2];
556
- to = (0, viem_1.getAddress)(target);
557
- from = '0x0000000000000000000000000000000000000000';
558
- }
559
- }
560
- catch (_c) {
561
- // Keep original values if decoding fails
560
+ }
561
+ else {
562
+ originAddress = (0, viem_1.getAddress)(tx.from);
563
+ from = (0, viem_1.getAddress)(tx.from);
564
+ to = (0, viem_1.getAddress)(tx.to);
565
+ value = tx.value;
566
+ data = tx.input;
567
+ rawTransactionData = {
568
+ from: (0, viem_1.getAddress)(tx.from),
569
+ to: (0, viem_1.getAddress)(tx.to),
570
+ nonce: tx.nonce,
571
+ data: tx.input,
572
+ value: tx.value,
573
+ };
574
+ }
575
+ // Extract 'to' and 'from' from depositPRC20WithAutoSwap (precompile call)
576
+ if (data && data.length >= 10) {
577
+ const depositPRC20Selector = '0x780ad827';
578
+ if (data.slice(0, 10) === depositPRC20Selector) {
579
+ try {
580
+ const decoded = (0, viem_1.decodeFunctionData)({
581
+ abi: [
582
+ {
583
+ name: 'depositPRC20WithAutoSwap',
584
+ type: 'function',
585
+ inputs: [
586
+ { name: 'prc20', type: 'address' },
587
+ { name: 'amount', type: 'uint256' },
588
+ { name: 'target', type: 'address' },
589
+ { name: 'fee', type: 'uint24' },
590
+ { name: 'minPCOut', type: 'uint256' },
591
+ { name: 'deadline', type: 'uint256' },
592
+ ],
593
+ },
594
+ ],
595
+ data: data,
596
+ });
597
+ if (decoded.args) {
598
+ const target = decoded.args[2];
599
+ to = (0, viem_1.getAddress)(target);
600
+ from = '0x0000000000000000000000000000000000000000';
562
601
  }
563
602
  }
564
- }
565
- const origin = `${chain_1.VM_NAMESPACE[vm]}:${chainId}:${originAddress}`;
566
- // Create signature from transaction r, s, v values.
567
- //
568
- // Push Chain's derived (module-injected) inbound txs are reported over the EVM
569
- // JSON-RPC as **legacy `type 0x0`**, which omits the `yParity` field that viem
570
- // only populates for typed (EIP-1559/2930) txs — so `tx.yParity` is `undefined`.
571
- // Per the chain team (the hash inconsistencies were fixed chain-side, but
572
- // derived txs remain legacy 0x0 by design), the SDK must accept `0x0` and
573
- // surface a numeric `yParity`. Derive it from `v`: 0/1 → as-is, 27/28 → v-27,
574
- // EIP-155 → (v-35)%2. (For the zeroed-signature derived txs, v=0 → yParity=0.)
575
- let signature;
576
- try {
577
- const vNum = typeof tx.v === 'bigint' ? Number(tx.v) : tx.v || 0;
578
- let yParity = tx.yParity;
579
- if (yParity === undefined) {
580
- if (vNum === 0 || vNum === 1)
581
- yParity = vNum;
582
- else if (vNum === 27 || vNum === 28)
583
- yParity = vNum - 27;
584
- else
585
- yParity = (vNum - 35) % 2;
603
+ catch {
604
+ // Keep original values if decoding fails
586
605
  }
587
- signature = {
588
- r: tx.r || '0x0',
589
- s: tx.s || '0x0',
590
- v: vNum,
591
- yParity,
592
- };
593
606
  }
594
- catch (_d) {
595
- // Fallback signature if parsing fails
596
- signature = {
597
- r: '0x0000000000000000000000000000000000000000000000000000000000000000',
598
- s: '0x0000000000000000000000000000000000000000000000000000000000000000',
599
- v: 0,
600
- yParity: 0,
601
- };
607
+ }
608
+ const origin = `${chain_1.VM_NAMESPACE[vm]}:${chainId}:${originAddress}`;
609
+ // Create signature from transaction r, s, v values.
610
+ //
611
+ // Push Chain's derived (module-injected) inbound txs are reported over the EVM
612
+ // JSON-RPC as **legacy `type 0x0`**, which omits the `yParity` field that viem
613
+ // only populates for typed (EIP-1559/2930) txs — so `tx.yParity` is `undefined`.
614
+ // Per the chain team (the hash inconsistencies were fixed chain-side, but
615
+ // derived txs remain legacy 0x0 by design), the SDK must accept `0x0` and
616
+ // surface a numeric `yParity`. Derive it from `v`: 0/1 → as-is, 27/28 → v-27,
617
+ // EIP-155 → (v-35)%2. (For the zeroed-signature derived txs, v=0 → yParity=0.)
618
+ let signature;
619
+ try {
620
+ const vNum = typeof tx.v === 'bigint' ? Number(tx.v) : tx.v || 0;
621
+ let yParity = tx.yParity;
622
+ if (yParity === undefined) {
623
+ if (vNum === 0 || vNum === 1)
624
+ yParity = vNum;
625
+ else if (vNum === 27 || vNum === 28)
626
+ yParity = vNum - 27;
627
+ else
628
+ yParity = (vNum - 35) % 2;
602
629
  }
603
- // Determine transaction type and typeVerbose
604
- let type = '99'; // universal
605
- let typeVerbose = 'universal';
606
- if (tx.type !== undefined) {
607
- const txType = tx.type;
608
- if (txType === 'eip1559') {
609
- type = '2';
610
- typeVerbose = 'eip1559';
611
- }
612
- else if (txType === 'eip2930') {
613
- type = '1';
614
- typeVerbose = 'eip2930';
615
- }
616
- else if (txType === 'legacy') {
617
- type = '0';
618
- typeVerbose = 'legacy';
619
- }
620
- else if (txType === 'eip4844') {
621
- type = '3';
622
- typeVerbose = 'eip4844';
623
- }
630
+ signature = {
631
+ r: tx.r || '0x0',
632
+ s: tx.s || '0x0',
633
+ v: vNum,
634
+ yParity,
635
+ };
636
+ }
637
+ catch {
638
+ // Fallback signature if parsing fails
639
+ signature = {
640
+ r: '0x0000000000000000000000000000000000000000000000000000000000000000',
641
+ s: '0x0000000000000000000000000000000000000000000000000000000000000000',
642
+ v: 0,
643
+ yParity: 0,
644
+ };
645
+ }
646
+ // Determine transaction type and typeVerbose
647
+ let type = '99'; // universal
648
+ let typeVerbose = 'universal';
649
+ if (tx.type !== undefined) {
650
+ const txType = tx.type;
651
+ if (txType === 'eip1559') {
652
+ type = '2';
653
+ typeVerbose = 'eip1559';
624
654
  }
625
- // Storage for registered progress callback (used by progressHook method)
626
- let registeredProgressHook;
627
- const universalTxResponse = {
628
- // 1. Identity
629
- hash: tx.hash,
630
- finalTxHash: tx.hash, // Push tx hash; .wait()'s receipt refines it for R2/R3
631
- origin,
632
- // 2. Block Info
633
- blockNumber: tx.blockNumber || BigInt(0),
634
- blockHash: tx.blockHash || '',
635
- transactionIndex: tx.transactionIndex || 0,
636
- chainId,
637
- // 3. Execution Context
638
- from: from, // UEA (executor) address, checksummed for EVM
639
- to: to || '',
640
- nonce: tx.nonce,
641
- // 4. Payload
642
- data, // perceived calldata (was input)
643
- value,
644
- // 5. Gas
645
- gasLimit: tx.gas || BigInt(0), // (was gas)
646
- gasPrice: tx.gasPrice,
647
- maxFeePerGas: tx.maxFeePerGas,
648
- maxPriorityFeePerGas: tx.maxPriorityFeePerGas,
649
- accessList: Array.isArray(tx.accessList) ? [...tx.accessList] : [],
650
- // 6. Utilities
651
- wait: (waitOptions) => tslib_1.__awaiter(this, void 0, void 0, function* () {
652
- var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
653
- // For trackTransaction replays the tx is historical — cosmos already
654
- // has the full graph indexed, so the 20s/30s initial-wait buffers
655
- // designed for live sends are pure deadweight. Default them to 0 when
656
- // replaying; callers can still override via WaitOptions.
657
- const isReplay = universalTxResponse._eventsReconstructed === true;
658
- const outboundTimeoutMs = (_a = waitOptions === null || waitOptions === void 0 ? void 0 : waitOptions.outboundTimeoutMs) !== null && _a !== void 0 ? _a : callbacks.outboundConstants.maxTimeoutMs;
659
- const inboundTimeoutMs = (_b = waitOptions === null || waitOptions === void 0 ? void 0 : waitOptions.inboundTimeoutMs) !== null && _b !== void 0 ? _b : callbacks.inboundConstants.maxTimeoutMs;
660
- const outboundInitialWaitMs = (_c = waitOptions === null || waitOptions === void 0 ? void 0 : waitOptions.outboundInitialWaitMs) !== null && _c !== void 0 ? _c : (isReplay ? 0 : callbacks.outboundConstants.initialWaitMs);
661
- const outboundPollingIntervalMs = (_d = waitOptions === null || waitOptions === void 0 ? void 0 : waitOptions.outboundPollingIntervalMs) !== null && _d !== void 0 ? _d : callbacks.outboundConstants.pollingIntervalMs;
662
- const inboundInitialWaitMs = isReplay
663
- ? 0
664
- : callbacks.inboundConstants.initialWaitMs;
665
- // Build the base receipt directly from this response. Any per-call
666
- // `tx.progressHook(cb)` already replayed buffered execute-phase events
667
- // to `cb` via the progressHook setter — re-running trackTransaction
668
- // here would duplicate the reconstructed stream on top of that replay,
669
- // which is exactly what broke R1 parity. Wait-phase events (209-xx /
670
- // 299-xx / 399-xx) still reach `registeredProgressHook` and
671
- // `ctx.progressHook` via the `emit` closure in the outbound branch.
672
- const receipt = yield tx.wait();
673
- let baseReceipt = callbacks.transformToUniversalTxReceipt(receipt, universalTxResponse);
674
- // If outbound route, poll for external chain details
675
- const route = universalTxResponse.route;
676
- const routeInfo = route ? (0, route_detector_1.getRouteInfo)(route) : undefined;
677
- if (routeInfo === null || routeInfo === void 0 ? void 0 : routeInfo.isOutbound) {
678
- // Pick route-specific hook builders. R4 (CEA_TO_CEA) currently has
679
- // no spec'd IDs — pickWaitHooks returns a no-op set that yields
680
- // events with `id: ''`. `emit` drops those so the consumer never
681
- // sees placeholder events, while receipt mutation still runs.
682
- const hooks = (0, progress_route_hooks_1.pickWaitHooks)(route);
683
- const targetChain = ((_e = universalTxResponse.chain) !== null && _e !== void 0 ? _e : 'external');
655
+ else if (txType === 'eip2930') {
656
+ type = '1';
657
+ typeVerbose = 'eip2930';
658
+ }
659
+ else if (txType === 'legacy') {
660
+ type = '0';
661
+ typeVerbose = 'legacy';
662
+ }
663
+ else if (txType === 'eip4844') {
664
+ type = '3';
665
+ typeVerbose = 'eip4844';
666
+ }
667
+ }
668
+ // Storage for registered progress callback (used by progressHook method)
669
+ let registeredProgressHook;
670
+ const universalTxResponse = {
671
+ // 1. Identity
672
+ hash: tx.hash,
673
+ finalTxHash: tx.hash, // Push tx hash; .wait()'s receipt refines it for R2/R3
674
+ origin,
675
+ // 2. Block Info
676
+ blockNumber: tx.blockNumber || BigInt(0),
677
+ blockHash: tx.blockHash || '',
678
+ transactionIndex: tx.transactionIndex || 0,
679
+ chainId,
680
+ // 3. Execution Context
681
+ from: from, // UEA (executor) address, checksummed for EVM
682
+ to: to || '',
683
+ nonce: tx.nonce,
684
+ // 4. Payload
685
+ data, // perceived calldata (was input)
686
+ value,
687
+ // 5. Gas
688
+ gasLimit: tx.gas || BigInt(0), // (was gas)
689
+ gasPrice: tx.gasPrice,
690
+ maxFeePerGas: tx.maxFeePerGas,
691
+ maxPriorityFeePerGas: tx.maxPriorityFeePerGas,
692
+ accessList: Array.isArray(tx.accessList) ? [...tx.accessList] : [],
693
+ // 6. Utilities
694
+ wait: async (waitOptions) => {
695
+ // For trackTransaction replays the tx is historical — cosmos already
696
+ // has the full graph indexed, so the 20s/30s initial-wait buffers
697
+ // designed for live sends are pure deadweight. Default them to 0 when
698
+ // replaying; callers can still override via WaitOptions.
699
+ const isReplay = universalTxResponse._eventsReconstructed === true;
700
+ const outboundTimeoutMs = waitOptions?.outboundTimeoutMs ??
701
+ callbacks.outboundConstants.maxTimeoutMs;
702
+ const inboundTimeoutMs = waitOptions?.inboundTimeoutMs ??
703
+ callbacks.inboundConstants.maxTimeoutMs;
704
+ const outboundInitialWaitMs = waitOptions?.outboundInitialWaitMs ??
705
+ (isReplay ? 0 : callbacks.outboundConstants.initialWaitMs);
706
+ const outboundPollingIntervalMs = waitOptions?.outboundPollingIntervalMs ??
707
+ callbacks.outboundConstants.pollingIntervalMs;
708
+ const inboundInitialWaitMs = isReplay
709
+ ? 0
710
+ : callbacks.inboundConstants.initialWaitMs;
711
+ // Build the base receipt directly from this response. Any per-call
712
+ // `tx.progressHook(cb)` already replayed buffered execute-phase events
713
+ // to `cb` via the progressHook setter — re-running trackTransaction
714
+ // here would duplicate the reconstructed stream on top of that replay,
715
+ // which is exactly what broke R1 parity. Wait-phase events (209-xx /
716
+ // 299-xx / 399-xx) still reach `registeredProgressHook` and
717
+ // `ctx.progressHook` via the `emit` closure in the outbound branch.
718
+ const receipt = await tx.wait();
719
+ let baseReceipt = callbacks.transformToUniversalTxReceipt(receipt, universalTxResponse);
720
+ // If outbound route, poll for external chain details
721
+ const route = universalTxResponse.route;
722
+ const routeInfo = route ? (0, route_detector_1.getRouteInfo)(route) : undefined;
723
+ // A reverted Push-chain root transaction can never produce an outbound
724
+ // leg. Previously we still entered waitForOutboundTx() here, which made
725
+ // a known failure look like an in-progress relay until the outbound
726
+ // timeout elapsed. Stop at the root receipt instead and emit the
727
+ // route-terminal failure immediately. `externalStatus` intentionally
728
+ // remains unset: no external transaction was submitted, so the failure
729
+ // is represented by the canonical receipt `status === 0`.
730
+ if (baseReceipt.status === 0) {
731
+ const errMsg = `Push Chain transaction ${universalTxResponse.hash} reverted; ` +
732
+ 'outbound relay was not started';
733
+ if (routeInfo?.isOutbound) {
734
+ const targetChain = (universalTxResponse.chain ??
735
+ 'external');
684
736
  const emit = (event) => {
685
737
  if (!event.id)
686
738
  return;
687
739
  fanOut(event, registeredProgressHook, ctx.progressHook);
688
740
  };
689
- // Awaiting relay
690
- emit(hooks.awaiting(targetChain, universalTxResponse.hash));
691
- let lastEmittedStatus;
692
- const outboundTranslator = (event) => {
693
- if (event.status === 'polling' && lastEmittedStatus !== 'polling') {
694
- lastEmittedStatus = 'polling';
695
- emit(hooks.polling(targetChain, event.elapsed, universalTxResponse.hash));
696
- }
741
+ if (route === route_detector_1.TransactionRoute.CEA_TO_PUSH) {
742
+ emit(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_399_02](errMsg, 'push', targetChain, undefined, universalTxResponse.hash));
743
+ }
744
+ else {
745
+ emit((0, progress_route_hooks_1.pickWaitHooks)(route).failed(targetChain, errMsg, {
746
+ pushTxHash: universalTxResponse.hash,
747
+ }));
748
+ }
749
+ }
750
+ callbacks.printLog(`[wait] ${errMsg}`);
751
+ return baseReceipt;
752
+ }
753
+ if (routeInfo?.isOutbound) {
754
+ // Pick route-specific hook builders. R4 (CEA_TO_CEA) currently has
755
+ // no spec'd IDs — pickWaitHooks returns a no-op set that yields
756
+ // events with `id: ''`. `emit` drops those so the consumer never
757
+ // sees placeholder events, while receipt mutation still runs.
758
+ const hooks = (0, progress_route_hooks_1.pickWaitHooks)(route);
759
+ const targetChain = (universalTxResponse.chain ?? 'external');
760
+ const emit = (event) => {
761
+ if (!event.id)
762
+ return;
763
+ fanOut(event, registeredProgressHook, ctx.progressHook);
764
+ };
765
+ // Awaiting relay
766
+ emit(hooks.awaiting(targetChain, universalTxResponse.hash));
767
+ let lastEmittedStatus;
768
+ const outboundTranslator = (event) => {
769
+ if (event.status === 'polling' && lastEmittedStatus !== 'polling') {
770
+ lastEmittedStatus = 'polling';
771
+ emit(hooks.polling(targetChain, event.elapsed, universalTxResponse.hash));
772
+ }
773
+ };
774
+ try {
775
+ const outboundDetails = await callbacks.waitForOutboundTx(tx.hash, {
776
+ initialWaitMs: outboundInitialWaitMs,
777
+ pollingIntervalMs: outboundPollingIntervalMs,
778
+ timeout: outboundTimeoutMs,
779
+ progressHook: outboundTranslator,
780
+ });
781
+ // `outboundDetails.externalTxHash` is the raw `0x`-hex form (kept
782
+ // internally so the R3 inbound tracker below can query Cosmos with
783
+ // it via `waitForInboundPushTx → findChildUtxIdFromExternalTx`).
784
+ // For every USER-FACING surface (progress hook event + receipt) we
785
+ // want the chain-native form so consumers can paste it straight
786
+ // into an explorer / RPC: base58 for SVM, hex for EVM. Build a
787
+ // display copy and use it for both the hook and the receipt.
788
+ const displayExternalTxHash = (0, helpers_1.toExternalTxHashDisplay)(outboundDetails.destinationChain, outboundDetails.externalTxHash) ?? outboundDetails.externalTxHash;
789
+ const userFacingOutboundDetails = {
790
+ ...outboundDetails,
791
+ externalTxHash: displayExternalTxHash,
697
792
  };
698
- try {
699
- const outboundDetails = yield callbacks.waitForOutboundTx(tx.hash, {
700
- initialWaitMs: outboundInitialWaitMs,
701
- pollingIntervalMs: outboundPollingIntervalMs,
702
- timeout: outboundTimeoutMs,
703
- progressHook: outboundTranslator,
704
- });
705
- // `outboundDetails.externalTxHash` is the raw `0x`-hex form (kept
706
- // internally so the R3 inbound tracker below can query Cosmos with
707
- // it via `waitForInboundPushTx → findChildUtxIdFromExternalTx`).
708
- // For every USER-FACING surface (progress hook event + receipt) we
709
- // want the chain-native form so consumers can paste it straight
710
- // into an explorer / RPC: base58 for SVM, hex for EVM. Build a
711
- // display copy and use it for both the hook and the receipt.
712
- const displayExternalTxHash = (_f = (0, helpers_1.toExternalTxHashDisplay)(outboundDetails.destinationChain, outboundDetails.externalTxHash)) !== null && _f !== void 0 ? _f : outboundDetails.externalTxHash;
713
- const userFacingOutboundDetails = Object.assign(Object.assign({}, outboundDetails), { externalTxHash: displayExternalTxHash });
714
- emit(hooks.success(userFacingOutboundDetails));
715
- baseReceipt = Object.assign(Object.assign({}, baseReceipt), { externalTxHash: displayExternalTxHash, externalChain: outboundDetails.destinationChain, externalExplorerUrl: outboundDetails.explorerUrl, externalRecipient: outboundDetails.recipient, externalAmount: outboundDetails.amount, externalAssetAddr: outboundDetails.assetAddr, externalStatus: 'success' });
716
- // R3 round-trip: poll Push Chain for the inbound tx that the
717
- // source-chain CEA's sendUniversalTxToUEA call produces. Correlate
718
- // via the outbound external tx hash → child UTX id → child pcTx.
719
- // Only run when the CEA payload actually fires sendUniversalTxToUEA;
720
- // payload-only R3 (no funds flowing back) has no child UTX and would
721
- // otherwise time out after 300s.
722
- if (route === route_detector_1.TransactionRoute.CEA_TO_PUSH &&
723
- universalTxResponse._expectsInboundRoundTrip === true) {
724
- emit(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_310_01](targetChain, universalTxResponse.hash));
725
- let inboundLastEmitted;
726
- try {
727
- const inbound = yield (0, inbound_tracker_1.waitForInboundPushTx)(ctx, outboundDetails.externalTxHash, targetChain, {
728
- initialWaitMs: inboundInitialWaitMs,
729
- pollingIntervalMs: callbacks.inboundConstants.pollingIntervalMs,
730
- timeout: inboundTimeoutMs,
731
- progressHook: (event) => {
732
- if (event.status === 'polling' &&
733
- inboundLastEmitted !== 'polling') {
734
- inboundLastEmitted = 'polling';
735
- emit(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_310_02](targetChain, event.elapsedMs, universalTxResponse.hash));
736
- }
737
- },
738
- });
739
- if (inbound.status === 'confirmed') {
740
- emit(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_399_01](targetChain, inbound.pushTxHash, baseReceipt));
741
- baseReceipt = Object.assign(Object.assign({}, baseReceipt), { pushInboundTxHash: inbound.pushTxHash, pushInboundUtxId: inbound.childUtxId });
742
- }
743
- else {
744
- // status === 'failed' (terminal failure status from chain)
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, undefined, universalTxResponse.hash, inbound.pushTxHash || undefined));
747
- baseReceipt = Object.assign(Object.assign({}, baseReceipt), { pushInboundUtxId: inbound.childUtxId, pushInboundTxHash: inbound.pushTxHash || undefined, externalStatus: 'failed', externalError: failMsg });
748
- }
793
+ emit(hooks.success(userFacingOutboundDetails));
794
+ baseReceipt = {
795
+ ...baseReceipt,
796
+ externalTxHash: displayExternalTxHash,
797
+ externalChain: outboundDetails.destinationChain,
798
+ externalExplorerUrl: outboundDetails.explorerUrl,
799
+ externalRecipient: outboundDetails.recipient,
800
+ externalAmount: outboundDetails.amount,
801
+ externalAssetAddr: outboundDetails.assetAddr,
802
+ externalStatus: 'success',
803
+ };
804
+ // R3 round-trip: poll Push Chain for the inbound tx that the
805
+ // source-chain CEA's sendUniversalTxToUEA call produces. Correlate
806
+ // via the outbound external tx hash → child UTX id → child pcTx.
807
+ // Only run when the CEA payload actually fires sendUniversalTxToUEA;
808
+ // payload-only R3 (no funds flowing back) has no child UTX and would
809
+ // otherwise time out after 300s.
810
+ if (route === route_detector_1.TransactionRoute.CEA_TO_PUSH &&
811
+ universalTxResponse._expectsInboundRoundTrip === true) {
812
+ emit(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_310_01](targetChain, universalTxResponse.hash));
813
+ let inboundLastEmitted;
814
+ try {
815
+ const inbound = await (0, inbound_tracker_1.waitForInboundPushTx)(ctx, outboundDetails.externalTxHash, targetChain, {
816
+ initialWaitMs: inboundInitialWaitMs,
817
+ pollingIntervalMs: callbacks.inboundConstants.pollingIntervalMs,
818
+ timeout: inboundTimeoutMs,
819
+ progressHook: (event) => {
820
+ if (event.status === 'polling' &&
821
+ inboundLastEmitted !== 'polling') {
822
+ inboundLastEmitted = 'polling';
823
+ emit(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_310_02](targetChain, event.elapsedMs, universalTxResponse.hash));
824
+ }
825
+ },
826
+ });
827
+ if (inbound.status === 'confirmed') {
828
+ emit(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_399_01](targetChain, inbound.pushTxHash, baseReceipt));
829
+ baseReceipt = {
830
+ ...baseReceipt,
831
+ pushInboundTxHash: inbound.pushTxHash,
832
+ pushInboundUtxId: inbound.childUtxId,
833
+ // Round-trip completed. externalStatus stays 'success'
834
+ // (set on the outbound-found branch above).
835
+ };
749
836
  }
750
- catch (inboundErr) {
751
- const errMsg = inboundErr instanceof Error
752
- ? inboundErr.message
753
- : String(inboundErr);
754
- const isTimeout = inboundErr instanceof inbound_tracker_1.InboundTimeoutError;
755
- // Annotate receipt for inbound-leg outcome too.
756
- baseReceipt = Object.assign(Object.assign({}, baseReceipt), { externalStatus: isTimeout ? 'timeout' : 'failed', externalError: errMsg });
757
- emit(isTimeout
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
- callbacks.printLog(`[wait] R3 inbound tracking failed: ${errMsg}`);
837
+ else {
838
+ // status === 'failed' (terminal failure status from chain)
839
+ const failMsg = inbound.errorMessage ??
840
+ 'inbound execution failed on Push Chain';
841
+ emit(progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_399_02](failMsg, 'inbound', targetChain, undefined, universalTxResponse.hash, inbound.pushTxHash || undefined));
842
+ baseReceipt = {
843
+ ...baseReceipt,
844
+ pushInboundUtxId: inbound.childUtxId,
845
+ pushInboundTxHash: inbound.pushTxHash || undefined,
846
+ externalStatus: 'failed',
847
+ externalError: failMsg,
848
+ };
761
849
  }
762
850
  }
851
+ catch (inboundErr) {
852
+ const errMsg = inboundErr instanceof Error
853
+ ? inboundErr.message
854
+ : String(inboundErr);
855
+ const isTimeout = inboundErr instanceof inbound_tracker_1.InboundTimeoutError;
856
+ // Annotate receipt for inbound-leg outcome too.
857
+ baseReceipt = {
858
+ ...baseReceipt,
859
+ externalStatus: isTimeout ? 'timeout' : 'failed',
860
+ externalError: errMsg,
861
+ };
862
+ emit(isTimeout
863
+ ? progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_399_03](targetChain, inboundTimeoutMs, 'inbound', universalTxResponse.hash)
864
+ : progress_hook_1.default[progress_hook_types_1.PROGRESS_HOOK.SEND_TX_399_02](errMsg, 'inbound', targetChain, undefined, universalTxResponse.hash));
865
+ callbacks.printLog(`[wait] R3 inbound tracking failed: ${errMsg}`);
866
+ }
763
867
  }
764
- catch (error) {
765
- const errMsg = error instanceof Error ? error.message : String(error);
766
- const isTimeout = error instanceof outbound_sync_1.OutboundTimeoutError;
767
- const failedExternalTxHash = error instanceof outbound_sync_1.OutboundFailedError ? error.externalTxHash : undefined;
768
- // Normalize to chain-native display form for SVM (base58); EVM hex
769
- // passes through unchanged. `OutboundFailedError.destinationChain`
770
- // is a chain namespace string (e.g., 'solana:devnet') — convert to
771
- // the CHAIN enum via the same helper used in outbound-sync.
772
- const failedChain = error instanceof outbound_sync_1.OutboundFailedError && error.destinationChain
773
- ? (_h = (0, helpers_1.chainFromNamespace)(error.destinationChain)) !== null && _h !== void 0 ? _h : undefined
774
- : undefined;
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.
788
- baseReceipt = Object.assign(Object.assign(Object.assign({}, baseReceipt), { externalStatus: isTimeout ? 'timeout' : 'failed', externalError: errMsg }), (failedExternalTxHashDisplay
868
+ }
869
+ catch (error) {
870
+ const errMsg = error instanceof Error ? error.message : String(error);
871
+ const isTimeout = error instanceof outbound_sync_1.OutboundTimeoutError;
872
+ const failedExternalTxHash = error instanceof outbound_sync_1.OutboundFailedError ? error.externalTxHash : undefined;
873
+ // Normalize to chain-native display form for SVM (base58); EVM hex
874
+ // passes through unchanged. `OutboundFailedError.destinationChain`
875
+ // is a chain namespace string (e.g., 'solana:devnet') — convert to
876
+ // the CHAIN enum via the same helper used in outbound-sync.
877
+ const failedChain = error instanceof outbound_sync_1.OutboundFailedError && error.destinationChain
878
+ ? (0, helpers_1.chainFromNamespace)(error.destinationChain) ?? undefined
879
+ : undefined;
880
+ const failedExternalTxHashDisplay = (0, helpers_1.toExternalTxHashDisplay)(failedChain, failedExternalTxHash) ??
881
+ failedExternalTxHash;
882
+ emit(isTimeout
883
+ ? hooks.timeout(targetChain, outboundTimeoutMs, universalTxResponse.hash)
884
+ : hooks.failed(targetChain, errMsg, {
885
+ pushTxHash: universalTxResponse.hash,
886
+ txHash: failedExternalTxHashDisplay,
887
+ }));
888
+ // Annotate the receipt so callers can distinguish external-leg
889
+ // outcomes without sniffing errMsg. Push Chain leg still succeeded
890
+ // (status stays 1); inspect `externalStatus` / `externalError` for
891
+ // the external/inbound leg outcome. On a reverted outbound the
892
+ // observed tx hash is carried by OutboundFailedError — expose it
893
+ // as externalTxHash so consumers can link to the explorer.
894
+ baseReceipt = {
895
+ ...baseReceipt,
896
+ externalStatus: isTimeout ? 'timeout' : 'failed',
897
+ externalError: errMsg,
898
+ ...(failedExternalTxHashDisplay
789
899
  ? { externalTxHash: failedExternalTxHashDisplay }
790
- : {}));
791
- // Outbound polling timed out or failed — return the annotated
792
- // receipt without throwing. Push Chain tx succeeded, external
793
- // tracking can be retried later.
794
- callbacks.printLog(`[wait] External chain tracking ${isTimeout ? 'timed out' : 'failed'}: ${errMsg}`);
795
- }
900
+ : {}),
901
+ };
902
+ // Outbound polling timed out or failed — return the annotated
903
+ // receipt without throwing. Push Chain tx succeeded, external
904
+ // tracking can be retried later.
905
+ callbacks.printLog(`[wait] External chain tracking ${isTimeout ? 'timed out' : 'failed'}: ${errMsg}`);
796
906
  }
797
- // Resolve the terminal hash for the whole journey: inbound (R3) >
798
- // external outbound (R2) > the Push tx itself (R1). Single-tx analogue
799
- // of CascadeCompletionResult.finalTxHash.
800
- baseReceipt = Object.assign(Object.assign({}, baseReceipt), { finalTxHash: (_l = (_k = baseReceipt.pushInboundTxHash) !== null && _k !== void 0 ? _k : baseReceipt.externalTxHash) !== null && _l !== void 0 ? _l : baseReceipt.hash,
801
- // Ensure `externalChain` is populated whenever an external/inbound leg
802
- // surfaced an `externalTxHash`. The outbound-success branch already sets
803
- // it to the destination chain (so this `??` never overrides it); this
804
- // only fills the inbound/R3 and failed/timeout paths, where it should be
805
- // the route's external chain (= the response's `chain`).
806
- externalChain: (_m = baseReceipt.externalChain) !== null && _m !== void 0 ? _m : (baseReceipt.externalTxHash
907
+ }
908
+ // Resolve the terminal hash for the whole journey: inbound (R3) >
909
+ // external outbound (R2) > the Push tx itself (R1). Single-tx analogue
910
+ // of CascadeCompletionResult.finalTxHash.
911
+ baseReceipt = {
912
+ ...baseReceipt,
913
+ finalTxHash: baseReceipt.pushInboundTxHash ??
914
+ baseReceipt.externalTxHash ??
915
+ baseReceipt.hash,
916
+ // Ensure `externalChain` is populated whenever an external/inbound leg
917
+ // surfaced an `externalTxHash`. The outbound-success branch already sets
918
+ // it to the destination chain (so this `??` never overrides it); this
919
+ // only fills the inbound/R3 and failed/timeout paths, where it should be
920
+ // the route's external chain (= the response's `chain`).
921
+ externalChain: baseReceipt.externalChain ??
922
+ (baseReceipt.externalTxHash
807
923
  ? universalTxResponse.chain
808
- : baseReceipt.externalChain) });
809
- return baseReceipt;
810
- }),
811
- // Internal: register a wait-phase progressHook without replaying
812
- // buffered events. Used by trackTransaction() after it has already
813
- // emitted the reconstructed stream to the caller's per-call hook,
814
- // so that tracked.wait() can deliver wait-phase events (209-xx /
815
- // 299-xx / 399-xx) to the same callback without duplicating replay.
816
- _setProgressHookNoReplay: (callback) => {
817
- registeredProgressHook = callback;
818
- },
819
- progressHook: (callback) => {
820
- registeredProgressHook = callback;
821
- // Immediately replay buffered events from execution
822
- if (eventBuffer.length > 0) {
823
- for (const event of eventBuffer) {
824
- callback(event);
825
- }
924
+ : baseReceipt.externalChain),
925
+ };
926
+ return baseReceipt;
927
+ },
928
+ // Internal: register a wait-phase progressHook without replaying
929
+ // buffered events. Used by trackTransaction() after it has already
930
+ // emitted the reconstructed stream to the caller's per-call hook,
931
+ // so that tracked.wait() can deliver wait-phase events (209-xx /
932
+ // 299-xx / 399-xx) to the same callback without duplicating replay.
933
+ _setProgressHookNoReplay: (callback) => {
934
+ registeredProgressHook = callback;
935
+ },
936
+ progressHook: (callback) => {
937
+ registeredProgressHook = callback;
938
+ // Immediately replay buffered events from execution
939
+ if (eventBuffer.length > 0) {
940
+ for (const event of eventBuffer) {
941
+ callback(event);
826
942
  }
827
- },
828
- // 7. Metadata
829
- type,
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,
837
- signature,
838
- // 8. Raw Universal Fields
839
- raw: rawTransactionData,
840
- };
841
- return universalTxResponse;
842
- });
943
+ }
944
+ },
945
+ // 7. Metadata
946
+ type,
947
+ typeVerbose,
948
+ // Atomic by default — a single tx, an EIP-7702 batch, and a UEA multicall
949
+ // are all all-or-nothing. Only the native sequential fallback loop overrides
950
+ // this to `false` (see sendPushTx in push-chain-tx.ts). Historical lookups
951
+ // via trackTransaction also keep the default, since the past execution mode
952
+ // isn't always recoverable.
953
+ atomic: true,
954
+ signature,
955
+ // 8. Raw Universal Fields
956
+ raw: rawTransactionData,
957
+ };
958
+ return universalTxResponse;
843
959
  }
844
960
  //# sourceMappingURL=response-builder.js.map