@pushchain/core 6.0.24 → 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,251 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.encodeSpec = encodeSpec;
|
|
4
|
+
exports.buildReadSpecFromPreflight = buildReadSpecFromPreflight;
|
|
5
|
+
exports.prepareRead = prepareRead;
|
|
6
|
+
exports.toCallData = toCallData;
|
|
7
|
+
exports.simulateRead = simulateRead;
|
|
8
|
+
exports.revalidateRead = revalidateRead;
|
|
9
|
+
const viem_1 = require("viem");
|
|
10
|
+
const universalCallback_evm_1 = require("../constants/abi/universalCallback.evm");
|
|
11
|
+
const uea_factory_1 = require("../constants/abi/uea-factory");
|
|
12
|
+
const chain_1 = require("../constants/chain");
|
|
13
|
+
const read_state_1 = require("../constants/read-state");
|
|
14
|
+
const budget_1 = require("./budget");
|
|
15
|
+
const registry_1 = require("./registry");
|
|
16
|
+
const destination_1 = require("./destination");
|
|
17
|
+
const envelopes_1 = require("./envelopes");
|
|
18
|
+
const errors_1 = require("./errors");
|
|
19
|
+
const preflight_1 = require("./preflight");
|
|
20
|
+
const validate_1 = require("./validate");
|
|
21
|
+
const progress_hook_types_1 = require("../progress-hook/progress-hook.types");
|
|
22
|
+
/** The ReadSpec tuple as the contract declares it — for abi.encode(spec). */
|
|
23
|
+
const READ_SPEC_ABI_PARAM = (0, viem_1.getAbiItem)({ abi: universalCallback_evm_1.UNIVERSAL_CALLBACK_EVM, name: 'requestExternalReadSelf' }).inputs[0];
|
|
24
|
+
function toTuple(spec) {
|
|
25
|
+
return [
|
|
26
|
+
[spec.account.chainNamespace, spec.account.chainId, spec.account.owner],
|
|
27
|
+
spec.query,
|
|
28
|
+
spec.minConfirmations,
|
|
29
|
+
spec.blockNumber,
|
|
30
|
+
spec.expiryPushChainHeight,
|
|
31
|
+
spec.maxFee,
|
|
32
|
+
spec.revertRecipient,
|
|
33
|
+
];
|
|
34
|
+
}
|
|
35
|
+
function encodeSpec(spec) {
|
|
36
|
+
return (0, viem_1.encodeAbiParameters)([READ_SPEC_ABI_PARAM], [spec]);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Assemble a `PreparedRead` from an already-fetched preflight. Pure — no network.
|
|
40
|
+
* Use directly when preparing several reads from one preflight.
|
|
41
|
+
*/
|
|
42
|
+
function buildReadSpecFromPreflight(preflight, params, defaults = {}) {
|
|
43
|
+
const dest = preflight.destination;
|
|
44
|
+
if ((0, destination_1.resolveDestination)(params.destination).caip2 !== dest.caip2) {
|
|
45
|
+
throw new errors_1.InvalidReadQueryError('preflight destination does not match the requested destination');
|
|
46
|
+
}
|
|
47
|
+
const isWeb2 = dest.namespace === read_state_1.READ_NAMESPACE.WEB2;
|
|
48
|
+
const minConfirmations = params.minConfirmations ?? read_state_1.MIN_CONFIRMATIONS_FLOOR;
|
|
49
|
+
// --- pin ---
|
|
50
|
+
let blockNumber;
|
|
51
|
+
if (isWeb2) {
|
|
52
|
+
if (params.blockNumber !== undefined && params.blockNumber !== 0n) {
|
|
53
|
+
throw new errors_1.InvalidReadQueryError('web2 is heightless: blockNumber must be 0 (or omitted)');
|
|
54
|
+
}
|
|
55
|
+
blockNumber = 0n;
|
|
56
|
+
}
|
|
57
|
+
else if (params.blockNumber !== undefined) {
|
|
58
|
+
blockNumber = params.blockNumber;
|
|
59
|
+
}
|
|
60
|
+
else {
|
|
61
|
+
// Back off by minConfirmations: validators hold until latest ≥ blockNumber + minConfirmations,
|
|
62
|
+
// and the oracle height IS roughly latest — pinning at the head would always wait.
|
|
63
|
+
const backed = preflight.observedChainHeight - BigInt(minConfirmations);
|
|
64
|
+
blockNumber = backed >= 1n ? backed : 1n;
|
|
65
|
+
}
|
|
66
|
+
// --- envelope ---
|
|
67
|
+
const encodedQuery = (0, envelopes_1.encodeReadQuery)(params.destination, params.query, {
|
|
68
|
+
blockNumber,
|
|
69
|
+
minSlot: dest.namespace === read_state_1.READ_NAMESPACE.SVM ? blockNumber : undefined,
|
|
70
|
+
});
|
|
71
|
+
// --- money ---
|
|
72
|
+
const callbackBudget = params.callbackBudget ?? (0, budget_1.sizeCallbackBudget)(params.callbackGasLimit, preflight.pushGasPrice, params.budgetBuffer);
|
|
73
|
+
const value = preflight.protocolFee + callbackBudget;
|
|
74
|
+
const bps = BigInt(params.maxFeeBufferBps ?? 0);
|
|
75
|
+
const maxFee = params.maxFee ?? value + (value * bps) / 10000n;
|
|
76
|
+
// --- identities ---
|
|
77
|
+
const refundTo = params.refundTo ?? defaults.refundTo;
|
|
78
|
+
if (!refundTo || !(0, viem_1.isAddress)(refundTo)) {
|
|
79
|
+
throw new errors_1.InvalidReadSpecError(['ZERO_REVERT_RECIPIENT'], {
|
|
80
|
+
destination: dest.caip2,
|
|
81
|
+
hint: 'Pass refundTo (where unspent budget is pushed). In the SDK it defaults to your Push-side account.',
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
let owner;
|
|
85
|
+
if (dest.namespace === read_state_1.READ_NAMESPACE.SVM) {
|
|
86
|
+
if (!encodedQuery.ownerBytes)
|
|
87
|
+
throw new errors_1.InvalidReadQueryError('SVM query did not yield the 32-byte owner');
|
|
88
|
+
if (params.owner !== undefined)
|
|
89
|
+
throw new errors_1.InvalidReadQueryError('SVM reads take the account from the query, not `owner`');
|
|
90
|
+
owner = encodedQuery.ownerBytes;
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
owner = params.owner ?? refundTo.toLowerCase(); // validators ignore it for EVM; any non-empty bytes
|
|
94
|
+
}
|
|
95
|
+
const expiryPushChainHeight = params.expiryPushChainHeight ?? preflight.pushBlockNumber + (params.expiryBlocks ?? read_state_1.DEFAULT_EXPIRY_BLOCKS);
|
|
96
|
+
const spec = {
|
|
97
|
+
account: { chainNamespace: dest.chainNamespace, chainId: dest.chainId, owner },
|
|
98
|
+
query: encodedQuery.encoded,
|
|
99
|
+
minConfirmations,
|
|
100
|
+
blockNumber,
|
|
101
|
+
expiryPushChainHeight,
|
|
102
|
+
maxFee,
|
|
103
|
+
revertRecipient: refundTo,
|
|
104
|
+
};
|
|
105
|
+
(0, validate_1.assertValidReadSpec)({ spec, value, callbackGasLimit: params.callbackGasLimit, preflight });
|
|
106
|
+
return {
|
|
107
|
+
callback: params.callback,
|
|
108
|
+
queryKey: (0, registry_1.computeReadQueryKey)(params.destination, params.query),
|
|
109
|
+
chain: dest.caip2,
|
|
110
|
+
spec,
|
|
111
|
+
specTuple: toTuple(spec),
|
|
112
|
+
encodedSpec: encodeSpec(spec),
|
|
113
|
+
value,
|
|
114
|
+
protocolFee: preflight.protocolFee,
|
|
115
|
+
callbackBudget,
|
|
116
|
+
fees: { protocolFee: preflight.protocolFee, callbackBudget, total: value },
|
|
117
|
+
callbackGasLimit: params.callbackGasLimit,
|
|
118
|
+
encodedQuery,
|
|
119
|
+
resultShape: encodedQuery.resultShape,
|
|
120
|
+
preflight,
|
|
121
|
+
warnings: [...encodedQuery.warnings],
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Preflight + build. Adds the `refundTo` advisory: a contract recipient that is not a
|
|
126
|
+
* UEA must have a payable receive() or the refund is forfeited to the admin rescue
|
|
127
|
+
* pool (verified live). UEAs (UEA_EVM / UEA_SVM) always have one.
|
|
128
|
+
*/
|
|
129
|
+
async function prepareRead(deps, params) {
|
|
130
|
+
const emit = deps.emit ?? (() => undefined);
|
|
131
|
+
const dest = (0, destination_1.resolveDestination)(params.destination);
|
|
132
|
+
emit(progress_hook_types_1.PROGRESS_HOOK.READ_TX_101, dest.caip2, dest.namespace, params.query.type);
|
|
133
|
+
emit(progress_hook_types_1.PROGRESS_HOOK.READ_TX_102_01, dest.caip2);
|
|
134
|
+
let preflight;
|
|
135
|
+
try {
|
|
136
|
+
preflight = await (0, preflight_1.preflightRead)(deps, params.destination);
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
if (error instanceof errors_1.ReadHeightUnavailableError)
|
|
140
|
+
emit(progress_hook_types_1.PROGRESS_HOOK.READ_TX_102_03, dest.caip2);
|
|
141
|
+
throw error;
|
|
142
|
+
}
|
|
143
|
+
const prepared = buildReadSpecFromPreflight(preflight, params, { refundTo: deps.defaultRefundTo });
|
|
144
|
+
emit(progress_hook_types_1.PROGRESS_HOOK.READ_TX_102_02, prepared.protocolFee, prepared.callbackBudget, prepared.value, prepared.spec.blockNumber, prepared.spec.expiryPushChainHeight);
|
|
145
|
+
if (params.query.type === 'http') {
|
|
146
|
+
const matched = Object.keys(params.query.headers ?? {}).filter((name) => /auth|key|token|secret|bearer/i.test(name));
|
|
147
|
+
if (matched.length)
|
|
148
|
+
emit(progress_hook_types_1.PROGRESS_HOOK.READ_TX_103_03, matched);
|
|
149
|
+
}
|
|
150
|
+
const refundTo = prepared.spec.revertRecipient;
|
|
151
|
+
const code = await deps.pushClient.publicClient.getCode({ address: refundTo });
|
|
152
|
+
// EIP-7702 delegation designator (0xef0100 + delegate): an EOA that batches via the SDK's
|
|
153
|
+
// executor. It receives value like an EOA — refunds to such accounts have landed live.
|
|
154
|
+
const isDelegatedEoa = !!code && code.toLowerCase().startsWith('0xef0100');
|
|
155
|
+
if (code && code !== '0x' && !isDelegatedEoa) {
|
|
156
|
+
const [, isUEA] = await deps.pushClient.readContract({
|
|
157
|
+
address: chain_1.UEA_FACTORY[deps.pushNetwork],
|
|
158
|
+
abi: uea_factory_1.UEA_FACTORY_ABI,
|
|
159
|
+
functionName: 'getOriginForUEA',
|
|
160
|
+
args: [refundTo],
|
|
161
|
+
});
|
|
162
|
+
if (!isUEA) {
|
|
163
|
+
emit(progress_hook_types_1.PROGRESS_HOOK.READ_TX_102_05, refundTo);
|
|
164
|
+
prepared.warnings.push(`refundTo ${refundTo} is a contract that is not a UEA — it must have a payable receive() or the unspent callback budget is forfeited`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return prepared;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Splice the prepared spec into the app's own entrypoint.
|
|
171
|
+
* Default argument order is `(spec, callbackGasLimit)`; pass `args` to remap.
|
|
172
|
+
*/
|
|
173
|
+
function toCallData(prepared, target) {
|
|
174
|
+
// Positional tuple: viem accepts it whether the app ABI names the struct components or not.
|
|
175
|
+
const args = target.args ? target.args(prepared.specTuple, prepared.callbackGasLimit) : [prepared.specTuple, prepared.callbackGasLimit];
|
|
176
|
+
return {
|
|
177
|
+
data: (0, viem_1.encodeFunctionData)({ abi: target.abi, functionName: target.functionName, args: args }),
|
|
178
|
+
value: prepared.value,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* eth_call `requestExternalReadSelf` with `from` = the app contract, so validation
|
|
183
|
+
* reverts surface before broadcast, decoded into the contract's custom errors.
|
|
184
|
+
* Only meaningful for the app-contract persona: `callbackTarget` becomes `msg.sender`.
|
|
185
|
+
*/
|
|
186
|
+
async function simulateRead(deps, prepared, opts) {
|
|
187
|
+
const age = Date.now() - prepared.preflight.fetchedAt;
|
|
188
|
+
if (age > (opts.staleAfterMs ?? 60_000)) {
|
|
189
|
+
prepared.warnings.push(`preflight is ${Math.round(age / 1000)} s old — pushBlockNumber moves every block; rebuild before sending`);
|
|
190
|
+
}
|
|
191
|
+
try {
|
|
192
|
+
await deps.pushClient.publicClient.simulateContract({
|
|
193
|
+
address: prepared.preflight.universalCallback,
|
|
194
|
+
abi: universalCallback_evm_1.UNIVERSAL_CALLBACK_EVM,
|
|
195
|
+
functionName: 'requestExternalReadSelf',
|
|
196
|
+
args: [prepared.spec, opts.callbackSelector, prepared.callbackGasLimit],
|
|
197
|
+
account: opts.appContract,
|
|
198
|
+
value: prepared.value,
|
|
199
|
+
// In the real flow the app contract forwards the msg.value it just received from the
|
|
200
|
+
// user, so for the simulation it must "hold" it. Without this the node answers
|
|
201
|
+
// "insufficient balance for transfer" — and maps it to JSON-RPC -32602, which viem
|
|
202
|
+
// renders as the misleading "Missing or invalid parameters". Verified on Donut.
|
|
203
|
+
stateOverride: [{ address: opts.appContract, balance: prepared.value * 2n + 10n ** 18n }],
|
|
204
|
+
});
|
|
205
|
+
return { ok: true };
|
|
206
|
+
}
|
|
207
|
+
catch (err) {
|
|
208
|
+
if (err instanceof viem_1.BaseError) {
|
|
209
|
+
const reverted = err.walk((e) => e instanceof viem_1.ContractFunctionRevertedError);
|
|
210
|
+
if (reverted instanceof viem_1.ContractFunctionRevertedError && reverted.data) {
|
|
211
|
+
return { ok: false, error: reverted.shortMessage, errorName: reverted.data.errorName, args: reverted.data.args };
|
|
212
|
+
}
|
|
213
|
+
// Surface the node's own reason when viem's summary is generic.
|
|
214
|
+
const details = err.details || err.cause?.details;
|
|
215
|
+
return { ok: false, error: details ? `${err.shortMessage} (${details})` : err.shortMessage };
|
|
216
|
+
}
|
|
217
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
/** Fresh validation only: never rebuild or mutate the prepared request. */
|
|
221
|
+
async function revalidateRead(deps, prepared) {
|
|
222
|
+
const [preflight, block] = await Promise.all([
|
|
223
|
+
(0, preflight_1.preflightRead)(deps, {
|
|
224
|
+
chainNamespace: prepared.spec.account.chainNamespace,
|
|
225
|
+
chainId: prepared.spec.account.chainId,
|
|
226
|
+
}),
|
|
227
|
+
deps.pushClient.publicClient.getBlock({ blockTag: 'latest' }),
|
|
228
|
+
]);
|
|
229
|
+
// Mirrors node keeper.CallbackCost / CanAffordCallback (x/ucallback/keeper/evm.go).
|
|
230
|
+
// The module prices declared gas at base fee, without tips or the SDK sizing buffer.
|
|
231
|
+
const baseFee = block.baseFeePerGas;
|
|
232
|
+
if (baseFee === null || baseFee === undefined) {
|
|
233
|
+
throw new errors_1.ReadStateError('READ_CALLBACK_BASE_FEE_UNAVAILABLE', 'cannot validate callback affordability without the current Push base fee');
|
|
234
|
+
}
|
|
235
|
+
try {
|
|
236
|
+
(0, validate_1.assertValidReadSpec)({ spec: prepared.spec, value: prepared.value, callbackGasLimit: prepared.callbackGasLimit, preflight });
|
|
237
|
+
if (prepared.value - preflight.protocolFee < prepared.callbackGasLimit * baseFee) {
|
|
238
|
+
throw new errors_1.InvalidReadSpecError(['INSUFFICIENT_CALLBACK_BUDGET']);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
catch (error) {
|
|
242
|
+
if (error instanceof errors_1.InvalidReadSpecError) {
|
|
243
|
+
throw new errors_1.InvalidReadSpecError([...error.violations], {
|
|
244
|
+
destination: preflight.destination.caip2,
|
|
245
|
+
hint: 'Prepared read is no longer valid. Call prepareRead again and review its pin, expiry and payment before executing.',
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
throw error;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
//# sourceMappingURL=spec-builder.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type ReadSpecViolation } from './errors';
|
|
2
|
+
import type { ReadPreflight, ReadSpec } from './read-state.types';
|
|
3
|
+
export interface ValidateReadSpecInput {
|
|
4
|
+
spec: ReadSpec;
|
|
5
|
+
/** msg.value the request will carry. */
|
|
6
|
+
value: bigint;
|
|
7
|
+
callbackGasLimit: bigint;
|
|
8
|
+
/** When present, the height / expiry / fee rules that need chain state are checked too. */
|
|
9
|
+
preflight?: ReadPreflight;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Client-side mirror of every revert in `UniversalCallback.requestExternalReadSelf`
|
|
13
|
+
* (feat-read-state f8d1a0c), plus two SDK invariants, so a developer gets a typed
|
|
14
|
+
* violation list instead of an opaque revert. Line numbers refer to the contract.
|
|
15
|
+
*/
|
|
16
|
+
export declare function validateReadSpec(input: ValidateReadSpecInput): {
|
|
17
|
+
ok: true;
|
|
18
|
+
} | {
|
|
19
|
+
ok: false;
|
|
20
|
+
violations: ReadSpecViolation[];
|
|
21
|
+
};
|
|
22
|
+
export declare function assertValidReadSpec(input: ValidateReadSpecInput): void;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.validateReadSpec = validateReadSpec;
|
|
4
|
+
exports.assertValidReadSpec = assertValidReadSpec;
|
|
5
|
+
const viem_1 = require("viem");
|
|
6
|
+
const read_state_1 = require("../constants/read-state");
|
|
7
|
+
const errors_1 = require("./errors");
|
|
8
|
+
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
|
|
9
|
+
const UINT16_MAX = 65_535;
|
|
10
|
+
const UINT64_MAX = (1n << 64n) - 1n;
|
|
11
|
+
/**
|
|
12
|
+
* Client-side mirror of every revert in `UniversalCallback.requestExternalReadSelf`
|
|
13
|
+
* (feat-read-state f8d1a0c), plus two SDK invariants, so a developer gets a typed
|
|
14
|
+
* violation list instead of an opaque revert. Line numbers refer to the contract.
|
|
15
|
+
*/
|
|
16
|
+
function validateReadSpec(input) {
|
|
17
|
+
const { spec, value, callbackGasLimit, preflight } = input;
|
|
18
|
+
const v = [];
|
|
19
|
+
// :98-103 InvalidAccountId — every account field non-empty
|
|
20
|
+
const ownerLen = isHexLike(spec.account.owner) ? (0, viem_1.size)(spec.account.owner) : 0;
|
|
21
|
+
if (!spec.account.chainNamespace || !spec.account.chainId || ownerLen === 0)
|
|
22
|
+
v.push('INVALID_ACCOUNT_ID');
|
|
23
|
+
// :104-106 EmptyQuery
|
|
24
|
+
if (!isHexLike(spec.query) || (0, viem_1.size)(spec.query) === 0)
|
|
25
|
+
v.push('EMPTY_QUERY');
|
|
26
|
+
// :107-109 InvalidMinConfirmations (uint16)
|
|
27
|
+
if (!Number.isInteger(spec.minConfirmations) || spec.minConfirmations < read_state_1.MIN_CONFIRMATIONS_FLOOR || spec.minConfirmations > UINT16_MAX) {
|
|
28
|
+
v.push('INVALID_MIN_CONFIRMATIONS');
|
|
29
|
+
}
|
|
30
|
+
// :113-134 InvalidBlockNumber — heightless: must be 0; else 1..oracleHeight
|
|
31
|
+
if (spec.blockNumber < 0n || spec.blockNumber > UINT64_MAX) {
|
|
32
|
+
v.push('INVALID_BLOCK_NUMBER');
|
|
33
|
+
}
|
|
34
|
+
else if (preflight) {
|
|
35
|
+
const h = preflight.observedChainHeight;
|
|
36
|
+
const bad = h === 0n ? spec.blockNumber !== 0n : spec.blockNumber === 0n || spec.blockNumber > h;
|
|
37
|
+
if (bad)
|
|
38
|
+
v.push('INVALID_BLOCK_NUMBER');
|
|
39
|
+
}
|
|
40
|
+
// :119-121 InvalidExpiryHeight — strictly in the future
|
|
41
|
+
if (spec.expiryPushChainHeight < 0n || spec.expiryPushChainHeight > UINT64_MAX) {
|
|
42
|
+
v.push('INVALID_EXPIRY_HEIGHT');
|
|
43
|
+
}
|
|
44
|
+
else if (preflight && spec.expiryPushChainHeight <= preflight.pushBlockNumber) {
|
|
45
|
+
v.push('INVALID_EXPIRY_HEIGHT');
|
|
46
|
+
}
|
|
47
|
+
// :122-124 ZeroRevertRecipient
|
|
48
|
+
if (!(0, viem_1.isAddress)(spec.revertRecipient) || spec.revertRecipient.toLowerCase() === ZERO_ADDRESS)
|
|
49
|
+
v.push('ZERO_REVERT_RECIPIENT');
|
|
50
|
+
// :125-130 ZeroCallbackGasLimit / CallbackGasLimitExceeded
|
|
51
|
+
if (callbackGasLimit <= 0n)
|
|
52
|
+
v.push('ZERO_CALLBACK_GAS_LIMIT');
|
|
53
|
+
else if (callbackGasLimit > read_state_1.MAX_CALLBACK_GAS_LIMIT)
|
|
54
|
+
v.push('CALLBACK_GAS_LIMIT_EXCEEDED');
|
|
55
|
+
// :132-139 InsufficientFee / ExcessiveFee
|
|
56
|
+
if (preflight && value < preflight.protocolFee)
|
|
57
|
+
v.push('INSUFFICIENT_FEE');
|
|
58
|
+
if (value > spec.maxFee)
|
|
59
|
+
v.push('EXCESSIVE_FEE');
|
|
60
|
+
// SDK invariant (not a contract revert): a zero budget is never fulfilled by the node.
|
|
61
|
+
if (preflight && value - preflight.protocolFee <= 0n)
|
|
62
|
+
v.push('ZERO_CALLBACK_BUDGET');
|
|
63
|
+
// SDM invariant from svm/read_executor.go:29-32 — validators hard-reject any other length.
|
|
64
|
+
if (spec.account.chainNamespace === read_state_1.READ_NAMESPACE.SVM && ownerLen !== 32)
|
|
65
|
+
v.push('SVM_OWNER_NOT_32_BYTES');
|
|
66
|
+
return v.length === 0 ? { ok: true } : { ok: false, violations: v };
|
|
67
|
+
}
|
|
68
|
+
function assertValidReadSpec(input) {
|
|
69
|
+
const r = validateReadSpec(input);
|
|
70
|
+
if (!r.ok) {
|
|
71
|
+
throw new errors_1.InvalidReadSpecError(r.violations, {
|
|
72
|
+
destination: `${input.spec.account.chainNamespace}:${input.spec.account.chainId}`,
|
|
73
|
+
hint: 'See ReadSpecViolation names — each mirrors a UniversalCallback revert.',
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function isHexLike(x) {
|
|
78
|
+
return typeof x === 'string' && /^0x[0-9a-fA-F]*$/.test(x);
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=validate.js.map
|
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
* See push-chain/x/uexecutor/types/keys.go:49-53 (GetInboundUniversalTxKey).
|
|
8
8
|
*
|
|
9
9
|
* For SVM sources, `<txHash>` is the 0x-prefixed hex encoding of the 64-byte
|
|
10
|
-
* Solana signature and `<logIndex>` is the event
|
|
10
|
+
* Solana signature and `<logIndex>` is the gateway-event ordinal. Current
|
|
11
|
+
* gateways derive it from authenticated `emit_cpi` inner instructions;
|
|
12
|
+
* legacy log-emitting gateways used the `meta.logMessages` index.
|
|
11
13
|
*
|
|
12
14
|
* For Push-initiated outbounds (pcTx entries) the formula drops the log index:
|
|
13
15
|
* utxId = sha256(`<pcChainCaip>:<pcTxHash>`)
|
|
@@ -14,7 +14,9 @@ exports.resolveChildInboundsFromLogs = resolveChildInboundsFromLogs;
|
|
|
14
14
|
* See push-chain/x/uexecutor/types/keys.go:49-53 (GetInboundUniversalTxKey).
|
|
15
15
|
*
|
|
16
16
|
* For SVM sources, `<txHash>` is the 0x-prefixed hex encoding of the 64-byte
|
|
17
|
-
* Solana signature and `<logIndex>` is the event
|
|
17
|
+
* Solana signature and `<logIndex>` is the gateway-event ordinal. Current
|
|
18
|
+
* gateways derive it from authenticated `emit_cpi` inner instructions;
|
|
19
|
+
* legacy log-emitting gateways used the `meta.logMessages` index.
|
|
18
20
|
*
|
|
19
21
|
* For Push-initiated outbounds (pcTx entries) the formula drops the log index:
|
|
20
22
|
* utxId = sha256(`<pcChainCaip>:<pcTxHash>`)
|
|
@@ -44,10 +46,7 @@ const OUTBOUND_STATUS_NAMES = {
|
|
|
44
46
|
2: 'OBSERVED',
|
|
45
47
|
3: 'REVERTED',
|
|
46
48
|
};
|
|
47
|
-
const INBOUND_EVENT_NAMES = new Set([
|
|
48
|
-
'UniversalTx',
|
|
49
|
-
'RevertUniversalTx',
|
|
50
|
-
]);
|
|
49
|
+
const INBOUND_EVENT_NAMES = new Set(['UniversalTx', 'RevertUniversalTx']);
|
|
51
50
|
const HEX_64_BYTE_RE = /^(?:0x)?[0-9a-fA-F]{128}$/;
|
|
52
51
|
function normalizeInboundTxHashForKey(sourceChainCaip, externalTxHash) {
|
|
53
52
|
if (!sourceChainCaip.startsWith('solana:')) {
|
|
@@ -6,18 +6,17 @@ const tslib_1 = require("tslib");
|
|
|
6
6
|
/**
|
|
7
7
|
* detectUniversalTxSvm — SVM counterpart to the EVM detector.
|
|
8
8
|
*
|
|
9
|
-
* Fetches the tx via Solana RPC
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* and hands the result to the same classifier used by the EVM branch.
|
|
9
|
+
* Fetches the tx via Solana RPC and decodes gateway events with Anchor's
|
|
10
|
+
* BorshEventCoder. Current gateways use authenticated `emit_cpi!` inner
|
|
11
|
+
* instructions; legacy `Program data:` log events remain as a fallback.
|
|
13
12
|
*
|
|
14
13
|
* Behavior deliberately mirrors detector.ts: same UniversalTxDetection
|
|
15
14
|
* return, same universalTxId derivation strategy (primary-log fallback
|
|
16
15
|
* via deriveChildUniversalTxId on source-chain inbounds).
|
|
17
16
|
*
|
|
18
17
|
* The Push keeper stores SVM inbound ids using the 0x-prefixed hex encoding
|
|
19
|
-
* of the 64-byte Solana signature and the
|
|
20
|
-
*
|
|
18
|
+
* of the 64-byte Solana signature and the gateway-event ordinal:
|
|
19
|
+
* `${caip}:${hexSig}:${eventIndex}`.
|
|
21
20
|
*/
|
|
22
21
|
const anchor_1 = require("@coral-xyz/anchor");
|
|
23
22
|
const web3_js_1 = require("@solana/web3.js");
|
|
@@ -86,17 +85,22 @@ async function detectUniversalTxSvm(signature, chain, opts = {}) {
|
|
|
86
85
|
}
|
|
87
86
|
const eventCoder = new anchor_1.BorshEventCoder(universalGatewayV0_json_1.default);
|
|
88
87
|
const matchingLogs = [];
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
88
|
+
const cpiPayloads = (0, svm_events_1.getSvmEventCpiPayloads)(tx, emitterBase58);
|
|
89
|
+
const eventPayloads = cpiPayloads.length > 0
|
|
90
|
+
? cpiPayloads.map(({ base64Data, eventIndex }) => ({
|
|
91
|
+
base64Data,
|
|
92
|
+
logIndex: eventIndex,
|
|
93
|
+
}))
|
|
94
|
+
: logMessages.flatMap((rawLog, logIndex) => {
|
|
95
|
+
const prefix = 'Program data: ';
|
|
96
|
+
return typeof rawLog === 'string' && rawLog.startsWith(prefix)
|
|
97
|
+
? [{ base64Data: rawLog.slice(prefix.length).trim(), logIndex }]
|
|
98
|
+
: [];
|
|
99
|
+
});
|
|
100
|
+
if (cpiPayloads.length > 0) {
|
|
101
|
+
notes.push('svm: decoded gateway events from emit_cpi inner instructions');
|
|
102
|
+
}
|
|
103
|
+
for (const { base64Data, logIndex } of eventPayloads) {
|
|
100
104
|
// Fast discriminator check — skip non-universal events without running
|
|
101
105
|
// the full Borsh decode.
|
|
102
106
|
let bytes;
|
|
@@ -141,7 +145,7 @@ async function detectUniversalTxSvm(signature, chain, opts = {}) {
|
|
|
141
145
|
notes.push(...classified.notes);
|
|
142
146
|
const decoded = { ...classified.decoded };
|
|
143
147
|
// Inbound fallback for universalTxId: same formula the EVM branch uses,
|
|
144
|
-
// with the canonical 0x-hex Solana signature and
|
|
148
|
+
// with the canonical 0x-hex Solana signature and gateway-event index.
|
|
145
149
|
if (!decoded.universalTxId) {
|
|
146
150
|
const caip = chain;
|
|
147
151
|
if (INBOUND_KINDS.has(classified.kind) &&
|
|
@@ -245,7 +249,9 @@ function coerceArgValue(srcKey, renamedKey, value) {
|
|
|
245
249
|
return (0, viem_1.bytesToHex)(new Uint8Array(value));
|
|
246
250
|
}
|
|
247
251
|
// BN from anchor — has toString().
|
|
248
|
-
if (value &&
|
|
252
|
+
if (value &&
|
|
253
|
+
typeof value === 'object' &&
|
|
254
|
+
typeof value.toString === 'function') {
|
|
249
255
|
const proto = Object.getPrototypeOf(value);
|
|
250
256
|
const ctorName = proto?.constructor?.name;
|
|
251
257
|
if (ctorName === 'BN') {
|
|
@@ -259,7 +265,10 @@ function coerceArgValue(srcKey, renamedKey, value) {
|
|
|
259
265
|
}
|
|
260
266
|
// Anchor enum → { [variantName]: {} }. tx_type must be surfaced as a
|
|
261
267
|
// numeric index to match classify.ts's setTxType expectations.
|
|
262
|
-
if (renamedKey === 'txType' &&
|
|
268
|
+
if (renamedKey === 'txType' &&
|
|
269
|
+
value &&
|
|
270
|
+
typeof value === 'object' &&
|
|
271
|
+
!Array.isArray(value)) {
|
|
263
272
|
return extractTxTypeIndex(value);
|
|
264
273
|
}
|
|
265
274
|
return value;
|
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* SVM-side universal-tx event metadata.
|
|
3
3
|
*
|
|
4
|
-
* Anchor programs emit events
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* converted to lowercase hex for matching.
|
|
4
|
+
* Current Anchor programs emit events through `emit_cpi!`: an inner
|
|
5
|
+
* instruction whose data is EVENT_IX_TAG || event discriminator || Borsh
|
|
6
|
+
* body. Older deployments emitted the discriminator + body as a base64
|
|
7
|
+
* `Program data:` log line. Helpers in this module support both transports.
|
|
9
8
|
*/
|
|
9
|
+
export declare const SVM_EVENT_IX_TAG: Uint8Array;
|
|
10
|
+
export interface SvmGatewayEventPayload {
|
|
11
|
+
/** Base64 discriminator + Borsh body accepted by BorshEventCoder. */
|
|
12
|
+
base64Data: string;
|
|
13
|
+
/** Gateway-event ordinal used by the Push keeper as log_index. */
|
|
14
|
+
eventIndex: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Extract authenticated gateway events from Anchor `emit_cpi!` inner
|
|
18
|
+
* instructions. Only self-CPIs whose program id equals `gatewayAddress` are
|
|
19
|
+
* accepted; matching a discriminator alone is not proof of the emitter.
|
|
20
|
+
*/
|
|
21
|
+
export declare function getSvmEventCpiPayloads(txResp: any, gatewayAddress: string): SvmGatewayEventPayload[];
|
|
10
22
|
/**
|
|
11
23
|
* Mapping: 8-byte discriminator (lowercase hex, no 0x) → Anchor event name.
|
|
12
24
|
* Only events with EVM classifier parity are listed; the keeper-agnostic
|
|
@@ -2,15 +2,90 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* SVM-side universal-tx event metadata.
|
|
4
4
|
*
|
|
5
|
-
* Anchor programs emit events
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* converted to lowercase hex for matching.
|
|
5
|
+
* Current Anchor programs emit events through `emit_cpi!`: an inner
|
|
6
|
+
* instruction whose data is EVENT_IX_TAG || event discriminator || Borsh
|
|
7
|
+
* body. Older deployments emitted the discriminator + body as a base64
|
|
8
|
+
* `Program data:` log line. Helpers in this module support both transports.
|
|
10
9
|
*/
|
|
11
10
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
exports.SVM_EVENT_FIELD_ALIASES = exports.SVM_FIELD_RENAMES = exports.SVM_EVENT_DISCRIMINATORS = void 0;
|
|
11
|
+
exports.SVM_EVENT_FIELD_ALIASES = exports.SVM_FIELD_RENAMES = exports.SVM_EVENT_DISCRIMINATORS = exports.SVM_EVENT_IX_TAG = void 0;
|
|
12
|
+
exports.getSvmEventCpiPayloads = getSvmEventCpiPayloads;
|
|
13
13
|
exports.discriminatorHex = discriminatorHex;
|
|
14
|
+
const web3_js_1 = require("@solana/web3.js");
|
|
15
|
+
const bs58_1 = require("../internal/bs58");
|
|
16
|
+
exports.SVM_EVENT_IX_TAG = Uint8Array.from([
|
|
17
|
+
0xe4, 0x45, 0xa5, 0x2e, 0x51, 0xcb, 0x9a, 0x1d,
|
|
18
|
+
]);
|
|
19
|
+
/**
|
|
20
|
+
* Extract authenticated gateway events from Anchor `emit_cpi!` inner
|
|
21
|
+
* instructions. Only self-CPIs whose program id equals `gatewayAddress` are
|
|
22
|
+
* accepted; matching a discriminator alone is not proof of the emitter.
|
|
23
|
+
*/
|
|
24
|
+
function getSvmEventCpiPayloads(txResp, gatewayAddress) {
|
|
25
|
+
const groups = txResp?.meta?.innerInstructions;
|
|
26
|
+
if (!Array.isArray(groups) || groups.length === 0)
|
|
27
|
+
return [];
|
|
28
|
+
let gateway;
|
|
29
|
+
try {
|
|
30
|
+
gateway = new web3_js_1.PublicKey(gatewayAddress);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
35
|
+
const message = txResp?.transaction?.message;
|
|
36
|
+
const loadedAddresses = txResp?.meta?.loadedAddresses;
|
|
37
|
+
const accountKeyAt = (index) => {
|
|
38
|
+
try {
|
|
39
|
+
if (typeof message?.getAccountKeys === 'function') {
|
|
40
|
+
return message
|
|
41
|
+
.getAccountKeys({
|
|
42
|
+
accountKeysFromLookups: loadedAddresses,
|
|
43
|
+
})
|
|
44
|
+
.get(index);
|
|
45
|
+
}
|
|
46
|
+
const keys = [
|
|
47
|
+
...(message?.accountKeys ?? message?.staticAccountKeys ?? []),
|
|
48
|
+
...(loadedAddresses?.writable ?? []),
|
|
49
|
+
...(loadedAddresses?.readonly ?? loadedAddresses?.readOnly ?? []),
|
|
50
|
+
];
|
|
51
|
+
const key = keys[index];
|
|
52
|
+
return key instanceof web3_js_1.PublicKey
|
|
53
|
+
? key
|
|
54
|
+
: key
|
|
55
|
+
? new web3_js_1.PublicKey(key)
|
|
56
|
+
: undefined;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
const payloads = [];
|
|
63
|
+
for (const group of groups) {
|
|
64
|
+
for (const instruction of group?.instructions ?? []) {
|
|
65
|
+
const programId = accountKeyAt(Number(instruction?.programIdIndex));
|
|
66
|
+
if (!programId?.equals(gateway) ||
|
|
67
|
+
typeof instruction?.data !== 'string') {
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
let raw;
|
|
71
|
+
try {
|
|
72
|
+
raw = bs58_1.bs58.decode(instruction.data);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
if (raw.length < exports.SVM_EVENT_IX_TAG.length + 8 ||
|
|
78
|
+
!exports.SVM_EVENT_IX_TAG.every((byte, i) => raw[i] === byte)) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
payloads.push({
|
|
82
|
+
base64Data: Buffer.from(raw.subarray(exports.SVM_EVENT_IX_TAG.length)).toString('base64'),
|
|
83
|
+
eventIndex: payloads.length,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return payloads;
|
|
88
|
+
}
|
|
14
89
|
const toHex = (bytes) => bytes.map((b) => b.toString(16).padStart(2, '0')).join('');
|
|
15
90
|
/**
|
|
16
91
|
* Mapping: 8-byte discriminator (lowercase hex, no 0x) → Anchor event name.
|
|
@@ -202,18 +202,35 @@ class EvmClient {
|
|
|
202
202
|
* console.log(`ETH transfer sent: ${txHash}`);
|
|
203
203
|
*/
|
|
204
204
|
async sendTransaction({ to, data, value = BigInt(0), signer, nonce: explicitNonce, gas: explicitGas, }) {
|
|
205
|
+
const resolveGas = async () => {
|
|
206
|
+
if (explicitGas)
|
|
207
|
+
return explicitGas;
|
|
208
|
+
const estimate = () => this.publicClient.estimateGas({
|
|
209
|
+
account: signer.account.address,
|
|
210
|
+
to,
|
|
211
|
+
data,
|
|
212
|
+
value,
|
|
213
|
+
});
|
|
214
|
+
if (data !== '0x')
|
|
215
|
+
return estimate();
|
|
216
|
+
// A plain native transfer to a code-free EOA always costs 21k. Empty
|
|
217
|
+
// calldata is not sufficient to identify that case, though: contracts
|
|
218
|
+
// and EIP-7702 delegated accounts still execute code and need estimation.
|
|
219
|
+
// If the code lookup is unavailable, prefer estimation over submitting
|
|
220
|
+
// a transaction that can deterministically run out of gas.
|
|
221
|
+
try {
|
|
222
|
+
const targetCode = await this.publicClient.getCode({ address: to });
|
|
223
|
+
if (!targetCode || targetCode === '0x')
|
|
224
|
+
return BigInt(21000);
|
|
225
|
+
}
|
|
226
|
+
catch {
|
|
227
|
+
// Fall through to estimateGas.
|
|
228
|
+
}
|
|
229
|
+
return estimate();
|
|
230
|
+
};
|
|
205
231
|
// Estimate gas and fees first (can run in parallel)
|
|
206
232
|
const [gas, feePerGas, chainId] = await Promise.all([
|
|
207
|
-
|
|
208
|
-
? Promise.resolve(explicitGas)
|
|
209
|
-
: data === '0x'
|
|
210
|
-
? Promise.resolve(BigInt(21000))
|
|
211
|
-
: this.publicClient.estimateGas({
|
|
212
|
-
account: signer.account.address,
|
|
213
|
-
to,
|
|
214
|
-
data,
|
|
215
|
-
value,
|
|
216
|
-
}),
|
|
233
|
+
resolveGas(),
|
|
217
234
|
this.publicClient.estimateFeesPerGas(),
|
|
218
235
|
this.publicClient.getChainId(),
|
|
219
236
|
]);
|