@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.
- package/CHANGELOG.md +84 -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 +23 -1
- package/src/lib/constants/enums.d.ts +7 -0
- package/src/lib/constants/enums.js +11 -0
- package/src/lib/constants/index.d.ts +52 -0
- package/src/lib/constants/index.js +37 -8
- package/src/lib/constants/read-state.d.ts +53 -0
- package/src/lib/constants/read-state.js +59 -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 +37 -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 +271 -0
- package/src/lib/progress-hook/progress-hook.types.d.ts +32 -0
- package/src/lib/progress-hook/progress-hook.types.js +34 -0
- package/src/lib/push-chain/push-chain.d.ts +70 -0
- package/src/lib/push-chain/push-chain.js +27 -0
- package/src/lib/push-client/push-client.d.ts +35 -2
- package/src/lib/push-client/push-client.js +117 -4
- 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 +148 -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 +107 -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 +212 -0
- package/src/lib/read-state/read-params.d.ts +205 -0
- package/src/lib/read-state/read-params.js +120 -0
- package/src/lib/read-state/read-state.types.d.ts +417 -0
- package/src/lib/read-state/read-state.types.js +50 -0
- package/src/lib/read-state/read-tracker.d.ts +50 -0
- package/src/lib/read-state/read-tracker.js +415 -0
- package/src/lib/read-state/registry.d.ts +21 -0
- package/src/lib/read-state/registry.js +54 -0
- package/src/lib/read-state/result-decoder.d.ts +16 -0
- package/src/lib/read-state/result-decoder.js +73 -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/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,135 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.REQUEST_EXPIRED_TOPIC0 = exports.REFUND_FAILED_TOPIC0 = exports.REFUND_SENT_TOPIC0 = exports.CALLBACK_GAS_REPORTED_TOPIC0 = exports.CALLBACK_FAILED_TOPIC0 = exports.READ_FULFILLED_TOPIC0 = exports.READ_REQUESTED_TOPIC0 = exports.READ_REQUESTED_EVENT = void 0;
|
|
4
|
+
exports.parseReadRequestsFromReceipt = parseReadRequestsFromReceipt;
|
|
5
|
+
exports.parseFulfilOutcome = parseFulfilOutcome;
|
|
6
|
+
const viem_1 = require("viem");
|
|
7
|
+
const universalCallback_evm_1 = require("../constants/abi/universalCallback.evm");
|
|
8
|
+
function eventItem(name) {
|
|
9
|
+
return (0, viem_1.getAbiItem)({ abi: universalCallback_evm_1.UNIVERSAL_CALLBACK_EVM, name });
|
|
10
|
+
}
|
|
11
|
+
exports.READ_REQUESTED_EVENT = eventItem('ReadRequested');
|
|
12
|
+
exports.READ_REQUESTED_TOPIC0 = (0, viem_1.toEventSelector)(exports.READ_REQUESTED_EVENT);
|
|
13
|
+
exports.READ_FULFILLED_TOPIC0 = (0, viem_1.toEventSelector)(eventItem('ReadFulfilled'));
|
|
14
|
+
exports.CALLBACK_FAILED_TOPIC0 = (0, viem_1.toEventSelector)(eventItem('CallbackFailed'));
|
|
15
|
+
exports.CALLBACK_GAS_REPORTED_TOPIC0 = (0, viem_1.toEventSelector)(eventItem('CallbackGasReported'));
|
|
16
|
+
exports.REFUND_SENT_TOPIC0 = (0, viem_1.toEventSelector)(eventItem('RefundSent'));
|
|
17
|
+
exports.REFUND_FAILED_TOPIC0 = (0, viem_1.toEventSelector)(eventItem('RefundFailed'));
|
|
18
|
+
exports.REQUEST_EXPIRED_TOPIC0 = (0, viem_1.toEventSelector)(eventItem('RequestExpired'));
|
|
19
|
+
function sameAddress(a, b) {
|
|
20
|
+
return a.toLowerCase() === b.toLowerCase();
|
|
21
|
+
}
|
|
22
|
+
function toLogIndex(v, fallback) {
|
|
23
|
+
if (v === undefined)
|
|
24
|
+
return fallback;
|
|
25
|
+
return typeof v === 'number' ? v : Number(BigInt(v));
|
|
26
|
+
}
|
|
27
|
+
function isTopics(t) {
|
|
28
|
+
return t.length > 0;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Every read a transaction requested, in log order.
|
|
32
|
+
*
|
|
33
|
+
* Filters on BOTH the contract address and topic0. The address filter is a security
|
|
34
|
+
* control (the node applies the same one in `ingest.go`): without it any contract
|
|
35
|
+
* can forge a matching log.
|
|
36
|
+
*/
|
|
37
|
+
function parseReadRequestsFromReceipt(receipt, universalCallback) {
|
|
38
|
+
const out = [];
|
|
39
|
+
receipt.logs.forEach((log, i) => {
|
|
40
|
+
if (!sameAddress(log.address, universalCallback))
|
|
41
|
+
return;
|
|
42
|
+
if (!isTopics(log.topics) || log.topics[0].toLowerCase() !== exports.READ_REQUESTED_TOPIC0)
|
|
43
|
+
return;
|
|
44
|
+
const { args } = (0, viem_1.decodeEventLog)({
|
|
45
|
+
abi: universalCallback_evm_1.UNIVERSAL_CALLBACK_EVM,
|
|
46
|
+
eventName: 'ReadRequested',
|
|
47
|
+
data: log.data,
|
|
48
|
+
topics: log.topics,
|
|
49
|
+
});
|
|
50
|
+
const spec = args.readSpec;
|
|
51
|
+
out.push({
|
|
52
|
+
requestId: log.topics[1].toLowerCase(),
|
|
53
|
+
requestIdUint: args.requestId,
|
|
54
|
+
callbackTarget: args.callbackTarget,
|
|
55
|
+
originalFunder: args.originalFunder,
|
|
56
|
+
spec: {
|
|
57
|
+
account: { chainNamespace: spec.account.chainNamespace, chainId: spec.account.chainId, owner: spec.account.owner },
|
|
58
|
+
query: spec.query,
|
|
59
|
+
minConfirmations: spec.minConfirmations,
|
|
60
|
+
blockNumber: spec.blockNumber,
|
|
61
|
+
expiryPushChainHeight: spec.expiryPushChainHeight,
|
|
62
|
+
maxFee: spec.maxFee,
|
|
63
|
+
revertRecipient: spec.revertRecipient,
|
|
64
|
+
},
|
|
65
|
+
callbackGasLimit: args.callbackGasLimit,
|
|
66
|
+
totalPaid: args.totalPaid,
|
|
67
|
+
protocolFee: args.protocolFee,
|
|
68
|
+
callbackBudget: args.callbackBudget,
|
|
69
|
+
logIndex: toLogIndex(log.logIndex, i),
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Read the fulfil / settle / expiry logs for ONE request out of a receipt.
|
|
76
|
+
*
|
|
77
|
+
* `FULFILLED` on the node does not mean the app callback ran: the contract swallows
|
|
78
|
+
* a reverting callback into `CallbackFailed`. This is where `callbackDelivered`
|
|
79
|
+
* comes from. Note: EndBlocker-driven expiry transactions are NOT EVM-indexed —
|
|
80
|
+
* `RequestExpired` will never be seen through a receipt; use the node record.
|
|
81
|
+
*/
|
|
82
|
+
function parseFulfilOutcome(receipt, universalCallback, requestId) {
|
|
83
|
+
const out = {};
|
|
84
|
+
const wanted = requestId.toLowerCase();
|
|
85
|
+
for (const log of receipt.logs) {
|
|
86
|
+
if (!sameAddress(log.address, universalCallback) || !isTopics(log.topics))
|
|
87
|
+
continue;
|
|
88
|
+
if ((log.topics[1] ?? '').toLowerCase() !== wanted)
|
|
89
|
+
continue;
|
|
90
|
+
const topic0 = log.topics[0].toLowerCase();
|
|
91
|
+
const common = { abi: universalCallback_evm_1.UNIVERSAL_CALLBACK_EVM, data: log.data, topics: log.topics };
|
|
92
|
+
switch (topic0) {
|
|
93
|
+
case exports.READ_FULFILLED_TOPIC0:
|
|
94
|
+
out.delivered = true;
|
|
95
|
+
break;
|
|
96
|
+
case exports.CALLBACK_FAILED_TOPIC0: {
|
|
97
|
+
const { args } = (0, viem_1.decodeEventLog)({ ...common, eventName: 'CallbackFailed' });
|
|
98
|
+
out.delivered = false;
|
|
99
|
+
out.failReason = args.reason;
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
case exports.CALLBACK_GAS_REPORTED_TOPIC0: {
|
|
103
|
+
const { args } = (0, viem_1.decodeEventLog)({ ...common, eventName: 'CallbackGasReported' });
|
|
104
|
+
out.gasReported = args.gasReported;
|
|
105
|
+
out.burned = args.burned;
|
|
106
|
+
out.refunded = args.refunded;
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
case exports.REFUND_SENT_TOPIC0: {
|
|
110
|
+
const { args } = (0, viem_1.decodeEventLog)({ ...common, eventName: 'RefundSent' });
|
|
111
|
+
out.refunded = args.amount;
|
|
112
|
+
out.refundTo = args.recipient;
|
|
113
|
+
out.refundFailed = false;
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
case exports.REFUND_FAILED_TOPIC0: {
|
|
117
|
+
const { args } = (0, viem_1.decodeEventLog)({ ...common, eventName: 'RefundFailed' });
|
|
118
|
+
out.refundTo = args.recipient;
|
|
119
|
+
out.refundFailed = true;
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
case exports.REQUEST_EXPIRED_TOPIC0: {
|
|
123
|
+
const { args } = (0, viem_1.decodeEventLog)({ ...common, eventName: 'RequestExpired' });
|
|
124
|
+
out.expired = true;
|
|
125
|
+
out.refunded = args.refunded;
|
|
126
|
+
out.refundTo = args.revertRecipient;
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
default:
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return out;
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=read-events.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type Hex } from 'viem';
|
|
2
|
+
import type { Orchestrator } from '../orchestrator/orchestrator';
|
|
3
|
+
import type { BatchReadResponse, PreparedRead, ReadCallback } from './read-state.types';
|
|
4
|
+
import { type ReadExecuteOptions } from './read-params';
|
|
5
|
+
export type ReadExecutorDeps = Pick<Orchestrator, 'execute' | 'trackRead' | 'revalidateRead'> & Partial<Pick<Orchestrator, 'getProgressHook' | 'getReadBalance'>>;
|
|
6
|
+
/**
|
|
7
|
+
* The custom-receiver contract of `read()` / `executeReads()`: a non-zero target and the
|
|
8
|
+
* app's request entrypoint. Pure — call it before any preflight so a malformed request
|
|
9
|
+
* fails without a network round-trip.
|
|
10
|
+
*/
|
|
11
|
+
export declare function assertRequestEntrypoint(callback: ReadCallback | undefined, method?: string): Required<Pick<ReadCallback, 'target' | 'abi' | 'functionName'>> & Pick<ReadCallback, 'args'>;
|
|
12
|
+
/** Validate every app call before sending any transaction. */
|
|
13
|
+
export declare function readCall(prepared: PreparedRead): {
|
|
14
|
+
data: Hex;
|
|
15
|
+
value: bigint;
|
|
16
|
+
to: `0x${string}`;
|
|
17
|
+
};
|
|
18
|
+
export declare function executeReads<const R extends readonly PreparedRead[]>(deps: ReadExecutorDeps, reads: R, options?: ReadExecuteOptions): Promise<BatchReadResponse<R>>;
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.assertRequestEntrypoint = assertRequestEntrypoint;
|
|
4
|
+
exports.readCall = readCall;
|
|
5
|
+
exports.executeReads = executeReads;
|
|
6
|
+
const tslib_1 = require("tslib");
|
|
7
|
+
const viem_1 = require("viem");
|
|
8
|
+
const errors_1 = require("./errors");
|
|
9
|
+
const read_params_1 = require("./read-params");
|
|
10
|
+
const spec_builder_1 = require("./spec-builder");
|
|
11
|
+
const progress_hook_1 = tslib_1.__importDefault(require("../progress-hook/progress-hook"));
|
|
12
|
+
const progress_hook_types_1 = require("../progress-hook/progress-hook.types");
|
|
13
|
+
const read_state_types_1 = require("./read-state.types");
|
|
14
|
+
/**
|
|
15
|
+
* The custom-receiver contract of `read()` / `executeReads()`: a non-zero target and the
|
|
16
|
+
* app's request entrypoint. Pure — call it before any preflight so a malformed request
|
|
17
|
+
* fails without a network round-trip.
|
|
18
|
+
*/
|
|
19
|
+
function assertRequestEntrypoint(callback, method = 'executeReads') {
|
|
20
|
+
const { target, abi, functionName, args } = callback ?? {};
|
|
21
|
+
if (callback && 'request' in callback)
|
|
22
|
+
throw new errors_1.InvalidReadQueryError('callback.request was removed; use callback.abi, functionName and args');
|
|
23
|
+
if (!target)
|
|
24
|
+
throw new errors_1.ReadRegistryUnavailableError(method);
|
|
25
|
+
if (!(0, viem_1.isAddress)(target) || /^0x0{40}$/i.test(target))
|
|
26
|
+
throw new errors_1.InvalidReadQueryError('callback.target must be a non-zero address');
|
|
27
|
+
if (!Array.isArray(abi) || !functionName)
|
|
28
|
+
throw new errors_1.InvalidReadQueryError('callback needs abi and functionName');
|
|
29
|
+
return { target, abi, functionName, args };
|
|
30
|
+
}
|
|
31
|
+
/** Validate every app call before sending any transaction. */
|
|
32
|
+
function readCall(prepared) {
|
|
33
|
+
const callback = assertRequestEntrypoint(prepared.callback);
|
|
34
|
+
return { to: callback.target, ...(0, spec_builder_1.toCallData)(prepared, callback) };
|
|
35
|
+
}
|
|
36
|
+
/** Sequential wallet fallback attaches the hashes it already mined to the error it throws. */
|
|
37
|
+
function committedHashes(err) {
|
|
38
|
+
const hashes = err?.transactionHashes;
|
|
39
|
+
return Array.isArray(hashes) ? hashes : [];
|
|
40
|
+
}
|
|
41
|
+
async function executeReads(deps, reads, options = {}) {
|
|
42
|
+
if (reads.length === 0)
|
|
43
|
+
throw new errors_1.InvalidReadQueryError('executeReads requires at least one prepared read');
|
|
44
|
+
const hooks = new Set([deps.getProgressHook?.(), options.progressHook]);
|
|
45
|
+
const emit = (id, ...args) => {
|
|
46
|
+
for (const hook of hooks)
|
|
47
|
+
if (hook)
|
|
48
|
+
hook(progress_hook_1.default[id](...args));
|
|
49
|
+
};
|
|
50
|
+
const batch = reads.length > 1;
|
|
51
|
+
let failedAt = 0; // 0 denotes a batch-wide failure before an individual read is known.
|
|
52
|
+
if (batch)
|
|
53
|
+
emit(progress_hook_types_1.PROGRESS_HOOK.READ_TX_001, reads.length, reads.map(r => r.preflight.destination.caip2));
|
|
54
|
+
try {
|
|
55
|
+
const calls = reads.map(readCall);
|
|
56
|
+
// Validate the whole batch against current state before sending any request.
|
|
57
|
+
for (const [i, prepared] of reads.entries()) {
|
|
58
|
+
failedAt = i + 1;
|
|
59
|
+
if (batch)
|
|
60
|
+
emit(progress_hook_types_1.PROGRESS_HOOK.READ_TX_002_01, i + 1, reads.length, prepared.preflight.destination.caip2);
|
|
61
|
+
await deps.revalidateRead(prepared);
|
|
62
|
+
}
|
|
63
|
+
failedAt = 0;
|
|
64
|
+
if (deps.getReadBalance) {
|
|
65
|
+
const required = reads.reduce((sum, read) => sum + read.value, 0n);
|
|
66
|
+
const enforceGasCheck = options.advanced?.enforceGasCheck ?? false;
|
|
67
|
+
let available;
|
|
68
|
+
try {
|
|
69
|
+
available = await deps.getReadBalance();
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
if (enforceGasCheck)
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
if (available !== undefined) {
|
|
76
|
+
emit(progress_hook_types_1.PROGRESS_HOOK.READ_TX_103_01, required, available, enforceGasCheck);
|
|
77
|
+
if (available < required && enforceGasCheck) {
|
|
78
|
+
emit(progress_hook_types_1.PROGRESS_HOOK.READ_TX_103_02, required, available);
|
|
79
|
+
throw new errors_1.ReadStateError('INSUFFICIENT_READ_BALANCE', `read requests need ${required} UPC but the funding account has ${available} UPC`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
emit(progress_hook_types_1.PROGRESS_HOOK.READ_TX_104_01);
|
|
84
|
+
let tx;
|
|
85
|
+
try {
|
|
86
|
+
tx = await deps.execute(calls.length === 1 ? calls[0] : { to: calls[0].to, data: calls }, { enforceGasCheck: options.advanced?.enforceGasCheck, progressHook: options.progressHook });
|
|
87
|
+
}
|
|
88
|
+
catch (err) {
|
|
89
|
+
// A non-atomic batch can fail after earlier calls were mined: those reads exist,
|
|
90
|
+
// their budgets are escrowed, and only these hashes lead back to them.
|
|
91
|
+
const committed = committedHashes(err);
|
|
92
|
+
const pending = err?.pendingTransactionHash;
|
|
93
|
+
if (committed.length === 0 && !pending)
|
|
94
|
+
throw err;
|
|
95
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
96
|
+
throw new errors_1.ReadStateError('READ_REQUEST_TX_FAILED', `read request batch failed after ${committed.length} of ${calls.length} calls were mined: ${reason}`, {
|
|
97
|
+
txHash: committed[committed.length - 1] ?? pending,
|
|
98
|
+
transactionHashes: committed,
|
|
99
|
+
pendingTransactionHash: pending,
|
|
100
|
+
hint: [
|
|
101
|
+
committed.length ? `Resume the committed reads with trackRead({ txHash }) for: ${committed.join(', ')}` : '',
|
|
102
|
+
pending ? `Check the receipt of broadcast transaction ${pending} before retrying; its outcome is unknown` : '',
|
|
103
|
+
].filter(Boolean).join('; '),
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
const receipt = await tx.wait();
|
|
107
|
+
if (receipt.status !== 1)
|
|
108
|
+
throw new errors_1.ReadStateError('READ_REQUEST_TX_FAILED', 'read request transaction reverted', { txHash: tx.hash });
|
|
109
|
+
const hashes = tx.transactionHashes ?? [tx.hash];
|
|
110
|
+
const lifecycle = { ...(0, read_params_1.toLifecycleOptions)(options), progressHook: options.progressHook };
|
|
111
|
+
const groups = await Promise.all(hashes.map((txHash) => deps.trackRead({ txHash }, lifecycle)));
|
|
112
|
+
const remaining = groups.flatMap((group) => [...group].sort((a, b) => a.request.logIndex - b.request.logIndex));
|
|
113
|
+
// Each entrypoint must emit one matching request. Never silently attach another
|
|
114
|
+
// read's ABI/result to an input, even if the app emitted unexpected extra logs.
|
|
115
|
+
const ordered = reads.map((prepared, i) => {
|
|
116
|
+
const index = remaining.findIndex((r) => r.request.callbackTarget.toLowerCase() === calls[i].to.toLowerCase() &&
|
|
117
|
+
r.request.callbackGasLimit === prepared.callbackGasLimit &&
|
|
118
|
+
(0, spec_builder_1.encodeSpec)(r.request.spec).toLowerCase() === (0, spec_builder_1.encodeSpec)(prepared.spec).toLowerCase());
|
|
119
|
+
if (index < 0)
|
|
120
|
+
throw new errors_1.ReadStateError('READ_REQUEST_MISMATCH', `no matching request for prepared read ${i}; resume using the Push transaction hashes: ${hashes.join(', ')}`, { txHash: tx.hash, transactionHashes: hashes });
|
|
121
|
+
return remaining.splice(index, 1)[0];
|
|
122
|
+
});
|
|
123
|
+
if (remaining.length)
|
|
124
|
+
throw new errors_1.ReadStateError('READ_REQUEST_MISMATCH', `app emitted unexpected extra reads; resume using the Push transaction hashes: ${hashes.join(', ')}`, { txHash: tx.hash, transactionHashes: hashes });
|
|
125
|
+
const completedReadIds = new Set();
|
|
126
|
+
const emitReadComplete = (terminal, i) => {
|
|
127
|
+
if (!batch || completedReadIds.has(terminal.requestId))
|
|
128
|
+
return;
|
|
129
|
+
completedReadIds.add(terminal.requestId);
|
|
130
|
+
emit(progress_hook_types_1.PROGRESS_HOOK.READ_TX_002_99_99, i + 1, reads.length, terminal.requestId);
|
|
131
|
+
};
|
|
132
|
+
const results = await Promise.all(ordered.map(async (record, i) => {
|
|
133
|
+
try {
|
|
134
|
+
const opts = { ...lifecycle, resultShape: reads[i].resultShape };
|
|
135
|
+
const response = await deps.trackRead({ requestId: record.requestId }, opts);
|
|
136
|
+
if (options.waitForCompletion === false) {
|
|
137
|
+
emit(progress_hook_types_1.PROGRESS_HOOK.READ_TX_104_02, response.txHash, response.requestId, response.request.logIndex);
|
|
138
|
+
return response;
|
|
139
|
+
}
|
|
140
|
+
const terminal = await response.wait();
|
|
141
|
+
emitReadComplete(terminal, i);
|
|
142
|
+
return terminal;
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
if (failedAt === 0)
|
|
146
|
+
failedAt = i + 1;
|
|
147
|
+
throw error;
|
|
148
|
+
}
|
|
149
|
+
}));
|
|
150
|
+
let emittedTerminalBatch = false;
|
|
151
|
+
const emitBatchOutcome = (terminal) => {
|
|
152
|
+
if (!batch || emittedTerminalBatch)
|
|
153
|
+
return;
|
|
154
|
+
emittedTerminalBatch = true;
|
|
155
|
+
const failed = terminal.findIndex(r => r.status !== read_state_types_1.UNIVERSAL_READ_STATUS.FULFILLED || r.callbackDelivered !== true || r.raw?.status !== read_state_types_1.READ_STATUS.SUCCESS);
|
|
156
|
+
if (failed < 0)
|
|
157
|
+
emit(progress_hook_types_1.PROGRESS_HOOK.READ_TX_999_01, terminal.length);
|
|
158
|
+
else
|
|
159
|
+
emit(progress_hook_types_1.PROGRESS_HOOK.READ_TX_999_02, failed + 1, terminal.length, terminal[failed].errorMsg || 'Read or callback did not succeed');
|
|
160
|
+
};
|
|
161
|
+
if (options.waitForCompletion !== false) {
|
|
162
|
+
emitBatchOutcome(results);
|
|
163
|
+
}
|
|
164
|
+
const typedReads = results;
|
|
165
|
+
const response = {
|
|
166
|
+
txHash: tx.hash,
|
|
167
|
+
...(tx.transactionHashes ? { transactionHashes: [...tx.transactionHashes] } : {}),
|
|
168
|
+
reads: typedReads,
|
|
169
|
+
count: typedReads.length,
|
|
170
|
+
atomic: tx.atomic ?? hashes.length === 1,
|
|
171
|
+
wait: async (waitOpts) => {
|
|
172
|
+
let waitFailedAt = 0;
|
|
173
|
+
try {
|
|
174
|
+
const terminal = await Promise.all(typedReads.map(async (read, i) => {
|
|
175
|
+
try {
|
|
176
|
+
const done = await read.wait(waitOpts);
|
|
177
|
+
emitReadComplete(done, i);
|
|
178
|
+
return done;
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
if (waitFailedAt === 0)
|
|
182
|
+
waitFailedAt = i + 1;
|
|
183
|
+
throw error;
|
|
184
|
+
}
|
|
185
|
+
}));
|
|
186
|
+
emitBatchOutcome(terminal);
|
|
187
|
+
return terminal;
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
if (batch) {
|
|
191
|
+
if (error instanceof errors_1.ReadTimeoutError)
|
|
192
|
+
emit(progress_hook_types_1.PROGRESS_HOOK.READ_TX_999_03, waitFailedAt, reads.length);
|
|
193
|
+
else
|
|
194
|
+
emit(progress_hook_types_1.PROGRESS_HOOK.READ_TX_999_02, waitFailedAt, reads.length, error instanceof Error ? error.message : String(error));
|
|
195
|
+
}
|
|
196
|
+
throw error;
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
};
|
|
200
|
+
return response;
|
|
201
|
+
}
|
|
202
|
+
catch (error) {
|
|
203
|
+
if (batch) {
|
|
204
|
+
if (error instanceof errors_1.ReadTimeoutError)
|
|
205
|
+
emit(progress_hook_types_1.PROGRESS_HOOK.READ_TX_999_03, failedAt, reads.length);
|
|
206
|
+
else
|
|
207
|
+
emit(progress_hook_types_1.PROGRESS_HOOK.READ_TX_999_02, failedAt, reads.length, error instanceof Error ? error.message : String(error));
|
|
208
|
+
}
|
|
209
|
+
throw error;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
//# sourceMappingURL=read-executor.js.map
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The public `read(subject, options)` grammar → the internal `BuildReadSpecParams`.
|
|
3
|
+
*
|
|
4
|
+
* read(user, { chain }) native balance (ETH / lamports)
|
|
5
|
+
* read(user, { chain, token }) token balance (ERC-20 / SPL via ATA)
|
|
6
|
+
* read(contract, { chain, abi, functionName, args }) typed call — abi encodes AND decodes
|
|
7
|
+
* read(contract, { chain, storageSlot }) storage word
|
|
8
|
+
* read(url, { chain: CHAIN.WEB2, web2 }) web2
|
|
9
|
+
*
|
|
10
|
+
* Query keys are mutually exclusive; the namespace decides which are legal.
|
|
11
|
+
*/
|
|
12
|
+
import type { Abi, Address, Hex, ContractFunctionReturnType, ContractFunctionName, ContractFunctionArgs, ExtractAbiFunctionForArgs } from 'viem';
|
|
13
|
+
import { CHAIN, PUSH_NETWORK } from '../constants/enums';
|
|
14
|
+
import type { BuildReadSpecParams, ReadChain, ReadDestination, ReadLifecycleOptions, ReadQuery, ReadResultShape, Web2Extract } from './read-state.types';
|
|
15
|
+
import type { ReadCallback } from './read-state.types';
|
|
16
|
+
import type { ProgressEvent } from '../progress-hook/progress-hook.types';
|
|
17
|
+
export interface ReadWeb2Options {
|
|
18
|
+
/**
|
|
19
|
+
* 1–16 entries; result values come back in this order. Validators vote on
|
|
20
|
+
* identical extracted bytes. A volatile API may not reach quorum until
|
|
21
|
+
* aggregation modes such as median are supported by the protocol.
|
|
22
|
+
*/
|
|
23
|
+
extract: readonly Web2Extract[];
|
|
24
|
+
method?: 'GET' | 'POST';
|
|
25
|
+
/** ⚠ Written to a public event log, forever. */
|
|
26
|
+
headers?: Record<string, string>;
|
|
27
|
+
body?: string | Uint8Array;
|
|
28
|
+
timeoutMs?: number;
|
|
29
|
+
}
|
|
30
|
+
interface ReadOptionsInput {
|
|
31
|
+
/** Destination. `CHAIN.WEB2` for https. */
|
|
32
|
+
chain: ReadChain;
|
|
33
|
+
/** Token balance of `subject`: ERC-20 `balanceOf` on EVM, the SPL ATA on SVM. */
|
|
34
|
+
token?: string;
|
|
35
|
+
/** Solana token balance only. Defaults to the original SPL Token program. */
|
|
36
|
+
tokenProgram?: 'spl-token' | 'token-2022';
|
|
37
|
+
/** Typed contract call — encodes the call and decodes the result. */
|
|
38
|
+
abi?: Abi;
|
|
39
|
+
functionName?: string;
|
|
40
|
+
args?: readonly unknown[];
|
|
41
|
+
/** EVM storage word. */
|
|
42
|
+
storageSlot?: Hex | bigint;
|
|
43
|
+
/** Web2 only — required when `chain` is web2. */
|
|
44
|
+
web2?: ReadWeb2Options;
|
|
45
|
+
blockNumber?: bigint;
|
|
46
|
+
minConfirmations?: number;
|
|
47
|
+
expiryBlocks?: bigint;
|
|
48
|
+
maxFee?: bigint;
|
|
49
|
+
callback?: ReadCallback;
|
|
50
|
+
/** ReadSpec.revertRecipient. Default: the sending account. */
|
|
51
|
+
refundTo?: Address;
|
|
52
|
+
waitForCompletion?: boolean;
|
|
53
|
+
progressHook?: (event: ProgressEvent) => void;
|
|
54
|
+
advanced?: {
|
|
55
|
+
pollingIntervalMs?: number;
|
|
56
|
+
timeout?: number;
|
|
57
|
+
enforceGasCheck?: boolean;
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
type NoQuery = {
|
|
61
|
+
token?: never;
|
|
62
|
+
tokenProgram?: never;
|
|
63
|
+
abi?: never;
|
|
64
|
+
functionName?: never;
|
|
65
|
+
args?: never;
|
|
66
|
+
storageSlot?: never;
|
|
67
|
+
web2?: never;
|
|
68
|
+
};
|
|
69
|
+
type Query<K extends keyof NoQuery, T> = Omit<NoQuery, K> & T;
|
|
70
|
+
type EvmChain = Extract<CHAIN, `eip155:${string}`>;
|
|
71
|
+
type SvmChain = Extract<CHAIN, `solana:${string}`>;
|
|
72
|
+
export type ReadQueryOptions = ({
|
|
73
|
+
chain: EvmChain;
|
|
74
|
+
} & NoQuery) | ({
|
|
75
|
+
chain: SvmChain;
|
|
76
|
+
} & NoQuery) | ({
|
|
77
|
+
chain: EvmChain;
|
|
78
|
+
} & Query<'token', {
|
|
79
|
+
token: string;
|
|
80
|
+
}>) | ({
|
|
81
|
+
chain: SvmChain;
|
|
82
|
+
} & Query<'token' | 'tokenProgram', {
|
|
83
|
+
token: string;
|
|
84
|
+
tokenProgram?: 'spl-token' | 'token-2022';
|
|
85
|
+
}>) | ({
|
|
86
|
+
chain: EvmChain;
|
|
87
|
+
} & Query<'abi' | 'functionName' | 'args', {
|
|
88
|
+
abi: Abi;
|
|
89
|
+
functionName: string;
|
|
90
|
+
args?: readonly unknown[];
|
|
91
|
+
}>) | ({
|
|
92
|
+
chain: EvmChain;
|
|
93
|
+
} & Query<'storageSlot', {
|
|
94
|
+
storageSlot: Hex | bigint;
|
|
95
|
+
}>) | ({
|
|
96
|
+
chain: typeof CHAIN.WEB2;
|
|
97
|
+
} & Query<'web2', {
|
|
98
|
+
web2: ReadWeb2Options;
|
|
99
|
+
}>);
|
|
100
|
+
export type ReadCallbackOptions = {
|
|
101
|
+
callback?: ({
|
|
102
|
+
target: Address;
|
|
103
|
+
gasLimit: bigint;
|
|
104
|
+
} & Pick<ReadCallback, 'abi' | 'functionName' | 'args'>) | {
|
|
105
|
+
target?: never;
|
|
106
|
+
gasLimit?: bigint;
|
|
107
|
+
abi?: never;
|
|
108
|
+
functionName?: never;
|
|
109
|
+
args?: never;
|
|
110
|
+
};
|
|
111
|
+
};
|
|
112
|
+
type SharedPrepareOptions = ReadCallbackOptions & Pick<ReadOptionsInput, 'expiryBlocks' | 'maxFee' | 'refundTo'> & {
|
|
113
|
+
advanced?: never;
|
|
114
|
+
waitForCompletion?: never;
|
|
115
|
+
progressHook?: never;
|
|
116
|
+
};
|
|
117
|
+
type EvmPinningOptions = Pick<ReadOptionsInput, 'blockNumber' | 'minConfirmations'>;
|
|
118
|
+
type FinalizedDestinationOptions = {
|
|
119
|
+
blockNumber?: never;
|
|
120
|
+
minConfirmations?: never;
|
|
121
|
+
};
|
|
122
|
+
/** EVM reads expose pinning; SVM and Web2 choose their finalized/heightless references internally. */
|
|
123
|
+
export type ReadPrepareOptions = SharedPrepareOptions & ((Extract<ReadQueryOptions, {
|
|
124
|
+
chain: EvmChain;
|
|
125
|
+
}> & EvmPinningOptions) | (Extract<ReadQueryOptions, {
|
|
126
|
+
chain: SvmChain | typeof CHAIN.WEB2;
|
|
127
|
+
}> & FinalizedDestinationOptions));
|
|
128
|
+
export type ReadExecuteOptions = Pick<ReadOptionsInput, 'advanced' | 'waitForCompletion' | 'progressHook'>;
|
|
129
|
+
type WithExecutionOptions<T> = T extends unknown ? Omit<T, 'advanced' | 'waitForCompletion' | 'progressHook'> & ReadExecuteOptions : never;
|
|
130
|
+
export type ReadOptions = WithExecutionOptions<ReadPrepareOptions> & {
|
|
131
|
+
callback?: (Required<Pick<ReadCallback, 'target' | 'gasLimit' | 'abi' | 'functionName'>> & Pick<ReadCallback, 'args'>) | {
|
|
132
|
+
target?: never;
|
|
133
|
+
gasLimit?: bigint;
|
|
134
|
+
abi?: never;
|
|
135
|
+
functionName?: never;
|
|
136
|
+
args?: never;
|
|
137
|
+
};
|
|
138
|
+
};
|
|
139
|
+
export type ReadTrackOptions = {
|
|
140
|
+
advanced?: Omit<NonNullable<ReadOptionsInput['advanced']>, 'enforceGasCheck'>;
|
|
141
|
+
resultShape?: ReadResultShape;
|
|
142
|
+
};
|
|
143
|
+
type Web2Values = {
|
|
144
|
+
uint256: bigint;
|
|
145
|
+
int256: bigint;
|
|
146
|
+
bool: boolean;
|
|
147
|
+
bytes: Hex;
|
|
148
|
+
string: string;
|
|
149
|
+
};
|
|
150
|
+
type Web2Value<E extends Web2Extract> = Web2Values[E['valueType']];
|
|
151
|
+
type ReadFunctionName<A extends Abi, F extends string> = F & ContractFunctionName<A, 'view' | 'pure'>;
|
|
152
|
+
type ReadFunctionArgs<O, A extends Abi, F extends string> = O extends {
|
|
153
|
+
args: infer Args extends ContractFunctionArgs<A, 'view' | 'pure', ReadFunctionName<A, F>>;
|
|
154
|
+
} ? Args : readonly [] extends ContractFunctionArgs<A, 'view' | 'pure', ReadFunctionName<A, F>> ? readonly [] : ContractFunctionArgs<A, 'view' | 'pure', ReadFunctionName<A, F>>;
|
|
155
|
+
type ReadCallValue<A extends Abi, F extends string, Args extends ContractFunctionArgs<A, 'view' | 'pure', ReadFunctionName<A, F>>> = ExtractAbiFunctionForArgs<A, 'view' | 'pure', ReadFunctionName<A, F>, Args> extends infer Selected ? Selected extends Abi[number] & {
|
|
156
|
+
type: 'function';
|
|
157
|
+
outputs: readonly unknown[];
|
|
158
|
+
} ? ContractFunctionReturnType<readonly [Selected]> : never : never;
|
|
159
|
+
/** Matches the existing decoder: select the overload by args before wrapping its outputs. */
|
|
160
|
+
export type ReadValue<O> = O extends {
|
|
161
|
+
abi: infer A extends Abi;
|
|
162
|
+
functionName: infer F extends string;
|
|
163
|
+
} ? number extends A['length'] ? readonly unknown[] : ReadCallValue<A, F, ReadFunctionArgs<O, A, F>> : O extends {
|
|
164
|
+
web2: {
|
|
165
|
+
extract: infer E extends readonly Web2Extract[];
|
|
166
|
+
};
|
|
167
|
+
} ? {
|
|
168
|
+
readonly [K in keyof E]: Web2Value<E[K]>;
|
|
169
|
+
} : O extends {
|
|
170
|
+
storageSlot: unknown;
|
|
171
|
+
} ? Hex : bigint;
|
|
172
|
+
/** Just `balanceOf` — it also types the decoded result as uint256. */
|
|
173
|
+
export declare const ERC20_BALANCE_OF_ABI: readonly [{
|
|
174
|
+
readonly type: "function";
|
|
175
|
+
readonly name: "balanceOf";
|
|
176
|
+
readonly stateMutability: "view";
|
|
177
|
+
readonly inputs: readonly [{
|
|
178
|
+
readonly name: "account";
|
|
179
|
+
readonly type: "address";
|
|
180
|
+
}];
|
|
181
|
+
readonly outputs: readonly [{
|
|
182
|
+
readonly name: "";
|
|
183
|
+
readonly type: "uint256";
|
|
184
|
+
}];
|
|
185
|
+
}];
|
|
186
|
+
export declare function toReadDestination(chain: ReadChain): ReadDestination;
|
|
187
|
+
/** Build the internal query from the public grammar. Pure. */
|
|
188
|
+
export declare function toReadQuery(subject: string, o: ReadOptionsInput): ReadQuery;
|
|
189
|
+
/** Public options → the spec builder's params. Pure; throws `InvalidReadQueryError` on a malformed request. */
|
|
190
|
+
export declare function toBuildReadSpecParams(subject: string, o: ReadOptionsInput, network?: PUSH_NETWORK): BuildReadSpecParams;
|
|
191
|
+
/** Offline logical query key for the registry's latestResult lookup. */
|
|
192
|
+
export declare function getReadQueryKey<const O extends ReadQueryOptions>(subject: string, options: O & ValidateReadCall<O>): Hex;
|
|
193
|
+
export declare function toLifecycleOptions(o?: ReadTrackOptions): ReadLifecycleOptions;
|
|
194
|
+
/** Restrict known ABIs to read functions and their declared argument tuple. */
|
|
195
|
+
export type ValidateReadCall<O> = O extends {
|
|
196
|
+
abi: infer A extends Abi;
|
|
197
|
+
functionName: infer F extends string;
|
|
198
|
+
} ? {
|
|
199
|
+
functionName: ContractFunctionName<A, 'view' | 'pure'>;
|
|
200
|
+
} & (readonly [] extends ContractFunctionArgs<A, 'view' | 'pure', F & ContractFunctionName<A, 'view' | 'pure'>> ? {
|
|
201
|
+
args?: ContractFunctionArgs<A, 'view' | 'pure', F & ContractFunctionName<A, 'view' | 'pure'>>;
|
|
202
|
+
} : {
|
|
203
|
+
args: ContractFunctionArgs<A, 'view' | 'pure', F & ContractFunctionName<A, 'view' | 'pure'>>;
|
|
204
|
+
}) : unknown;
|
|
205
|
+
export {};
|