@pushchain/core 6.0.24 → 6.0.26
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.
- package/CHANGELOG.md +150 -0
- package/package.json +1 -1
- package/src/lib/constants/abi/index.d.ts +1 -0
- package/src/lib/constants/abi/index.js +4 -1
- package/src/lib/constants/abi/prc20.evm.d.ts +32 -0
- package/src/lib/constants/abi/prc20.evm.js +20 -0
- package/src/lib/constants/abi/uea-factory.d.ts +31 -0
- package/src/lib/constants/abi/uea-factory.js +19 -0
- package/src/lib/constants/abi/universalCallback.evm.d.ts +561 -0
- package/src/lib/constants/abi/universalCallback.evm.js +247 -0
- package/src/lib/constants/abi/universalGatewayV0.json +1 -1
- package/src/lib/constants/abi/universalReadRegistry.evm.d.ts +247 -0
- package/src/lib/constants/abi/universalReadRegistry.evm.js +38 -0
- package/src/lib/constants/chain.d.ts +12 -0
- package/src/lib/constants/chain.js +24 -2
- package/src/lib/constants/enums.d.ts +7 -0
- package/src/lib/constants/enums.js +11 -0
- package/src/lib/constants/index.d.ts +54 -0
- package/src/lib/constants/index.js +38 -8
- package/src/lib/constants/read-state.d.ts +57 -0
- package/src/lib/constants/read-state.js +63 -0
- package/src/lib/generated/ucallback/v1/index.d.ts +2 -0
- package/src/lib/generated/ucallback/v1/index.js +6 -0
- package/src/lib/generated/ucallback/v1/query.d.ts +47 -0
- package/src/lib/generated/ucallback/v1/query.js +131 -0
- package/src/lib/generated/ucallback/v1/types.d.ts +99 -0
- package/src/lib/generated/ucallback/v1/types.js +366 -0
- package/src/lib/generated/uexecutor/v1/query.d.ts +29 -0
- package/src/lib/generated/uexecutor/v1/query.js +98 -1
- package/src/lib/index.d.ts +9 -0
- package/src/lib/index.js +38 -1
- package/src/lib/orchestrator/internals/cascade.js +5 -2
- package/src/lib/orchestrator/internals/errors.d.ts +6 -0
- package/src/lib/orchestrator/internals/errors.js +13 -1
- package/src/lib/orchestrator/internals/index.d.ts +1 -0
- package/src/lib/orchestrator/internals/index.js +6 -1
- package/src/lib/orchestrator/internals/push-chain-tx.js +81 -61
- package/src/lib/orchestrator/internals/read-state.d.ts +24 -0
- package/src/lib/orchestrator/internals/read-state.js +47 -0
- package/src/lib/orchestrator/internals/route-handlers.js +6 -1
- package/src/lib/orchestrator/internals/svm-helpers.js +24 -0
- package/src/lib/orchestrator/orchestrator.d.ts +26 -0
- package/src/lib/orchestrator/orchestrator.js +22 -0
- package/src/lib/orchestrator/orchestrator.types.d.ts +2 -0
- package/src/lib/orchestrator/route-detector.js +4 -0
- package/src/lib/progress-hook/progress-hook.js +292 -0
- package/src/lib/progress-hook/progress-hook.types.d.ts +35 -0
- package/src/lib/progress-hook/progress-hook.types.js +37 -0
- package/src/lib/push-chain/push-chain.d.ts +71 -0
- package/src/lib/push-chain/push-chain.js +28 -0
- package/src/lib/push-client/push-client.d.ts +42 -2
- package/src/lib/push-client/push-client.js +152 -13
- package/src/lib/read-state/budget.d.ts +16 -0
- package/src/lib/read-state/budget.js +32 -0
- package/src/lib/read-state/destination.d.ts +12 -0
- package/src/lib/read-state/destination.js +51 -0
- package/src/lib/read-state/envelopes/evm.d.ts +24 -0
- package/src/lib/read-state/envelopes/evm.js +147 -0
- package/src/lib/read-state/envelopes/index.d.ts +12 -0
- package/src/lib/read-state/envelopes/index.js +45 -0
- package/src/lib/read-state/envelopes/svm.d.ts +32 -0
- package/src/lib/read-state/envelopes/svm.js +117 -0
- package/src/lib/read-state/envelopes/web2.d.ts +29 -0
- package/src/lib/read-state/envelopes/web2.js +149 -0
- package/src/lib/read-state/errors.d.ts +70 -0
- package/src/lib/read-state/errors.js +116 -0
- package/src/lib/read-state/index.d.ts +13 -0
- package/src/lib/read-state/index.js +38 -0
- package/src/lib/read-state/preflight.d.ts +21 -0
- package/src/lib/read-state/preflight.js +70 -0
- package/src/lib/read-state/read-events.d.ts +27 -0
- package/src/lib/read-state/read-events.js +135 -0
- package/src/lib/read-state/read-executor.d.ts +18 -0
- package/src/lib/read-state/read-executor.js +216 -0
- package/src/lib/read-state/read-params.d.ts +250 -0
- package/src/lib/read-state/read-params.js +164 -0
- package/src/lib/read-state/read-state.types.d.ts +458 -0
- package/src/lib/read-state/read-state.types.js +72 -0
- package/src/lib/read-state/read-tracker.d.ts +55 -0
- package/src/lib/read-state/read-tracker.js +481 -0
- package/src/lib/read-state/registry.d.ts +21 -0
- package/src/lib/read-state/registry.js +53 -0
- package/src/lib/read-state/result-decoder.d.ts +16 -0
- package/src/lib/read-state/result-decoder.js +79 -0
- package/src/lib/read-state/spec-builder.d.ts +47 -0
- package/src/lib/read-state/spec-builder.js +251 -0
- package/src/lib/read-state/svm-account.d.ts +20 -0
- package/src/lib/read-state/svm-account.js +114 -0
- package/src/lib/read-state/svm-pda.d.ts +13 -0
- package/src/lib/read-state/svm-pda.js +147 -0
- package/src/lib/read-state/validate.d.ts +22 -0
- package/src/lib/read-state/validate.js +80 -0
- package/src/lib/universal-tx-detector/child-inbounds.d.ts +3 -1
- package/src/lib/universal-tx-detector/child-inbounds.js +4 -5
- package/src/lib/universal-tx-detector/detector-svm.js +29 -20
- package/src/lib/universal-tx-detector/svm-events.d.ts +17 -5
- package/src/lib/universal-tx-detector/svm-events.js +81 -6
- package/src/lib/vm-client/evm-client.js +27 -10
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TERMINAL_READ_STATUSES = exports.CONTRACT_REQUEST_STATUS = exports.READ_OUTCOME = exports.READ_ERROR_CODE = exports.READ_STATUS = exports.UNIVERSAL_READ_STATUS = void 0;
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// Status enums (mirror ucallback.v1 protos; re-exported for the public surface)
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
var UNIVERSAL_READ_STATUS;
|
|
8
|
+
(function (UNIVERSAL_READ_STATUS) {
|
|
9
|
+
UNIVERSAL_READ_STATUS[UNIVERSAL_READ_STATUS["UNSPECIFIED"] = 0] = "UNSPECIFIED";
|
|
10
|
+
UNIVERSAL_READ_STATUS[UNIVERSAL_READ_STATUS["PENDING"] = 1] = "PENDING";
|
|
11
|
+
UNIVERSAL_READ_STATUS[UNIVERSAL_READ_STATUS["VOTING"] = 2] = "VOTING";
|
|
12
|
+
/** fulfil tx succeeded — check `callbackDelivered` before trusting the result */
|
|
13
|
+
UNIVERSAL_READ_STATUS[UNIVERSAL_READ_STATUS["FULFILLED"] = 3] = "FULFILLED";
|
|
14
|
+
UNIVERSAL_READ_STATUS[UNIVERSAL_READ_STATUS["EXPIRED"] = 4] = "EXPIRED";
|
|
15
|
+
/** contract had already settled the request another way */
|
|
16
|
+
UNIVERSAL_READ_STATUS[UNIVERSAL_READ_STATUS["FAILED"] = 5] = "FAILED";
|
|
17
|
+
UNIVERSAL_READ_STATUS[UNIVERSAL_READ_STATUS["ABORTED"] = 6] = "ABORTED";
|
|
18
|
+
})(UNIVERSAL_READ_STATUS || (exports.UNIVERSAL_READ_STATUS = UNIVERSAL_READ_STATUS = {}));
|
|
19
|
+
var READ_STATUS;
|
|
20
|
+
(function (READ_STATUS) {
|
|
21
|
+
READ_STATUS[READ_STATUS["UNSPECIFIED"] = 0] = "UNSPECIFIED";
|
|
22
|
+
READ_STATUS[READ_STATUS["SUCCESS"] = 1] = "SUCCESS";
|
|
23
|
+
READ_STATUS[READ_STATUS["ERROR"] = 2] = "ERROR";
|
|
24
|
+
})(READ_STATUS || (exports.READ_STATUS = READ_STATUS = {}));
|
|
25
|
+
var READ_ERROR_CODE;
|
|
26
|
+
(function (READ_ERROR_CODE) {
|
|
27
|
+
READ_ERROR_CODE[READ_ERROR_CODE["UNSPECIFIED"] = 0] = "UNSPECIFIED";
|
|
28
|
+
READ_ERROR_CODE[READ_ERROR_CODE["INVALID_QUERY"] = 1] = "INVALID_QUERY";
|
|
29
|
+
READ_ERROR_CODE[READ_ERROR_CODE["UNSUPPORTED"] = 2] = "UNSUPPORTED";
|
|
30
|
+
READ_ERROR_CODE[READ_ERROR_CODE["REVERTED"] = 3] = "REVERTED";
|
|
31
|
+
READ_ERROR_CODE[READ_ERROR_CODE["NOT_FOUND"] = 4] = "NOT_FOUND";
|
|
32
|
+
READ_ERROR_CODE[READ_ERROR_CODE["INVALID_RESULT"] = 5] = "INVALID_RESULT";
|
|
33
|
+
READ_ERROR_CODE[READ_ERROR_CODE["REJECTED"] = 6] = "REJECTED";
|
|
34
|
+
})(READ_ERROR_CODE || (exports.READ_ERROR_CODE = READ_ERROR_CODE = {}));
|
|
35
|
+
/**
|
|
36
|
+
* SDK-derived single outcome. FULFILLED on the node only means the fulfil tx
|
|
37
|
+
* landed; this separates the ways a FULFILLED read can still not have worked.
|
|
38
|
+
*/
|
|
39
|
+
var READ_OUTCOME;
|
|
40
|
+
(function (READ_OUTCOME) {
|
|
41
|
+
/** Not terminal yet. */
|
|
42
|
+
READ_OUTCOME["PENDING"] = "PENDING";
|
|
43
|
+
/** Source SUCCESS, callback delivered, result decoded (or no shape to decode with). */
|
|
44
|
+
READ_OUTCOME["SUCCESS"] = "SUCCESS";
|
|
45
|
+
/** FULFILLED, but the destination returned an error (`raw.errorCode`). */
|
|
46
|
+
READ_OUTCOME["SOURCE_ERROR"] = "SOURCE_ERROR";
|
|
47
|
+
/** FULFILLED, but the receiver callback reverted or ran out of gas (`callbackFailReason`). */
|
|
48
|
+
READ_OUTCOME["CALLBACK_FAILED"] = "CALLBACK_FAILED";
|
|
49
|
+
/** Delivered, but `resultData` did not match the expected shape (`decodeError`). */
|
|
50
|
+
READ_OUTCOME["DECODE_FAILED"] = "DECODE_FAILED";
|
|
51
|
+
READ_OUTCOME["EXPIRED"] = "EXPIRED";
|
|
52
|
+
READ_OUTCOME["FAILED"] = "FAILED";
|
|
53
|
+
READ_OUTCOME["ABORTED"] = "ABORTED";
|
|
54
|
+
/** FULFILLED, but the fulfil receipt was unavailable, so delivery cannot be confirmed. */
|
|
55
|
+
READ_OUTCOME["UNKNOWN"] = "UNKNOWN";
|
|
56
|
+
})(READ_OUTCOME || (exports.READ_OUTCOME = READ_OUTCOME = {}));
|
|
57
|
+
/** On-chain lifecycle (`RequestStatus` in ReadTypes.sol). */
|
|
58
|
+
var CONTRACT_REQUEST_STATUS;
|
|
59
|
+
(function (CONTRACT_REQUEST_STATUS) {
|
|
60
|
+
CONTRACT_REQUEST_STATUS[CONTRACT_REQUEST_STATUS["NONE"] = 0] = "NONE";
|
|
61
|
+
CONTRACT_REQUEST_STATUS[CONTRACT_REQUEST_STATUS["PENDING"] = 1] = "PENDING";
|
|
62
|
+
CONTRACT_REQUEST_STATUS[CONTRACT_REQUEST_STATUS["EXECUTED"] = 2] = "EXECUTED";
|
|
63
|
+
CONTRACT_REQUEST_STATUS[CONTRACT_REQUEST_STATUS["SETTLED"] = 3] = "SETTLED";
|
|
64
|
+
CONTRACT_REQUEST_STATUS[CONTRACT_REQUEST_STATUS["EXPIRED"] = 4] = "EXPIRED";
|
|
65
|
+
})(CONTRACT_REQUEST_STATUS || (exports.CONTRACT_REQUEST_STATUS = CONTRACT_REQUEST_STATUS = {}));
|
|
66
|
+
exports.TERMINAL_READ_STATUSES = new Set([
|
|
67
|
+
UNIVERSAL_READ_STATUS.FULFILLED,
|
|
68
|
+
UNIVERSAL_READ_STATUS.EXPIRED,
|
|
69
|
+
UNIVERSAL_READ_STATUS.FAILED,
|
|
70
|
+
UNIVERSAL_READ_STATUS.ABORTED,
|
|
71
|
+
]);
|
|
72
|
+
//# sourceMappingURL=read-state.types.js.map
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* trackRead / wait / refresh — resume a read from its Push tx hash or requestId
|
|
3
|
+
* and follow it to a terminal status.
|
|
4
|
+
*
|
|
5
|
+
* Source of truth is the node's `ucallback.v1.Query` record. What the node
|
|
6
|
+
* does NOT tell us is parsed from the UniversalCallback logs of the Push txs it
|
|
7
|
+
* lists in `pc_tx` (fulfil → ReadFulfilled | CallbackFailed; settle →
|
|
8
|
+
* CallbackGasReported + RefundSent | RefundFailed).
|
|
9
|
+
*
|
|
10
|
+
* Two rules verified live on Donut (2026-09-09):
|
|
11
|
+
* - FULFILLED does not mean delivered: the node sets it even when the app
|
|
12
|
+
* callback reverted. `callbackDelivered` is the real answer (I4).
|
|
13
|
+
* - EXPIRED has no EVM-indexed tx / receipt / logs (EndBlocker); never fetch
|
|
14
|
+
* its `pc_tx`. The refund is confirmed from the EndBlock `tx_log` events in the
|
|
15
|
+
* Cosmos block results instead (RefundSent / RefundFailed); if that lookup fails
|
|
16
|
+
* the refund fields stay undefined rather than guessed.
|
|
17
|
+
*/
|
|
18
|
+
import { type Hex } from 'viem';
|
|
19
|
+
import { PUSH_NETWORK } from '../constants/enums';
|
|
20
|
+
import type { PushClient } from '../push-client/push-client';
|
|
21
|
+
import { READ_OUTCOME, READ_STATUS, UNIVERSAL_READ_STATUS, type ReadLifecycleOptions, type ReadResultShape, type UniversalReadResponse } from './read-state.types';
|
|
22
|
+
export type ReadHookEmitter = (hookId: string, ...args: unknown[]) => void;
|
|
23
|
+
export interface TrackReadDeps {
|
|
24
|
+
pushClient: Pick<PushClient, 'getUniversalRead' | 'getReadsByTx' | 'getTransactionReceiptWithArchiveFallback' | 'getBlockResultEvents'> & {
|
|
25
|
+
publicClient: Pick<PushClient['publicClient'], 'getBlockNumber'>;
|
|
26
|
+
};
|
|
27
|
+
pushNetwork: PUSH_NETWORK;
|
|
28
|
+
/** Progress events. Wire to `fireProgressHook(ctx, …)` in the orchestrator. */
|
|
29
|
+
emit?: ReadHookEmitter;
|
|
30
|
+
/** Override for tests. */
|
|
31
|
+
now?: () => number;
|
|
32
|
+
}
|
|
33
|
+
export declare function trackRead(deps: TrackReadDeps, ref: {
|
|
34
|
+
txHash: Hex;
|
|
35
|
+
}, opts?: ReadLifecycleOptions): Promise<UniversalReadResponse[]>;
|
|
36
|
+
export declare function trackRead(deps: TrackReadDeps, ref: {
|
|
37
|
+
requestId: Hex | bigint;
|
|
38
|
+
}, opts?: ReadLifecycleOptions): Promise<UniversalReadResponse>;
|
|
39
|
+
/** Normalise a bigint / hex requestId to the node's lowercase 32-byte key. */
|
|
40
|
+
export declare function toRequestIdHex(id: Hex | bigint): Hex;
|
|
41
|
+
/**
|
|
42
|
+
* One answer to "did it work". Order matters: a source error is delivered to the
|
|
43
|
+
* callback as an ERROR result, so it is reported before any callback outcome.
|
|
44
|
+
*/
|
|
45
|
+
export declare function deriveReadOutcome(status: UNIVERSAL_READ_STATUS, rawStatus: READ_STATUS | undefined, callbackDelivered: boolean | undefined, decodeError: string | undefined): READ_OUTCOME;
|
|
46
|
+
/** Best-effort shape from the on-chain envelope. Contract calls carry no ABI → raw. */
|
|
47
|
+
export declare function inferResultShape(namespace: string, query: Hex): ReadResultShape;
|
|
48
|
+
export declare function statusName(status: number): string;
|
|
49
|
+
/** Default: remaining Push blocks plus observation margin, capped at 180 seconds. */
|
|
50
|
+
export declare function defaultWaitTimeoutMs(currentPushHeight: bigint, expiryPushChainHeight: bigint): number;
|
|
51
|
+
/**
|
|
52
|
+
* Poll until terminal. Resolves on EVERY terminal status (EXPIRED / FAILED / ABORTED
|
|
53
|
+
* are statuses, not throws — house convention); only a client timeout throws.
|
|
54
|
+
*/
|
|
55
|
+
export declare function waitForRead(deps: TrackReadDeps, initial: UniversalReadResponse, opts: ReadLifecycleOptions): Promise<UniversalReadResponse>;
|
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.trackRead = trackRead;
|
|
4
|
+
exports.toRequestIdHex = toRequestIdHex;
|
|
5
|
+
exports.deriveReadOutcome = deriveReadOutcome;
|
|
6
|
+
exports.inferResultShape = inferResultShape;
|
|
7
|
+
exports.statusName = statusName;
|
|
8
|
+
exports.defaultWaitTimeoutMs = defaultWaitTimeoutMs;
|
|
9
|
+
exports.waitForRead = waitForRead;
|
|
10
|
+
/**
|
|
11
|
+
* trackRead / wait / refresh — resume a read from its Push tx hash or requestId
|
|
12
|
+
* and follow it to a terminal status.
|
|
13
|
+
*
|
|
14
|
+
* Source of truth is the node's `ucallback.v1.Query` record. What the node
|
|
15
|
+
* does NOT tell us is parsed from the UniversalCallback logs of the Push txs it
|
|
16
|
+
* lists in `pc_tx` (fulfil → ReadFulfilled | CallbackFailed; settle →
|
|
17
|
+
* CallbackGasReported + RefundSent | RefundFailed).
|
|
18
|
+
*
|
|
19
|
+
* Two rules verified live on Donut (2026-09-09):
|
|
20
|
+
* - FULFILLED does not mean delivered: the node sets it even when the app
|
|
21
|
+
* callback reverted. `callbackDelivered` is the real answer (I4).
|
|
22
|
+
* - EXPIRED has no EVM-indexed tx / receipt / logs (EndBlocker); never fetch
|
|
23
|
+
* its `pc_tx`. The refund is confirmed from the EndBlock `tx_log` events in the
|
|
24
|
+
* Cosmos block results instead (RefundSent / RefundFailed); if that lookup fails
|
|
25
|
+
* the refund fields stay undefined rather than guessed.
|
|
26
|
+
*/
|
|
27
|
+
const viem_1 = require("viem");
|
|
28
|
+
const chain_1 = require("../constants/chain");
|
|
29
|
+
const enums_1 = require("../constants/enums");
|
|
30
|
+
const read_state_1 = require("../constants/read-state");
|
|
31
|
+
const progress_hook_types_1 = require("../progress-hook/progress-hook.types");
|
|
32
|
+
const destination_1 = require("./destination");
|
|
33
|
+
const envelopes_1 = require("./envelopes");
|
|
34
|
+
const errors_1 = require("./errors");
|
|
35
|
+
const read_events_1 = require("./read-events");
|
|
36
|
+
const read_state_types_1 = require("./read-state.types");
|
|
37
|
+
const result_decoder_1 = require("./result-decoder");
|
|
38
|
+
async function trackRead(deps, ref, opts = {}) {
|
|
39
|
+
if ('txHash' in ref) {
|
|
40
|
+
const records = await lookupWithRetry(deps, ref, opts);
|
|
41
|
+
return Promise.all(records.map((r) => buildResponse(deps, r, opts)));
|
|
42
|
+
}
|
|
43
|
+
const [record] = await lookupWithRetry(deps, ref, opts);
|
|
44
|
+
return buildResponse(deps, record, opts);
|
|
45
|
+
}
|
|
46
|
+
/** Normalise a bigint / hex requestId to the node's lowercase 32-byte key. */
|
|
47
|
+
function toRequestIdHex(id) {
|
|
48
|
+
const hex = typeof id === 'bigint' ? id.toString(16) : id.replace(/^0x/i, '');
|
|
49
|
+
return `0x${hex.toLowerCase().padStart(64, '0')}`;
|
|
50
|
+
}
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
// Lookup
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
async function fetchRecords(deps, ref) {
|
|
55
|
+
if ('txHash' in ref) {
|
|
56
|
+
const { reads } = await deps.pushClient.getReadsByTx(ref.txHash);
|
|
57
|
+
return reads;
|
|
58
|
+
}
|
|
59
|
+
const { read } = await deps.pushClient.getUniversalRead(toRequestIdHex(ref.requestId));
|
|
60
|
+
return read ? [read] : [];
|
|
61
|
+
}
|
|
62
|
+
/** A request is indexed when its block is processed — briefly retry a miss. */
|
|
63
|
+
async function lookupWithRetry(deps, ref, opts) {
|
|
64
|
+
const now = deps.now ?? Date.now;
|
|
65
|
+
const emit = deps.emit ?? (() => undefined);
|
|
66
|
+
const interval = pollInterval(opts);
|
|
67
|
+
const start = now();
|
|
68
|
+
const hookRef = 'txHash' in ref ? { txHash: ref.txHash } : { requestId: toRequestIdHex(ref.requestId) };
|
|
69
|
+
emit(progress_hook_types_1.PROGRESS_HOOK.READ_TX_104_03, hookRef);
|
|
70
|
+
for (;;) {
|
|
71
|
+
const records = await fetchRecords(deps, ref);
|
|
72
|
+
if (records.length > 0) {
|
|
73
|
+
for (const r of records)
|
|
74
|
+
emit(progress_hook_types_1.PROGRESS_HOOK.READ_TX_104_04, toRequestIdHex(r.id), statusName(r.status));
|
|
75
|
+
return records;
|
|
76
|
+
}
|
|
77
|
+
if (now() - start + interval > read_state_1.READ_TRACK_LOOKUP_TIMEOUT_MS)
|
|
78
|
+
break;
|
|
79
|
+
await sleep(interval);
|
|
80
|
+
}
|
|
81
|
+
emit(progress_hook_types_1.PROGRESS_HOOK.READ_TX_104_05, hookRef, now() - start);
|
|
82
|
+
const label = 'txHash' in ref ? ref.txHash : toRequestIdHex(ref.requestId);
|
|
83
|
+
throw new errors_1.ReadNotFoundError(label, 'txHash' in ref ? { txHash: ref.txHash } : { requestId: label });
|
|
84
|
+
}
|
|
85
|
+
function pollInterval(opts) {
|
|
86
|
+
return Math.max(read_state_1.READ_TRACK_MIN_POLL_INTERVAL_MS, opts.pollingIntervalMs ?? read_state_1.READ_TRACK_POLL_INTERVAL_MS);
|
|
87
|
+
}
|
|
88
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
89
|
+
// ---------------------------------------------------------------------------
|
|
90
|
+
// Record → response
|
|
91
|
+
// ---------------------------------------------------------------------------
|
|
92
|
+
async function buildResponse(deps, record, opts) {
|
|
93
|
+
const req = record.request;
|
|
94
|
+
if (!req)
|
|
95
|
+
throw new errors_1.ReadStateError('READ_RECORD_INCOMPLETE', 'node record has no request', { requestId: record.id });
|
|
96
|
+
const requestId = toRequestIdHex(record.id);
|
|
97
|
+
const status = record.status;
|
|
98
|
+
const isTerminal = read_state_types_1.TERMINAL_READ_STATUSES.has(status);
|
|
99
|
+
const destination = (0, destination_1.resolveDestination)({ chain: req.destinationChain });
|
|
100
|
+
const callbackBudget = BigInt(req.callbackBudget || '0');
|
|
101
|
+
const spec = {
|
|
102
|
+
account: { chainNamespace: destination.chainNamespace, chainId: destination.chainId, owner: (0, viem_1.bytesToHex)(req.owner) },
|
|
103
|
+
query: (0, viem_1.bytesToHex)(req.query),
|
|
104
|
+
minConfirmations: req.minConfirmations,
|
|
105
|
+
blockNumber: BigInt(req.destinationBlockHeight),
|
|
106
|
+
expiryPushChainHeight: BigInt(req.expiryBlockHeight),
|
|
107
|
+
maxFee: BigInt(req.maxFee || '0'),
|
|
108
|
+
revertRecipient: req.revertRecipient,
|
|
109
|
+
};
|
|
110
|
+
const fees = {
|
|
111
|
+
paid: BigInt(req.feesDeposited || '0'),
|
|
112
|
+
protocolFee: BigInt(req.protocolFee || '0'),
|
|
113
|
+
callbackBudget,
|
|
114
|
+
};
|
|
115
|
+
// Settlement facts live in the logs of the node's own txs.
|
|
116
|
+
let outcome = {};
|
|
117
|
+
if (status === read_state_types_1.UNIVERSAL_READ_STATUS.EXPIRED) {
|
|
118
|
+
// EndBlocker expiry: no receipt exists. The contract pushes the full budget to
|
|
119
|
+
// refundTo, but a rejecting recipient does not prevent EXPIRED — so read the
|
|
120
|
+
// RefundSent / RefundFailed logs from the block results instead of assuming.
|
|
121
|
+
outcome = await collectExpiryOutcome(deps, record, requestId);
|
|
122
|
+
if (outcome.refunded !== undefined)
|
|
123
|
+
fees.refunded = outcome.refunded;
|
|
124
|
+
if (outcome.refundFailed !== undefined)
|
|
125
|
+
fees.refundFailed = outcome.refundFailed;
|
|
126
|
+
}
|
|
127
|
+
else if (status === read_state_types_1.UNIVERSAL_READ_STATUS.FULFILLED || status === read_state_types_1.UNIVERSAL_READ_STATUS.FAILED) {
|
|
128
|
+
outcome = await collectOutcome(deps, record, requestId);
|
|
129
|
+
if (outcome.burned !== undefined)
|
|
130
|
+
fees.burned = outcome.burned;
|
|
131
|
+
if (outcome.refunded !== undefined)
|
|
132
|
+
fees.refunded = outcome.refunded;
|
|
133
|
+
if (outcome.refundFailed !== undefined)
|
|
134
|
+
fees.refundFailed = outcome.refundFailed;
|
|
135
|
+
}
|
|
136
|
+
const raw = record.result
|
|
137
|
+
? { status: record.result.status, resultData: (0, viem_1.bytesToHex)(record.result.resultData), errorCode: record.result.errorCode }
|
|
138
|
+
: null;
|
|
139
|
+
const callbackDelivered = status === read_state_types_1.UNIVERSAL_READ_STATUS.FULFILLED ? outcome.delivered : undefined;
|
|
140
|
+
let decoded;
|
|
141
|
+
let decodeError;
|
|
142
|
+
let value;
|
|
143
|
+
if (status === read_state_types_1.UNIVERSAL_READ_STATUS.FULFILLED && callbackDelivered && raw?.status === read_state_types_1.READ_STATUS.SUCCESS) {
|
|
144
|
+
const shape = opts.resultShape ?? inferResultShape(destination.namespace, spec.query);
|
|
145
|
+
try {
|
|
146
|
+
decoded = (0, result_decoder_1.decodeReadResult)(raw.resultData, shape);
|
|
147
|
+
value = 'values' in decoded ? decoded.values : decoded.value;
|
|
148
|
+
}
|
|
149
|
+
catch (e) {
|
|
150
|
+
decodeError = e instanceof errors_1.ReadDecodeError ? e.message : String(e);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
const txHash = req.requestedTxHash.toLowerCase();
|
|
154
|
+
const response = {
|
|
155
|
+
requestIdUint: BigInt(requestId),
|
|
156
|
+
outcome: deriveReadOutcome(status, raw?.status, callbackDelivered, decodeError),
|
|
157
|
+
requestId,
|
|
158
|
+
txHash,
|
|
159
|
+
destination,
|
|
160
|
+
chain: chainFromCaip2(destination.caip2) ?? destination.caip2,
|
|
161
|
+
status,
|
|
162
|
+
isTerminal,
|
|
163
|
+
callbackDelivered,
|
|
164
|
+
callbackFailReason: outcome.failReason,
|
|
165
|
+
value,
|
|
166
|
+
decoded,
|
|
167
|
+
decodeError,
|
|
168
|
+
raw,
|
|
169
|
+
errorMsg: record.errorMsg,
|
|
170
|
+
fees,
|
|
171
|
+
request: {
|
|
172
|
+
spec,
|
|
173
|
+
callbackTarget: req.callbackTarget,
|
|
174
|
+
originalFunder: req.originalFunder,
|
|
175
|
+
refundTo: req.revertRecipient,
|
|
176
|
+
callbackGasLimit: BigInt(req.callbackGasLimit),
|
|
177
|
+
logIndex: req.requestedLogIndex,
|
|
178
|
+
createdAtHeight: BigInt(req.createdAtHeight),
|
|
179
|
+
},
|
|
180
|
+
pcTx: record.pcTx.map((t) => ({ txHash: t.txHash, blockHeight: t.blockHeight, status: t.status, errorMsg: t.errorMsg })),
|
|
181
|
+
explorerUrl: explorerTxUrl(deps.pushNetwork, txHash),
|
|
182
|
+
refresh: () => trackRead(deps, { requestId }, opts),
|
|
183
|
+
wait: (waitOpts) => waitForRead(deps, response, { ...opts, ...waitOpts }),
|
|
184
|
+
};
|
|
185
|
+
return response;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* One answer to "did it work". Order matters: a source error is delivered to the
|
|
189
|
+
* callback as an ERROR result, so it is reported before any callback outcome.
|
|
190
|
+
*/
|
|
191
|
+
function deriveReadOutcome(status, rawStatus, callbackDelivered, decodeError) {
|
|
192
|
+
switch (status) {
|
|
193
|
+
case read_state_types_1.UNIVERSAL_READ_STATUS.EXPIRED: return read_state_types_1.READ_OUTCOME.EXPIRED;
|
|
194
|
+
case read_state_types_1.UNIVERSAL_READ_STATUS.FAILED: return read_state_types_1.READ_OUTCOME.FAILED;
|
|
195
|
+
case read_state_types_1.UNIVERSAL_READ_STATUS.ABORTED: return read_state_types_1.READ_OUTCOME.ABORTED;
|
|
196
|
+
case read_state_types_1.UNIVERSAL_READ_STATUS.FULFILLED:
|
|
197
|
+
if (rawStatus !== undefined && rawStatus !== read_state_types_1.READ_STATUS.SUCCESS)
|
|
198
|
+
return read_state_types_1.READ_OUTCOME.SOURCE_ERROR;
|
|
199
|
+
if (callbackDelivered === false)
|
|
200
|
+
return read_state_types_1.READ_OUTCOME.CALLBACK_FAILED;
|
|
201
|
+
if (callbackDelivered !== true)
|
|
202
|
+
return read_state_types_1.READ_OUTCOME.UNKNOWN;
|
|
203
|
+
if (decodeError !== undefined)
|
|
204
|
+
return read_state_types_1.READ_OUTCOME.DECODE_FAILED;
|
|
205
|
+
return rawStatus === read_state_types_1.READ_STATUS.SUCCESS ? read_state_types_1.READ_OUTCOME.SUCCESS : read_state_types_1.READ_OUTCOME.UNKNOWN;
|
|
206
|
+
default:
|
|
207
|
+
return read_state_types_1.READ_OUTCOME.PENDING;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
/** Parse every pc_tx receipt (fulfil, settle) into one merged outcome. Missing receipts are skipped. */
|
|
211
|
+
async function collectOutcome(deps, record, requestId) {
|
|
212
|
+
const uc = chain_1.UNIVERSAL_CALLBACK_ADDRESSES[deps.pushNetwork];
|
|
213
|
+
const merged = {};
|
|
214
|
+
// Fetch in parallel, merge in pc_tx order (settle facts overwrite fulfil facts as before).
|
|
215
|
+
const outcomes = await Promise.all(record.pcTx.map(async (t) => {
|
|
216
|
+
if (!t.txHash)
|
|
217
|
+
return undefined;
|
|
218
|
+
try {
|
|
219
|
+
const receipt = await deps.pushClient.getTransactionReceiptWithArchiveFallback(t.txHash);
|
|
220
|
+
return (0, read_events_1.parseFulfilOutcome)(receipt, uc, requestId);
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
return undefined; // pruned or not yet available — leave those fields undefined rather than guess
|
|
224
|
+
}
|
|
225
|
+
}));
|
|
226
|
+
for (const o of outcomes) {
|
|
227
|
+
if (!o)
|
|
228
|
+
continue;
|
|
229
|
+
for (const [k, v] of Object.entries(o)) {
|
|
230
|
+
if (v !== undefined)
|
|
231
|
+
merged[k] = v;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return merged;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Expiry runs in the EndBlocker, so its UniversalCallback logs exist only as Cosmos
|
|
238
|
+
* `tx_log` events (`mode: EndBlock`) at the sweeper's block. Each `txLog` attribute is
|
|
239
|
+
* one JSON log with base64 `data`. Any failure leaves the outcome empty (unknown).
|
|
240
|
+
*/
|
|
241
|
+
async function collectExpiryOutcome(deps, record, requestId) {
|
|
242
|
+
const heights = [...new Set(record.pcTx.map((t) => t.blockHeight).filter((h) => h > 0))].sort((a, b) => b - a);
|
|
243
|
+
for (const height of heights) {
|
|
244
|
+
try {
|
|
245
|
+
const events = await deps.pushClient.getBlockResultEvents(height);
|
|
246
|
+
const logs = [];
|
|
247
|
+
for (const ev of events) {
|
|
248
|
+
if (ev.type !== 'tx_log')
|
|
249
|
+
continue;
|
|
250
|
+
if (!ev.attributes.some((a) => a.key === 'mode' && a.value === 'EndBlock'))
|
|
251
|
+
continue;
|
|
252
|
+
for (const a of ev.attributes) {
|
|
253
|
+
if (a.key !== 'txLog')
|
|
254
|
+
continue;
|
|
255
|
+
try {
|
|
256
|
+
const log = JSON.parse(a.value);
|
|
257
|
+
if (typeof log.address !== 'string' || !Array.isArray(log.topics) || !log.topics.every((t) => typeof t === 'string'))
|
|
258
|
+
continue;
|
|
259
|
+
const data = typeof log.data === 'string' && log.data.startsWith('0x') ? log.data : (0, viem_1.bytesToHex)(new Uint8Array(Buffer.from(log.data ?? '', 'base64')));
|
|
260
|
+
logs.push({ address: log.address, topics: log.topics, data });
|
|
261
|
+
}
|
|
262
|
+
catch { /* An unrelated malformed log must not hide this request's refund. */ }
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
const out = (0, read_events_1.parseFulfilOutcome)({ logs }, chain_1.UNIVERSAL_CALLBACK_ADDRESSES[deps.pushNetwork], requestId);
|
|
266
|
+
// RequestExpired carries the amount the contract tried to push; only RefundSent /
|
|
267
|
+
// RefundFailed say whether it landed. Report the amount only when it did.
|
|
268
|
+
if (out.refundFailed === true)
|
|
269
|
+
return { refundFailed: true };
|
|
270
|
+
if (out.refundFailed === false)
|
|
271
|
+
return { refunded: out.refunded, refundFailed: false };
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
// One unavailable attempt must not hide matching logs at another height.
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return {};
|
|
278
|
+
}
|
|
279
|
+
/** Best-effort shape from the on-chain envelope. Contract calls carry no ABI → raw. */
|
|
280
|
+
function inferResultShape(namespace, query) {
|
|
281
|
+
try {
|
|
282
|
+
switch (namespace) {
|
|
283
|
+
case read_state_1.READ_NAMESPACE.EVM: {
|
|
284
|
+
const { queryType } = (0, envelopes_1.decodeEvmQueryEnvelope)(query);
|
|
285
|
+
if (queryType === envelopes_1.EVM_QUERY_TYPE.ACCOUNT_BALANCE)
|
|
286
|
+
return { kind: 'uint256' };
|
|
287
|
+
if (queryType === envelopes_1.EVM_QUERY_TYPE.STORAGE_SLOT)
|
|
288
|
+
return { kind: 'bytes32' };
|
|
289
|
+
return { kind: 'raw' };
|
|
290
|
+
}
|
|
291
|
+
case read_state_1.READ_NAMESPACE.SVM: {
|
|
292
|
+
const { queryType } = (0, envelopes_1.decodeSvmQueryEnvelope)(query);
|
|
293
|
+
return queryType === envelopes_1.SVM_QUERY_TYPE.RAW_ACCOUNT_DATA ? { kind: 'raw' } : { kind: 'uint256' };
|
|
294
|
+
}
|
|
295
|
+
case read_state_1.READ_NAMESPACE.WEB2: {
|
|
296
|
+
const { extract } = (0, envelopes_1.decodeWeb2QueryEnvelope)(query);
|
|
297
|
+
const byNumber = Object.fromEntries(Object.entries(envelopes_1.WEB2_VALUE_TYPE).map(([name, n]) => [n, name]));
|
|
298
|
+
return {
|
|
299
|
+
kind: 'web2',
|
|
300
|
+
extract: extract.map((e) => ({ path: e.path, valueType: byNumber[e.valueType] ?? 'bytes', decimals: e.decimals })),
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
default:
|
|
304
|
+
return { kind: 'raw' };
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
catch {
|
|
308
|
+
return { kind: 'raw' };
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
function chainFromCaip2(caip2) {
|
|
312
|
+
if (caip2 === enums_1.CHAIN.WEB2)
|
|
313
|
+
return enums_1.CHAIN.WEB2;
|
|
314
|
+
return Object.values(enums_1.CHAIN).includes(caip2) ? caip2 : undefined;
|
|
315
|
+
}
|
|
316
|
+
function explorerTxUrl(network, txHash) {
|
|
317
|
+
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;
|
|
318
|
+
const base = chain_1.PUSH_CHAIN_INFO[chain].explorerUrl ?? 'https://donut.push.network';
|
|
319
|
+
return `${base}/tx/${txHash}`;
|
|
320
|
+
}
|
|
321
|
+
// ---------------------------------------------------------------------------
|
|
322
|
+
// wait()
|
|
323
|
+
// ---------------------------------------------------------------------------
|
|
324
|
+
const STATUS_NAME = {
|
|
325
|
+
[read_state_types_1.UNIVERSAL_READ_STATUS.UNSPECIFIED]: 'UNSPECIFIED',
|
|
326
|
+
[read_state_types_1.UNIVERSAL_READ_STATUS.PENDING]: 'PENDING',
|
|
327
|
+
[read_state_types_1.UNIVERSAL_READ_STATUS.VOTING]: 'VOTING',
|
|
328
|
+
[read_state_types_1.UNIVERSAL_READ_STATUS.FULFILLED]: 'FULFILLED',
|
|
329
|
+
[read_state_types_1.UNIVERSAL_READ_STATUS.EXPIRED]: 'EXPIRED',
|
|
330
|
+
[read_state_types_1.UNIVERSAL_READ_STATUS.FAILED]: 'FAILED',
|
|
331
|
+
[read_state_types_1.UNIVERSAL_READ_STATUS.ABORTED]: 'ABORTED',
|
|
332
|
+
};
|
|
333
|
+
function statusName(status) {
|
|
334
|
+
return STATUS_NAME[status] ?? `UNKNOWN(${status})`;
|
|
335
|
+
}
|
|
336
|
+
/** Observation allowance after estimated expiry; not a guarantee of node settlement. */
|
|
337
|
+
const READ_TIMEOUT_MARGIN_MS = 10_000;
|
|
338
|
+
/** Default: remaining Push blocks plus observation margin, capped at 180 seconds. */
|
|
339
|
+
function defaultWaitTimeoutMs(currentPushHeight, expiryPushChainHeight) {
|
|
340
|
+
const blocks = expiryPushChainHeight > currentPushHeight ? Number(expiryPushChainHeight - currentPushHeight) : 0;
|
|
341
|
+
return Math.min(read_state_1.READ_TRACK_MAX_TIMEOUT_MS, blocks * read_state_1.PUSH_BLOCK_TIME_MS + READ_TIMEOUT_MARGIN_MS);
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Poll until terminal. Resolves on EVERY terminal status (EXPIRED / FAILED / ABORTED
|
|
345
|
+
* are statuses, not throws — house convention); only a client timeout throws.
|
|
346
|
+
*/
|
|
347
|
+
async function waitForRead(deps, initial, opts) {
|
|
348
|
+
const now = deps.now ?? Date.now;
|
|
349
|
+
const emit = deps.emit ?? (() => undefined);
|
|
350
|
+
const interval = pollInterval(opts);
|
|
351
|
+
const start = now();
|
|
352
|
+
let timeoutMs = opts.timeoutMs ?? read_state_1.READ_TRACK_MAX_TIMEOUT_MS;
|
|
353
|
+
// Push head at a known time; later heights are estimated from the block time (no extra RPC per poll).
|
|
354
|
+
let head;
|
|
355
|
+
// Resolve a fresh head for each wait/resume, only when a default is needed.
|
|
356
|
+
// Bound this RPC too: a stalled height lookup must not hang the promise forever.
|
|
357
|
+
if (opts.timeoutMs === undefined && !initial.isTerminal) {
|
|
358
|
+
let timer;
|
|
359
|
+
try {
|
|
360
|
+
const height = await Promise.race([
|
|
361
|
+
deps.pushClient.publicClient.getBlockNumber({ cacheTime: 0 }),
|
|
362
|
+
new Promise((_, reject) => {
|
|
363
|
+
timer = setTimeout(() => {
|
|
364
|
+
const elapsed = now() - start;
|
|
365
|
+
emit(progress_hook_types_1.PROGRESS_HOOK.READ_TX_199_03, initial.requestId, statusName(initial.status), elapsed);
|
|
366
|
+
reject(new errors_1.ReadTimeoutError(initial.status, elapsed, { requestId: initial.requestId, txHash: initial.txHash }));
|
|
367
|
+
}, read_state_1.READ_TRACK_MAX_TIMEOUT_MS);
|
|
368
|
+
}),
|
|
369
|
+
]);
|
|
370
|
+
head = { height, at: now() };
|
|
371
|
+
timeoutMs = Math.min(read_state_1.READ_TRACK_MAX_TIMEOUT_MS, now() - start + defaultWaitTimeoutMs(height, initial.request.spec.expiryPushChainHeight));
|
|
372
|
+
}
|
|
373
|
+
finally {
|
|
374
|
+
if (timer !== undefined)
|
|
375
|
+
clearTimeout(timer);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
else if (!initial.isTerminal) {
|
|
379
|
+
// Explicit timeout: the head only feeds the expiry warning, so never block on it.
|
|
380
|
+
deps.pushClient.publicClient.getBlockNumber({ cacheTime: 0 })
|
|
381
|
+
.then((height) => { head = { height, at: now() }; })
|
|
382
|
+
.catch(() => undefined);
|
|
383
|
+
}
|
|
384
|
+
let expiryWarned = false;
|
|
385
|
+
const warnNearExpiry = (r) => {
|
|
386
|
+
if (expiryWarned || !head)
|
|
387
|
+
return;
|
|
388
|
+
const estimated = head.height + BigInt(Math.floor((now() - head.at) / read_state_1.PUSH_BLOCK_TIME_MS));
|
|
389
|
+
const left = r.request.spec.expiryPushChainHeight - estimated;
|
|
390
|
+
if (left > read_state_1.READ_EXPIRY_WARNING_BLOCKS)
|
|
391
|
+
return;
|
|
392
|
+
expiryWarned = true;
|
|
393
|
+
emit(progress_hook_types_1.PROGRESS_HOOK.READ_TX_105_04, r.requestId, left > 0n ? left : 0n);
|
|
394
|
+
};
|
|
395
|
+
emit(progress_hook_types_1.PROGRESS_HOOK.READ_TX_104_02, initial.txHash, initial.requestId, initial.request.logIndex);
|
|
396
|
+
// Every snapshot taken from here on is built with the wait() options, so a
|
|
397
|
+
// resultShape given to wait() decodes even a record that is already terminal.
|
|
398
|
+
let current = initial;
|
|
399
|
+
const timeout = () => {
|
|
400
|
+
const elapsed = now() - start;
|
|
401
|
+
emit(progress_hook_types_1.PROGRESS_HOOK.READ_TX_199_03, current.requestId, statusName(current.status), elapsed);
|
|
402
|
+
throw new errors_1.ReadTimeoutError(current.status, elapsed, { requestId: current.requestId, txHash: current.txHash });
|
|
403
|
+
};
|
|
404
|
+
// A single deadline includes RPC/receipt latency. A missing snapshot must not
|
|
405
|
+
// start trackRead's separate initial-ingestion retry window.
|
|
406
|
+
const reload = async () => {
|
|
407
|
+
const remaining = timeoutMs - (now() - start);
|
|
408
|
+
if (remaining <= 0)
|
|
409
|
+
return timeout();
|
|
410
|
+
let timer;
|
|
411
|
+
try {
|
|
412
|
+
return await Promise.race([
|
|
413
|
+
(async () => {
|
|
414
|
+
const records = await fetchRecords(deps, { requestId: initial.requestId });
|
|
415
|
+
return records[0] ? buildResponse(deps, records[0], opts) : current;
|
|
416
|
+
})(),
|
|
417
|
+
new Promise((_, reject) => {
|
|
418
|
+
timer = setTimeout(() => {
|
|
419
|
+
try {
|
|
420
|
+
timeout();
|
|
421
|
+
}
|
|
422
|
+
catch (error) {
|
|
423
|
+
reject(error);
|
|
424
|
+
}
|
|
425
|
+
}, remaining);
|
|
426
|
+
}),
|
|
427
|
+
]);
|
|
428
|
+
}
|
|
429
|
+
finally {
|
|
430
|
+
if (timer !== undefined)
|
|
431
|
+
clearTimeout(timer);
|
|
432
|
+
}
|
|
433
|
+
};
|
|
434
|
+
if (initial.isTerminal && opts.resultShape !== undefined)
|
|
435
|
+
current = await reload();
|
|
436
|
+
let announced;
|
|
437
|
+
for (;;) {
|
|
438
|
+
if (current.isTerminal) {
|
|
439
|
+
emitTerminal(emit, current);
|
|
440
|
+
return current;
|
|
441
|
+
}
|
|
442
|
+
if (current.status !== announced) {
|
|
443
|
+
announced = current.status;
|
|
444
|
+
if (current.status === read_state_types_1.UNIVERSAL_READ_STATUS.PENDING)
|
|
445
|
+
emit(progress_hook_types_1.PROGRESS_HOOK.READ_TX_105_01, current.requestId);
|
|
446
|
+
else if (current.status === read_state_types_1.UNIVERSAL_READ_STATUS.VOTING)
|
|
447
|
+
emit(progress_hook_types_1.PROGRESS_HOOK.READ_TX_105_02, current.requestId);
|
|
448
|
+
}
|
|
449
|
+
warnNearExpiry(current);
|
|
450
|
+
const elapsed = now() - start;
|
|
451
|
+
if (elapsed >= timeoutMs)
|
|
452
|
+
timeout();
|
|
453
|
+
await sleep(Math.min(interval, timeoutMs - elapsed));
|
|
454
|
+
current = await reload();
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
function emitTerminal(emit, r) {
|
|
458
|
+
if (r.status === read_state_types_1.UNIVERSAL_READ_STATUS.FULFILLED) {
|
|
459
|
+
if (r.callbackDelivered === true)
|
|
460
|
+
emit(progress_hook_types_1.PROGRESS_HOOK.READ_TX_106_02, r.requestId);
|
|
461
|
+
else if (r.callbackDelivered === false)
|
|
462
|
+
emit(progress_hook_types_1.PROGRESS_HOOK.READ_TX_106_03, r.requestId, r.callbackFailReason);
|
|
463
|
+
if (r.fees.burned !== undefined)
|
|
464
|
+
emit(progress_hook_types_1.PROGRESS_HOOK.READ_TX_106_04, r.requestId, r.fees.burned, r.fees.refunded ?? 0n);
|
|
465
|
+
}
|
|
466
|
+
// Refund facts exist for FULFILLED/FAILED (settle logs) and EXPIRED (EndBlock logs).
|
|
467
|
+
if (r.fees.refundFailed === true)
|
|
468
|
+
emit(progress_hook_types_1.PROGRESS_HOOK.READ_TX_106_06, r.requestId, r.fees.refunded, r.request.refundTo);
|
|
469
|
+
else if (r.fees.refunded !== undefined)
|
|
470
|
+
emit(progress_hook_types_1.PROGRESS_HOOK.READ_TX_106_05, r.requestId, r.fees.refunded, r.request.refundTo);
|
|
471
|
+
// 199-01 means it worked. A FULFILLED read that did not (source error, callback
|
|
472
|
+
// failure, undecodable, unconfirmed delivery) ends on 199-02 named by its outcome.
|
|
473
|
+
if (r.outcome === read_state_types_1.READ_OUTCOME.SUCCESS) {
|
|
474
|
+
emit(progress_hook_types_1.PROGRESS_HOOK.READ_TX_199_01, r.requestId, r.value, r.raw?.resultData ?? '0x', r.callbackDelivered);
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
const ended = r.status === read_state_types_1.UNIVERSAL_READ_STATUS.FULFILLED ? r.outcome : statusName(r.status);
|
|
478
|
+
const detail = r.errorMsg || r.decodeError || '';
|
|
479
|
+
emit(progress_hook_types_1.PROGRESS_HOOK.READ_TX_199_02, r.requestId, ended, r.raw?.errorCode, detail, r.fees.refunded);
|
|
480
|
+
}
|
|
481
|
+
//# 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
|
+
}>;
|