@pushchain/core 6.0.23 → 6.0.25

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 (94) hide show
  1. package/CHANGELOG.md +84 -0
  2. package/package.json +1 -1
  3. package/src/lib/constants/abi/index.d.ts +1 -0
  4. package/src/lib/constants/abi/index.js +4 -1
  5. package/src/lib/constants/abi/prc20.evm.d.ts +32 -0
  6. package/src/lib/constants/abi/prc20.evm.js +20 -0
  7. package/src/lib/constants/abi/uea-factory.d.ts +31 -0
  8. package/src/lib/constants/abi/uea-factory.js +19 -0
  9. package/src/lib/constants/abi/universalCallback.evm.d.ts +561 -0
  10. package/src/lib/constants/abi/universalCallback.evm.js +247 -0
  11. package/src/lib/constants/abi/universalGatewayV0.json +1 -1
  12. package/src/lib/constants/abi/universalReadRegistry.evm.d.ts +247 -0
  13. package/src/lib/constants/abi/universalReadRegistry.evm.js +38 -0
  14. package/src/lib/constants/chain.d.ts +12 -0
  15. package/src/lib/constants/chain.js +23 -1
  16. package/src/lib/constants/enums.d.ts +7 -0
  17. package/src/lib/constants/enums.js +11 -0
  18. package/src/lib/constants/index.d.ts +52 -0
  19. package/src/lib/constants/index.js +37 -8
  20. package/src/lib/constants/read-state.d.ts +53 -0
  21. package/src/lib/constants/read-state.js +59 -0
  22. package/src/lib/generated/ucallback/v1/index.d.ts +2 -0
  23. package/src/lib/generated/ucallback/v1/index.js +6 -0
  24. package/src/lib/generated/ucallback/v1/query.d.ts +47 -0
  25. package/src/lib/generated/ucallback/v1/query.js +131 -0
  26. package/src/lib/generated/ucallback/v1/types.d.ts +99 -0
  27. package/src/lib/generated/ucallback/v1/types.js +366 -0
  28. package/src/lib/generated/uexecutor/v1/query.d.ts +29 -0
  29. package/src/lib/generated/uexecutor/v1/query.js +98 -1
  30. package/src/lib/index.d.ts +9 -0
  31. package/src/lib/index.js +37 -1
  32. package/src/lib/orchestrator/internals/cascade.js +5 -2
  33. package/src/lib/orchestrator/internals/errors.d.ts +6 -0
  34. package/src/lib/orchestrator/internals/errors.js +13 -1
  35. package/src/lib/orchestrator/internals/index.d.ts +1 -0
  36. package/src/lib/orchestrator/internals/index.js +6 -1
  37. package/src/lib/orchestrator/internals/push-chain-tx.js +81 -61
  38. package/src/lib/orchestrator/internals/read-state.d.ts +24 -0
  39. package/src/lib/orchestrator/internals/read-state.js +47 -0
  40. package/src/lib/orchestrator/internals/route-handlers.js +6 -1
  41. package/src/lib/orchestrator/internals/svm-helpers.js +24 -0
  42. package/src/lib/orchestrator/orchestrator.d.ts +26 -0
  43. package/src/lib/orchestrator/orchestrator.js +22 -0
  44. package/src/lib/orchestrator/orchestrator.types.d.ts +2 -0
  45. package/src/lib/orchestrator/route-detector.js +4 -0
  46. package/src/lib/progress-hook/progress-hook.js +271 -0
  47. package/src/lib/progress-hook/progress-hook.types.d.ts +32 -0
  48. package/src/lib/progress-hook/progress-hook.types.js +34 -0
  49. package/src/lib/push-chain/push-chain.d.ts +70 -0
  50. package/src/lib/push-chain/push-chain.js +27 -0
  51. package/src/lib/push-client/push-client.d.ts +35 -2
  52. package/src/lib/push-client/push-client.js +117 -4
  53. package/src/lib/read-state/budget.d.ts +16 -0
  54. package/src/lib/read-state/budget.js +32 -0
  55. package/src/lib/read-state/destination.d.ts +12 -0
  56. package/src/lib/read-state/destination.js +51 -0
  57. package/src/lib/read-state/envelopes/evm.d.ts +24 -0
  58. package/src/lib/read-state/envelopes/evm.js +148 -0
  59. package/src/lib/read-state/envelopes/index.d.ts +12 -0
  60. package/src/lib/read-state/envelopes/index.js +45 -0
  61. package/src/lib/read-state/envelopes/svm.d.ts +32 -0
  62. package/src/lib/read-state/envelopes/svm.js +107 -0
  63. package/src/lib/read-state/envelopes/web2.d.ts +29 -0
  64. package/src/lib/read-state/envelopes/web2.js +149 -0
  65. package/src/lib/read-state/errors.d.ts +70 -0
  66. package/src/lib/read-state/errors.js +116 -0
  67. package/src/lib/read-state/index.d.ts +13 -0
  68. package/src/lib/read-state/index.js +38 -0
  69. package/src/lib/read-state/preflight.d.ts +21 -0
  70. package/src/lib/read-state/preflight.js +70 -0
  71. package/src/lib/read-state/read-events.d.ts +27 -0
  72. package/src/lib/read-state/read-events.js +135 -0
  73. package/src/lib/read-state/read-executor.d.ts +18 -0
  74. package/src/lib/read-state/read-executor.js +212 -0
  75. package/src/lib/read-state/read-params.d.ts +205 -0
  76. package/src/lib/read-state/read-params.js +120 -0
  77. package/src/lib/read-state/read-state.types.d.ts +417 -0
  78. package/src/lib/read-state/read-state.types.js +50 -0
  79. package/src/lib/read-state/read-tracker.d.ts +50 -0
  80. package/src/lib/read-state/read-tracker.js +415 -0
  81. package/src/lib/read-state/registry.d.ts +21 -0
  82. package/src/lib/read-state/registry.js +54 -0
  83. package/src/lib/read-state/result-decoder.d.ts +16 -0
  84. package/src/lib/read-state/result-decoder.js +73 -0
  85. package/src/lib/read-state/spec-builder.d.ts +47 -0
  86. package/src/lib/read-state/spec-builder.js +251 -0
  87. package/src/lib/read-state/validate.d.ts +22 -0
  88. package/src/lib/read-state/validate.js +80 -0
  89. package/src/lib/universal-tx-detector/child-inbounds.d.ts +3 -1
  90. package/src/lib/universal-tx-detector/child-inbounds.js +4 -5
  91. package/src/lib/universal-tx-detector/detector-svm.js +29 -20
  92. package/src/lib/universal-tx-detector/svm-events.d.ts +17 -5
  93. package/src/lib/universal-tx-detector/svm-events.js +81 -6
  94. package/src/lib/vm-client/evm-client.js +27 -10
@@ -0,0 +1,415 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.trackRead = trackRead;
4
+ exports.toRequestIdHex = toRequestIdHex;
5
+ exports.inferResultShape = inferResultShape;
6
+ exports.statusName = statusName;
7
+ exports.defaultWaitTimeoutMs = defaultWaitTimeoutMs;
8
+ exports.waitForRead = waitForRead;
9
+ /**
10
+ * trackRead / wait / refresh — resume a read from its Push tx hash or requestId
11
+ * and follow it to a terminal status.
12
+ *
13
+ * Source of truth is the node's `ucallback.v1.Query` record. What the node
14
+ * does NOT tell us is parsed from the UniversalCallback logs of the Push txs it
15
+ * lists in `pc_tx` (fulfil → ReadFulfilled | CallbackFailed; settle →
16
+ * CallbackGasReported + RefundSent | RefundFailed).
17
+ *
18
+ * Two rules verified live on Donut (2026-09-09):
19
+ * - FULFILLED does not mean delivered: the node sets it even when the app
20
+ * callback reverted. `callbackDelivered` is the real answer (I4).
21
+ * - EXPIRED has no EVM-indexed tx / receipt / logs (EndBlocker); never fetch
22
+ * its `pc_tx`. The refund is confirmed from the EndBlock `tx_log` events in the
23
+ * Cosmos block results instead (RefundSent / RefundFailed); if that lookup fails
24
+ * the refund fields stay undefined rather than guessed.
25
+ */
26
+ const viem_1 = require("viem");
27
+ const chain_1 = require("../constants/chain");
28
+ const enums_1 = require("../constants/enums");
29
+ const read_state_1 = require("../constants/read-state");
30
+ const progress_hook_types_1 = require("../progress-hook/progress-hook.types");
31
+ const destination_1 = require("./destination");
32
+ const envelopes_1 = require("./envelopes");
33
+ const errors_1 = require("./errors");
34
+ const read_events_1 = require("./read-events");
35
+ const read_state_types_1 = require("./read-state.types");
36
+ const result_decoder_1 = require("./result-decoder");
37
+ async function trackRead(deps, ref, opts = {}) {
38
+ if ('txHash' in ref) {
39
+ const records = await lookupWithRetry(deps, ref, opts);
40
+ return Promise.all(records.map((r) => buildResponse(deps, r, opts)));
41
+ }
42
+ const [record] = await lookupWithRetry(deps, ref, opts);
43
+ return buildResponse(deps, record, opts);
44
+ }
45
+ /** Normalise a bigint / hex requestId to the node's lowercase 32-byte key. */
46
+ function toRequestIdHex(id) {
47
+ const hex = typeof id === 'bigint' ? id.toString(16) : id.replace(/^0x/i, '');
48
+ return `0x${hex.toLowerCase().padStart(64, '0')}`;
49
+ }
50
+ // ---------------------------------------------------------------------------
51
+ // Lookup
52
+ // ---------------------------------------------------------------------------
53
+ async function fetchRecords(deps, ref) {
54
+ if ('txHash' in ref) {
55
+ const { reads } = await deps.pushClient.getReadsByTx(ref.txHash);
56
+ return reads;
57
+ }
58
+ const { read } = await deps.pushClient.getUniversalRead(toRequestIdHex(ref.requestId));
59
+ return read ? [read] : [];
60
+ }
61
+ /** A request is indexed when its block is processed — briefly retry a miss. */
62
+ async function lookupWithRetry(deps, ref, opts) {
63
+ const now = deps.now ?? Date.now;
64
+ const interval = pollInterval(opts);
65
+ const start = now();
66
+ for (;;) {
67
+ const records = await fetchRecords(deps, ref);
68
+ if (records.length > 0)
69
+ return records;
70
+ if (now() - start + interval > read_state_1.READ_TRACK_LOOKUP_TIMEOUT_MS)
71
+ break;
72
+ await sleep(interval);
73
+ }
74
+ const label = 'txHash' in ref ? ref.txHash : toRequestIdHex(ref.requestId);
75
+ throw new errors_1.ReadNotFoundError(label, 'txHash' in ref ? { txHash: ref.txHash } : { requestId: label });
76
+ }
77
+ function pollInterval(opts) {
78
+ return Math.max(read_state_1.READ_TRACK_MIN_POLL_INTERVAL_MS, opts.pollingIntervalMs ?? read_state_1.READ_TRACK_POLL_INTERVAL_MS);
79
+ }
80
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
81
+ // ---------------------------------------------------------------------------
82
+ // Record → response
83
+ // ---------------------------------------------------------------------------
84
+ async function buildResponse(deps, record, opts) {
85
+ const req = record.request;
86
+ if (!req)
87
+ throw new errors_1.ReadStateError('READ_RECORD_INCOMPLETE', 'node record has no request', { requestId: record.id });
88
+ const requestId = toRequestIdHex(record.id);
89
+ const status = record.status;
90
+ const isTerminal = read_state_types_1.TERMINAL_READ_STATUSES.has(status);
91
+ const destination = (0, destination_1.resolveDestination)({ chain: req.destinationChain });
92
+ const callbackBudget = BigInt(req.callbackBudget || '0');
93
+ const spec = {
94
+ account: { chainNamespace: destination.chainNamespace, chainId: destination.chainId, owner: (0, viem_1.bytesToHex)(req.owner) },
95
+ query: (0, viem_1.bytesToHex)(req.query),
96
+ minConfirmations: req.minConfirmations,
97
+ blockNumber: BigInt(req.destinationBlockHeight),
98
+ expiryPushChainHeight: BigInt(req.expiryBlockHeight),
99
+ maxFee: BigInt(req.maxFee || '0'),
100
+ revertRecipient: req.revertRecipient,
101
+ };
102
+ const fees = {
103
+ paid: BigInt(req.feesDeposited || '0'),
104
+ protocolFee: BigInt(req.protocolFee || '0'),
105
+ callbackBudget,
106
+ };
107
+ // Settlement facts live in the logs of the node's own txs.
108
+ let outcome = {};
109
+ if (status === read_state_types_1.UNIVERSAL_READ_STATUS.EXPIRED) {
110
+ // EndBlocker expiry: no receipt exists. The contract pushes the full budget to
111
+ // refundTo, but a rejecting recipient does not prevent EXPIRED — so read the
112
+ // RefundSent / RefundFailed logs from the block results instead of assuming.
113
+ outcome = await collectExpiryOutcome(deps, record, requestId);
114
+ if (outcome.refunded !== undefined)
115
+ fees.refunded = outcome.refunded;
116
+ if (outcome.refundFailed !== undefined)
117
+ fees.refundFailed = outcome.refundFailed;
118
+ }
119
+ else if (status === read_state_types_1.UNIVERSAL_READ_STATUS.FULFILLED || status === read_state_types_1.UNIVERSAL_READ_STATUS.FAILED) {
120
+ outcome = await collectOutcome(deps, record, requestId);
121
+ if (outcome.burned !== undefined)
122
+ fees.burned = outcome.burned;
123
+ if (outcome.refunded !== undefined)
124
+ fees.refunded = outcome.refunded;
125
+ if (outcome.refundFailed !== undefined)
126
+ fees.refundFailed = outcome.refundFailed;
127
+ }
128
+ const raw = record.result
129
+ ? { status: record.result.status, resultData: (0, viem_1.bytesToHex)(record.result.resultData), errorCode: record.result.errorCode }
130
+ : null;
131
+ const callbackDelivered = status === read_state_types_1.UNIVERSAL_READ_STATUS.FULFILLED ? outcome.delivered : undefined;
132
+ let decoded;
133
+ let decodeError;
134
+ let value;
135
+ if (status === read_state_types_1.UNIVERSAL_READ_STATUS.FULFILLED && callbackDelivered && raw?.status === read_state_types_1.READ_STATUS.SUCCESS) {
136
+ const shape = opts.resultShape ?? inferResultShape(destination.namespace, spec.query);
137
+ try {
138
+ decoded = (0, result_decoder_1.decodeReadResult)(raw.resultData, shape);
139
+ value = 'values' in decoded ? decoded.values : decoded.value;
140
+ }
141
+ catch (e) {
142
+ decodeError = e instanceof errors_1.ReadDecodeError ? e.message : String(e);
143
+ }
144
+ }
145
+ const txHash = req.requestedTxHash.toLowerCase();
146
+ const response = {
147
+ requestId,
148
+ txHash,
149
+ destination,
150
+ chain: chainFromCaip2(destination.caip2) ?? destination.caip2,
151
+ status,
152
+ isTerminal,
153
+ callbackDelivered,
154
+ callbackFailReason: outcome.failReason,
155
+ value,
156
+ decoded,
157
+ decodeError,
158
+ raw,
159
+ errorMsg: record.errorMsg,
160
+ fees,
161
+ request: {
162
+ spec,
163
+ callbackTarget: req.callbackTarget,
164
+ originalFunder: req.originalFunder,
165
+ refundTo: req.revertRecipient,
166
+ callbackGasLimit: BigInt(req.callbackGasLimit),
167
+ logIndex: req.requestedLogIndex,
168
+ createdAtHeight: BigInt(req.createdAtHeight),
169
+ },
170
+ pcTx: record.pcTx.map((t) => ({ txHash: t.txHash, blockHeight: t.blockHeight, status: t.status, errorMsg: t.errorMsg })),
171
+ explorerUrl: explorerTxUrl(deps.pushNetwork, txHash),
172
+ refresh: () => trackRead(deps, { requestId }, opts),
173
+ wait: (waitOpts) => waitForRead(deps, response, { ...opts, ...waitOpts }),
174
+ };
175
+ return response;
176
+ }
177
+ /** Parse every pc_tx receipt (fulfil, settle) into one merged outcome. Missing receipts are skipped. */
178
+ async function collectOutcome(deps, record, requestId) {
179
+ const uc = chain_1.UNIVERSAL_CALLBACK_ADDRESSES[deps.pushNetwork];
180
+ const merged = {};
181
+ for (const t of record.pcTx) {
182
+ if (!t.txHash)
183
+ continue;
184
+ try {
185
+ const receipt = await deps.pushClient.getTransactionReceiptWithArchiveFallback(t.txHash);
186
+ const o = (0, read_events_1.parseFulfilOutcome)(receipt, uc, requestId);
187
+ for (const [k, v] of Object.entries(o)) {
188
+ if (v !== undefined)
189
+ merged[k] = v;
190
+ }
191
+ }
192
+ catch {
193
+ // pruned or not yet available — leave those fields undefined rather than guess
194
+ }
195
+ }
196
+ return merged;
197
+ }
198
+ /**
199
+ * Expiry runs in the EndBlocker, so its UniversalCallback logs exist only as Cosmos
200
+ * `tx_log` events (`mode: EndBlock`) at the sweeper's block. Each `txLog` attribute is
201
+ * one JSON log with base64 `data`. Any failure leaves the outcome empty (unknown).
202
+ */
203
+ async function collectExpiryOutcome(deps, record, requestId) {
204
+ const heights = [...new Set(record.pcTx.map((t) => t.blockHeight).filter((h) => h > 0))].sort((a, b) => b - a);
205
+ for (const height of heights) {
206
+ try {
207
+ const events = await deps.pushClient.getBlockResultEvents(height);
208
+ const logs = [];
209
+ for (const ev of events) {
210
+ if (ev.type !== 'tx_log')
211
+ continue;
212
+ if (!ev.attributes.some((a) => a.key === 'mode' && a.value === 'EndBlock'))
213
+ continue;
214
+ for (const a of ev.attributes) {
215
+ if (a.key !== 'txLog')
216
+ continue;
217
+ try {
218
+ const log = JSON.parse(a.value);
219
+ if (typeof log.address !== 'string' || !Array.isArray(log.topics) || !log.topics.every((t) => typeof t === 'string'))
220
+ continue;
221
+ const data = typeof log.data === 'string' && log.data.startsWith('0x') ? log.data : (0, viem_1.bytesToHex)(new Uint8Array(Buffer.from(log.data ?? '', 'base64')));
222
+ logs.push({ address: log.address, topics: log.topics, data });
223
+ }
224
+ catch { /* An unrelated malformed log must not hide this request's refund. */ }
225
+ }
226
+ }
227
+ const out = (0, read_events_1.parseFulfilOutcome)({ logs }, chain_1.UNIVERSAL_CALLBACK_ADDRESSES[deps.pushNetwork], requestId);
228
+ // RequestExpired carries the amount the contract tried to push; only RefundSent /
229
+ // RefundFailed say whether it landed. Report the amount only when it did.
230
+ if (out.refundFailed === true)
231
+ return { refundFailed: true };
232
+ if (out.refundFailed === false)
233
+ return { refunded: out.refunded, refundFailed: false };
234
+ }
235
+ catch {
236
+ // One unavailable attempt must not hide matching logs at another height.
237
+ }
238
+ }
239
+ return {};
240
+ }
241
+ /** Best-effort shape from the on-chain envelope. Contract calls carry no ABI → raw. */
242
+ function inferResultShape(namespace, query) {
243
+ try {
244
+ switch (namespace) {
245
+ case read_state_1.READ_NAMESPACE.EVM: {
246
+ const { queryType } = (0, envelopes_1.decodeEvmQueryEnvelope)(query);
247
+ if (queryType === envelopes_1.EVM_QUERY_TYPE.ACCOUNT_BALANCE)
248
+ return { kind: 'uint256' };
249
+ if (queryType === envelopes_1.EVM_QUERY_TYPE.STORAGE_SLOT)
250
+ return { kind: 'bytes32' };
251
+ return { kind: 'raw' };
252
+ }
253
+ case read_state_1.READ_NAMESPACE.SVM: {
254
+ const { queryType } = (0, envelopes_1.decodeSvmQueryEnvelope)(query);
255
+ return queryType === envelopes_1.SVM_QUERY_TYPE.RAW_ACCOUNT_DATA ? { kind: 'raw' } : { kind: 'uint256' };
256
+ }
257
+ case read_state_1.READ_NAMESPACE.WEB2: {
258
+ const { extract } = (0, envelopes_1.decodeWeb2QueryEnvelope)(query);
259
+ const byNumber = Object.fromEntries(Object.entries(envelopes_1.WEB2_VALUE_TYPE).map(([name, n]) => [n, name]));
260
+ return {
261
+ kind: 'web2',
262
+ extract: extract.map((e) => ({ path: e.path, valueType: byNumber[e.valueType] ?? 'bytes', decimals: e.decimals })),
263
+ };
264
+ }
265
+ default:
266
+ return { kind: 'raw' };
267
+ }
268
+ }
269
+ catch {
270
+ return { kind: 'raw' };
271
+ }
272
+ }
273
+ function chainFromCaip2(caip2) {
274
+ if (caip2 === enums_1.CHAIN.WEB2)
275
+ return enums_1.CHAIN.WEB2;
276
+ return Object.values(enums_1.CHAIN).includes(caip2) ? caip2 : undefined;
277
+ }
278
+ function explorerTxUrl(network, txHash) {
279
+ const chain = network === enums_1.PUSH_NETWORK.MAINNET ? enums_1.CHAIN.PUSH_MAINNET : network === enums_1.PUSH_NETWORK.LOCALNET ? enums_1.CHAIN.PUSH_LOCALNET : enums_1.CHAIN.PUSH_TESTNET_DONUT;
280
+ const base = chain_1.PUSH_CHAIN_INFO[chain].explorerUrl ?? 'https://explorer.donut.push.org';
281
+ return `${base}/tx/${txHash}`;
282
+ }
283
+ // ---------------------------------------------------------------------------
284
+ // wait()
285
+ // ---------------------------------------------------------------------------
286
+ const STATUS_NAME = {
287
+ [read_state_types_1.UNIVERSAL_READ_STATUS.UNSPECIFIED]: 'UNSPECIFIED',
288
+ [read_state_types_1.UNIVERSAL_READ_STATUS.PENDING]: 'PENDING',
289
+ [read_state_types_1.UNIVERSAL_READ_STATUS.VOTING]: 'VOTING',
290
+ [read_state_types_1.UNIVERSAL_READ_STATUS.FULFILLED]: 'FULFILLED',
291
+ [read_state_types_1.UNIVERSAL_READ_STATUS.EXPIRED]: 'EXPIRED',
292
+ [read_state_types_1.UNIVERSAL_READ_STATUS.FAILED]: 'FAILED',
293
+ [read_state_types_1.UNIVERSAL_READ_STATUS.ABORTED]: 'ABORTED',
294
+ };
295
+ function statusName(status) {
296
+ return STATUS_NAME[status] ?? `UNKNOWN(${status})`;
297
+ }
298
+ /** Observation allowance after estimated expiry; not a guarantee of node settlement. */
299
+ const READ_TIMEOUT_MARGIN_MS = 10_000;
300
+ /** Default: remaining Push blocks plus observation margin, capped at 180 seconds. */
301
+ function defaultWaitTimeoutMs(currentPushHeight, expiryPushChainHeight) {
302
+ const blocks = expiryPushChainHeight > currentPushHeight ? Number(expiryPushChainHeight - currentPushHeight) : 0;
303
+ return Math.min(read_state_1.READ_TRACK_MAX_TIMEOUT_MS, blocks * read_state_1.PUSH_BLOCK_TIME_MS + READ_TIMEOUT_MARGIN_MS);
304
+ }
305
+ /**
306
+ * Poll until terminal. Resolves on EVERY terminal status (EXPIRED / FAILED / ABORTED
307
+ * are statuses, not throws — house convention); only a client timeout throws.
308
+ */
309
+ async function waitForRead(deps, initial, opts) {
310
+ const now = deps.now ?? Date.now;
311
+ const emit = deps.emit ?? (() => undefined);
312
+ const interval = pollInterval(opts);
313
+ const start = now();
314
+ let timeoutMs = opts.timeoutMs ?? read_state_1.READ_TRACK_MAX_TIMEOUT_MS;
315
+ // Resolve a fresh head for each wait/resume, only when a default is needed.
316
+ // Bound this RPC too: a stalled height lookup must not hang the promise forever.
317
+ if (opts.timeoutMs === undefined && !initial.isTerminal) {
318
+ let timer;
319
+ try {
320
+ const height = await Promise.race([
321
+ deps.pushClient.publicClient.getBlockNumber({ cacheTime: 0 }),
322
+ new Promise((_, reject) => {
323
+ timer = setTimeout(() => {
324
+ const elapsed = now() - start;
325
+ emit(progress_hook_types_1.PROGRESS_HOOK.READ_TX_199_03, initial.requestId, statusName(initial.status), elapsed);
326
+ reject(new errors_1.ReadTimeoutError(initial.status, elapsed, { requestId: initial.requestId, txHash: initial.txHash }));
327
+ }, read_state_1.READ_TRACK_MAX_TIMEOUT_MS);
328
+ }),
329
+ ]);
330
+ timeoutMs = Math.min(read_state_1.READ_TRACK_MAX_TIMEOUT_MS, now() - start + defaultWaitTimeoutMs(height, initial.request.spec.expiryPushChainHeight));
331
+ }
332
+ finally {
333
+ if (timer !== undefined)
334
+ clearTimeout(timer);
335
+ }
336
+ }
337
+ emit(progress_hook_types_1.PROGRESS_HOOK.READ_TX_104_02, initial.txHash, initial.requestId, initial.request.logIndex);
338
+ // Every snapshot taken from here on is built with the wait() options, so a
339
+ // resultShape given to wait() decodes even a record that is already terminal.
340
+ let current = initial;
341
+ const timeout = () => {
342
+ const elapsed = now() - start;
343
+ emit(progress_hook_types_1.PROGRESS_HOOK.READ_TX_199_03, current.requestId, statusName(current.status), elapsed);
344
+ throw new errors_1.ReadTimeoutError(current.status, elapsed, { requestId: current.requestId, txHash: current.txHash });
345
+ };
346
+ // A single deadline includes RPC/receipt latency. A missing snapshot must not
347
+ // start trackRead's separate initial-ingestion retry window.
348
+ const reload = async () => {
349
+ const remaining = timeoutMs - (now() - start);
350
+ if (remaining <= 0)
351
+ return timeout();
352
+ let timer;
353
+ try {
354
+ return await Promise.race([
355
+ (async () => {
356
+ const records = await fetchRecords(deps, { requestId: initial.requestId });
357
+ return records[0] ? buildResponse(deps, records[0], opts) : current;
358
+ })(),
359
+ new Promise((_, reject) => {
360
+ timer = setTimeout(() => {
361
+ try {
362
+ timeout();
363
+ }
364
+ catch (error) {
365
+ reject(error);
366
+ }
367
+ }, remaining);
368
+ }),
369
+ ]);
370
+ }
371
+ finally {
372
+ if (timer !== undefined)
373
+ clearTimeout(timer);
374
+ }
375
+ };
376
+ if (initial.isTerminal && opts.resultShape !== undefined)
377
+ current = await reload();
378
+ let announced;
379
+ for (;;) {
380
+ if (current.isTerminal) {
381
+ emitTerminal(emit, current);
382
+ return current;
383
+ }
384
+ if (current.status !== announced) {
385
+ announced = current.status;
386
+ if (current.status === read_state_types_1.UNIVERSAL_READ_STATUS.PENDING)
387
+ emit(progress_hook_types_1.PROGRESS_HOOK.READ_TX_105_01, current.requestId);
388
+ else if (current.status === read_state_types_1.UNIVERSAL_READ_STATUS.VOTING)
389
+ emit(progress_hook_types_1.PROGRESS_HOOK.READ_TX_105_02, current.requestId);
390
+ }
391
+ const elapsed = now() - start;
392
+ if (elapsed >= timeoutMs)
393
+ timeout();
394
+ await sleep(Math.min(interval, timeoutMs - elapsed));
395
+ current = await reload();
396
+ }
397
+ }
398
+ function emitTerminal(emit, r) {
399
+ if (r.status === read_state_types_1.UNIVERSAL_READ_STATUS.FULFILLED) {
400
+ if (r.callbackDelivered === true)
401
+ emit(progress_hook_types_1.PROGRESS_HOOK.READ_TX_106_02, r.requestId);
402
+ else if (r.callbackDelivered === false)
403
+ emit(progress_hook_types_1.PROGRESS_HOOK.READ_TX_106_03, r.requestId, r.callbackFailReason);
404
+ if (r.fees.burned !== undefined)
405
+ emit(progress_hook_types_1.PROGRESS_HOOK.READ_TX_106_04, r.requestId, r.fees.burned, r.fees.refunded ?? 0n);
406
+ if (r.fees.refundFailed === true)
407
+ emit(progress_hook_types_1.PROGRESS_HOOK.READ_TX_106_06, r.requestId, r.fees.refunded, r.request.refundTo);
408
+ else if (r.fees.refunded !== undefined)
409
+ emit(progress_hook_types_1.PROGRESS_HOOK.READ_TX_106_05, r.requestId, r.fees.refunded, r.request.refundTo);
410
+ emit(progress_hook_types_1.PROGRESS_HOOK.READ_TX_199_01, r.requestId, r.value, r.raw?.resultData ?? '0x', r.callbackDelivered);
411
+ return;
412
+ }
413
+ emit(progress_hook_types_1.PROGRESS_HOOK.READ_TX_199_02, r.requestId, statusName(r.status), r.raw?.errorCode, r.errorMsg, r.fees.refunded);
414
+ }
415
+ //# sourceMappingURL=read-tracker.js.map
@@ -0,0 +1,21 @@
1
+ import { type Address, type Hex, type PublicClient } from 'viem';
2
+ import { PUSH_NETWORK } from '../constants/enums';
3
+ import type { ReadCallback, ReadDestination, ReadQuery } from './read-state.types';
4
+ /** Versioned logical identity. Height, fee, expiry, refund, callback and HTTP timeout are excluded. */
5
+ export declare function computeReadQueryKey(destination: ReadDestination, query: ReadQuery): Hex;
6
+ export declare function getReadRegistryAddress(network: PUSH_NETWORK): Address;
7
+ export declare function resolveReadCallback(callback: ReadCallback | undefined, network: PUSH_NETWORK, queryKey: Hex): ReadCallback & {
8
+ gasLimit: bigint;
9
+ };
10
+ /** Raw registry storage only: presence does not prove successful consensus or freshness. */
11
+ export declare function getRegistryReadResult(client: Pick<PublicClient, 'readContract'>, network: PUSH_NETWORK, requestId: bigint): Promise<{
12
+ requestId: bigint;
13
+ resultData: `0x${string}`;
14
+ updatedAtBlock: bigint;
15
+ }>;
16
+ /** Keys are caller-supplied labels; verify the associated request before relying on its identity. */
17
+ export declare function getLatestRegistryReadResult(client: Pick<PublicClient, 'readContract'>, network: PUSH_NETWORK, reader: Address, queryKey: Hex): Promise<{
18
+ requestId: bigint;
19
+ resultData: `0x${string}`;
20
+ updatedAtBlock: bigint;
21
+ }>;
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.computeReadQueryKey = computeReadQueryKey;
4
+ exports.getReadRegistryAddress = getReadRegistryAddress;
5
+ exports.resolveReadCallback = resolveReadCallback;
6
+ exports.getRegistryReadResult = getRegistryReadResult;
7
+ exports.getLatestRegistryReadResult = getLatestRegistryReadResult;
8
+ const viem_1 = require("viem");
9
+ const read_state_1 = require("../constants/read-state");
10
+ const universalReadRegistry_evm_1 = require("../constants/abi/universalReadRegistry.evm");
11
+ const envelopes_1 = require("./envelopes");
12
+ const destination_1 = require("./destination");
13
+ const errors_1 = require("./errors");
14
+ /** Versioned logical identity. Height, fee, expiry, refund, callback and HTTP timeout are excluded. */
15
+ function computeReadQueryKey(destination, query) {
16
+ const dest = (0, destination_1.resolveDestination)(destination);
17
+ // Use fixed references in the existing canonical encoders; never hash live heights.
18
+ const encoded = (0, envelopes_1.encodeReadQuery)(destination, query.type === 'http' ? { ...query, timeoutMs: read_state_1.WEB2_DEFAULT_TIMEOUT_MS } : query, { blockNumber: 1n, minSlot: 0n });
19
+ return (0, viem_1.keccak256)((0, viem_1.encodeAbiParameters)([{ type: 'string' }, { type: 'string' }, { type: 'string' }, { type: 'bytes' }, { type: 'bytes' }], ['pushchain:universal-read:query:v1', dest.chainNamespace, dest.chainId, encoded.ownerBytes ?? '0x', encoded.encoded]));
20
+ }
21
+ function getReadRegistryAddress(network) {
22
+ const address = read_state_1.UNIVERSAL_READ_REGISTRY_ADDRESS[network];
23
+ if (!address)
24
+ throw new errors_1.ReadRegistryUnavailableError('read registry');
25
+ return address;
26
+ }
27
+ function resolveReadCallback(callback, network, queryKey) {
28
+ if (callback && 'request' in callback)
29
+ throw new errors_1.InvalidReadQueryError('callback.request was removed; use callback.abi, functionName and args');
30
+ if (callback?.target !== undefined) {
31
+ if (callback.gasLimit === undefined)
32
+ throw new errors_1.InvalidReadQueryError('callback.gasLimit is required for a custom target');
33
+ return { ...callback, gasLimit: callback.gasLimit };
34
+ }
35
+ if (callback?.abi !== undefined || callback?.functionName !== undefined || callback?.args !== undefined) {
36
+ throw new errors_1.InvalidReadQueryError('callback abi, functionName and args require callback.target');
37
+ }
38
+ return {
39
+ target: getReadRegistryAddress(network),
40
+ gasLimit: callback?.gasLimit ?? read_state_1.REGISTRY_CALLBACK_GAS,
41
+ abi: universalReadRegistry_evm_1.UNIVERSAL_READ_REGISTRY_EVM, functionName: 'read', args: (spec, gas) => [spec, queryKey, gas],
42
+ };
43
+ }
44
+ /** Raw registry storage only: presence does not prove successful consensus or freshness. */
45
+ function getRegistryReadResult(client, network, requestId) {
46
+ return client.readContract({ address: getReadRegistryAddress(network), abi: universalReadRegistry_evm_1.UNIVERSAL_READ_REGISTRY_EVM,
47
+ functionName: 'resultByRequestId', args: [requestId] });
48
+ }
49
+ /** Keys are caller-supplied labels; verify the associated request before relying on its identity. */
50
+ function getLatestRegistryReadResult(client, network, reader, queryKey) {
51
+ return client.readContract({ address: getReadRegistryAddress(network), abi: universalReadRegistry_evm_1.UNIVERSAL_READ_REGISTRY_EVM,
52
+ functionName: 'latestResult', args: [reader, queryKey] });
53
+ }
54
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1,16 @@
1
+ import { type Hex } from 'viem';
2
+ import type { DecodedReadResult, ReadResultShape } from './read-state.types';
3
+ /**
4
+ * Decode `resultData` using the shape recorded by the query that produced it.
5
+ *
6
+ * `resultData` is not self-describing. Shapes (from the validator executors):
7
+ * EVM AccountBalance / SVM LamportBalance / SVM SPLTokenAccount → abi.encode(uint256)
8
+ * EVM ContractCall → raw eth_call return bytes (decoded with the recorded ABI, else raw)
9
+ * EVM StorageSlot → raw 32 bytes
10
+ * SVM RawAccountData → raw account bytes
11
+ * Web2 → abi.encode(v1, v2, …) — a FLAT argument list, one per extract, in order
12
+ *
13
+ * Throws `ReadDecodeError` rather than mis-decoding. Do not call this for an ERROR
14
+ * result (empty bytes) — check `result.status` first.
15
+ */
16
+ export declare function decodeReadResult(resultData: Hex, shape: ReadResultShape): DecodedReadResult;
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.decodeReadResult = decodeReadResult;
4
+ const viem_1 = require("viem");
5
+ const errors_1 = require("./errors");
6
+ const WEB2_ABI_TYPE = {
7
+ uint256: 'uint256',
8
+ int256: 'int256',
9
+ bool: 'bool',
10
+ string: 'string',
11
+ bytes: 'bytes',
12
+ };
13
+ /**
14
+ * Decode `resultData` using the shape recorded by the query that produced it.
15
+ *
16
+ * `resultData` is not self-describing. Shapes (from the validator executors):
17
+ * EVM AccountBalance / SVM LamportBalance / SVM SPLTokenAccount → abi.encode(uint256)
18
+ * EVM ContractCall → raw eth_call return bytes (decoded with the recorded ABI, else raw)
19
+ * EVM StorageSlot → raw 32 bytes
20
+ * SVM RawAccountData → raw account bytes
21
+ * Web2 → abi.encode(v1, v2, …) — a FLAT argument list, one per extract, in order
22
+ *
23
+ * Throws `ReadDecodeError` rather than mis-decoding. Do not call this for an ERROR
24
+ * result (empty bytes) — check `result.status` first.
25
+ */
26
+ function decodeReadResult(resultData, shape) {
27
+ if (!(0, viem_1.isHex)(resultData))
28
+ throw new errors_1.ReadDecodeError('resultData is not hex');
29
+ const len = (0, viem_1.size)(resultData);
30
+ try {
31
+ switch (shape.kind) {
32
+ case 'uint256': {
33
+ if (len !== 32)
34
+ throw new errors_1.ReadDecodeError(`expected 32 bytes for uint256, got ${len}`);
35
+ const [value] = (0, viem_1.decodeAbiParameters)([{ type: 'uint256' }], resultData);
36
+ return { kind: 'uint256', value };
37
+ }
38
+ case 'bytes32': {
39
+ if (len !== 32)
40
+ throw new errors_1.ReadDecodeError(`expected 32 bytes for bytes32, got ${len}`);
41
+ return { kind: 'bytes32', value: resultData };
42
+ }
43
+ case 'raw':
44
+ return { kind: 'raw', value: resultData };
45
+ case 'evmCall': {
46
+ if (len === 0)
47
+ throw new errors_1.ReadDecodeError('empty resultData for a contract call');
48
+ const item = (0, viem_1.getAbiItem)({ abi: shape.abi, name: shape.functionName });
49
+ if (!item)
50
+ throw new errors_1.ReadDecodeError(`function not found in abi: ${shape.functionName}`);
51
+ const decoded = (0, viem_1.decodeFunctionResult)({ abi: shape.abi, functionName: shape.functionName, data: resultData });
52
+ return { kind: 'evmCall', value: decoded };
53
+ }
54
+ case 'web2': {
55
+ if (shape.extract.length === 0)
56
+ throw new errors_1.ReadDecodeError('web2 shape has no extracts');
57
+ if (len === 0)
58
+ throw new errors_1.ReadDecodeError('empty resultData for a web2 read');
59
+ const params = shape.extract.map((e) => ({ type: WEB2_ABI_TYPE[e.valueType] }));
60
+ const values = (0, viem_1.decodeAbiParameters)(params, resultData);
61
+ return { kind: 'web2', values };
62
+ }
63
+ default:
64
+ throw new errors_1.ReadDecodeError(`unknown result shape: ${shape.kind}`);
65
+ }
66
+ }
67
+ catch (err) {
68
+ if (err instanceof errors_1.ReadDecodeError)
69
+ throw err;
70
+ throw new errors_1.ReadDecodeError(`resultData does not match shape ${shape.kind}: ${err.message}`);
71
+ }
72
+ }
73
+ //# sourceMappingURL=result-decoder.js.map
@@ -0,0 +1,47 @@
1
+ import { type Abi, type Address, type Hex } from 'viem';
2
+ import { type PreflightDeps } from './preflight';
3
+ import type { BuildReadSpecParams, PreparedRead, ReadPreflight, ReadSpec, ReadSpecTuple, SimulateReadResult } from './read-state.types';
4
+ import type { ReadHookEmitter } from './read-tracker';
5
+ export interface PrepareReadDeps extends PreflightDeps {
6
+ emit?: ReadHookEmitter;
7
+ /** The signer's Push-side account (UEA or EOA). Used when `refundTo` is not given. */
8
+ defaultRefundTo?: Address;
9
+ }
10
+ export declare function encodeSpec(spec: ReadSpec): Hex;
11
+ /**
12
+ * Assemble a `PreparedRead` from an already-fetched preflight. Pure — no network.
13
+ * Use directly when preparing several reads from one preflight.
14
+ */
15
+ export declare function buildReadSpecFromPreflight(preflight: ReadPreflight, params: BuildReadSpecParams, defaults?: {
16
+ refundTo?: Address;
17
+ }): PreparedRead;
18
+ /**
19
+ * Preflight + build. Adds the `refundTo` advisory: a contract recipient that is not a
20
+ * UEA must have a payable receive() or the refund is forfeited to the admin rescue
21
+ * pool (verified live). UEAs (UEA_EVM / UEA_SVM) always have one.
22
+ */
23
+ export declare function prepareRead(deps: PrepareReadDeps, params: BuildReadSpecParams): Promise<PreparedRead>;
24
+ /**
25
+ * Splice the prepared spec into the app's own entrypoint.
26
+ * Default argument order is `(spec, callbackGasLimit)`; pass `args` to remap.
27
+ */
28
+ export declare function toCallData(prepared: PreparedRead, target: {
29
+ abi: Abi;
30
+ functionName: string;
31
+ args?: (spec: ReadSpecTuple, callbackGasLimit: bigint) => readonly unknown[];
32
+ }): {
33
+ data: Hex;
34
+ value: bigint;
35
+ };
36
+ /**
37
+ * eth_call `requestExternalReadSelf` with `from` = the app contract, so validation
38
+ * reverts surface before broadcast, decoded into the contract's custom errors.
39
+ * Only meaningful for the app-contract persona: `callbackTarget` becomes `msg.sender`.
40
+ */
41
+ export declare function simulateRead(deps: PreflightDeps, prepared: PreparedRead, opts: {
42
+ appContract: Address;
43
+ callbackSelector: Hex;
44
+ staleAfterMs?: number;
45
+ }): Promise<SimulateReadResult>;
46
+ /** Fresh validation only: never rebuild or mutate the prepared request. */
47
+ export declare function revalidateRead(deps: PreflightDeps, prepared: PreparedRead): Promise<void>;