@pushchain/core 6.0.23 → 6.0.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/CHANGELOG.md +84 -0
  2. package/package.json +1 -1
  3. package/src/lib/constants/abi/index.d.ts +1 -0
  4. package/src/lib/constants/abi/index.js +4 -1
  5. package/src/lib/constants/abi/prc20.evm.d.ts +32 -0
  6. package/src/lib/constants/abi/prc20.evm.js +20 -0
  7. package/src/lib/constants/abi/uea-factory.d.ts +31 -0
  8. package/src/lib/constants/abi/uea-factory.js +19 -0
  9. package/src/lib/constants/abi/universalCallback.evm.d.ts +561 -0
  10. package/src/lib/constants/abi/universalCallback.evm.js +247 -0
  11. package/src/lib/constants/abi/universalGatewayV0.json +1 -1
  12. package/src/lib/constants/abi/universalReadRegistry.evm.d.ts +247 -0
  13. package/src/lib/constants/abi/universalReadRegistry.evm.js +38 -0
  14. package/src/lib/constants/chain.d.ts +12 -0
  15. package/src/lib/constants/chain.js +23 -1
  16. package/src/lib/constants/enums.d.ts +7 -0
  17. package/src/lib/constants/enums.js +11 -0
  18. package/src/lib/constants/index.d.ts +52 -0
  19. package/src/lib/constants/index.js +37 -8
  20. package/src/lib/constants/read-state.d.ts +53 -0
  21. package/src/lib/constants/read-state.js +59 -0
  22. package/src/lib/generated/ucallback/v1/index.d.ts +2 -0
  23. package/src/lib/generated/ucallback/v1/index.js +6 -0
  24. package/src/lib/generated/ucallback/v1/query.d.ts +47 -0
  25. package/src/lib/generated/ucallback/v1/query.js +131 -0
  26. package/src/lib/generated/ucallback/v1/types.d.ts +99 -0
  27. package/src/lib/generated/ucallback/v1/types.js +366 -0
  28. package/src/lib/generated/uexecutor/v1/query.d.ts +29 -0
  29. package/src/lib/generated/uexecutor/v1/query.js +98 -1
  30. package/src/lib/index.d.ts +9 -0
  31. package/src/lib/index.js +37 -1
  32. package/src/lib/orchestrator/internals/cascade.js +5 -2
  33. package/src/lib/orchestrator/internals/errors.d.ts +6 -0
  34. package/src/lib/orchestrator/internals/errors.js +13 -1
  35. package/src/lib/orchestrator/internals/index.d.ts +1 -0
  36. package/src/lib/orchestrator/internals/index.js +6 -1
  37. package/src/lib/orchestrator/internals/push-chain-tx.js +81 -61
  38. package/src/lib/orchestrator/internals/read-state.d.ts +24 -0
  39. package/src/lib/orchestrator/internals/read-state.js +47 -0
  40. package/src/lib/orchestrator/internals/route-handlers.js +6 -1
  41. package/src/lib/orchestrator/internals/svm-helpers.js +24 -0
  42. package/src/lib/orchestrator/orchestrator.d.ts +26 -0
  43. package/src/lib/orchestrator/orchestrator.js +22 -0
  44. package/src/lib/orchestrator/orchestrator.types.d.ts +2 -0
  45. package/src/lib/orchestrator/route-detector.js +4 -0
  46. package/src/lib/progress-hook/progress-hook.js +271 -0
  47. package/src/lib/progress-hook/progress-hook.types.d.ts +32 -0
  48. package/src/lib/progress-hook/progress-hook.types.js +34 -0
  49. package/src/lib/push-chain/push-chain.d.ts +70 -0
  50. package/src/lib/push-chain/push-chain.js +27 -0
  51. package/src/lib/push-client/push-client.d.ts +35 -2
  52. package/src/lib/push-client/push-client.js +117 -4
  53. package/src/lib/read-state/budget.d.ts +16 -0
  54. package/src/lib/read-state/budget.js +32 -0
  55. package/src/lib/read-state/destination.d.ts +12 -0
  56. package/src/lib/read-state/destination.js +51 -0
  57. package/src/lib/read-state/envelopes/evm.d.ts +24 -0
  58. package/src/lib/read-state/envelopes/evm.js +148 -0
  59. package/src/lib/read-state/envelopes/index.d.ts +12 -0
  60. package/src/lib/read-state/envelopes/index.js +45 -0
  61. package/src/lib/read-state/envelopes/svm.d.ts +32 -0
  62. package/src/lib/read-state/envelopes/svm.js +107 -0
  63. package/src/lib/read-state/envelopes/web2.d.ts +29 -0
  64. package/src/lib/read-state/envelopes/web2.js +149 -0
  65. package/src/lib/read-state/errors.d.ts +70 -0
  66. package/src/lib/read-state/errors.js +116 -0
  67. package/src/lib/read-state/index.d.ts +13 -0
  68. package/src/lib/read-state/index.js +38 -0
  69. package/src/lib/read-state/preflight.d.ts +21 -0
  70. package/src/lib/read-state/preflight.js +70 -0
  71. package/src/lib/read-state/read-events.d.ts +27 -0
  72. package/src/lib/read-state/read-events.js +135 -0
  73. package/src/lib/read-state/read-executor.d.ts +18 -0
  74. package/src/lib/read-state/read-executor.js +212 -0
  75. package/src/lib/read-state/read-params.d.ts +205 -0
  76. package/src/lib/read-state/read-params.js +120 -0
  77. package/src/lib/read-state/read-state.types.d.ts +417 -0
  78. package/src/lib/read-state/read-state.types.js +50 -0
  79. package/src/lib/read-state/read-tracker.d.ts +50 -0
  80. package/src/lib/read-state/read-tracker.js +415 -0
  81. package/src/lib/read-state/registry.d.ts +21 -0
  82. package/src/lib/read-state/registry.js +54 -0
  83. package/src/lib/read-state/result-decoder.d.ts +16 -0
  84. package/src/lib/read-state/result-decoder.js +73 -0
  85. package/src/lib/read-state/spec-builder.d.ts +47 -0
  86. package/src/lib/read-state/spec-builder.js +251 -0
  87. package/src/lib/read-state/validate.d.ts +22 -0
  88. package/src/lib/read-state/validate.js +80 -0
  89. package/src/lib/universal-tx-detector/child-inbounds.d.ts +3 -1
  90. package/src/lib/universal-tx-detector/child-inbounds.js +4 -5
  91. package/src/lib/universal-tx-detector/detector-svm.js +29 -20
  92. package/src/lib/universal-tx-detector/svm-events.d.ts +17 -5
  93. package/src/lib/universal-tx-detector/svm-events.js +81 -6
  94. package/src/lib/vm-client/evm-client.js +27 -10
@@ -8,6 +8,7 @@ const proto_signing_1 = require("@cosmjs/proto-signing");
8
8
  const stargate_1 = require("@cosmjs/stargate");
9
9
  const query_1 = require("../generated/uexecutor/v1/query");
10
10
  const query_2 = require("../generated/uexecutor/v2/query");
11
+ const v1_1 = require("../generated/ucallback/v1");
11
12
  const crypto_1 = require("@cosmjs/crypto");
12
13
  const tendermint_rpc_1 = require("@cosmjs/tendermint-rpc");
13
14
  const auth_1 = require("cosmjs-types/cosmos/auth/v1beta1/auth");
@@ -24,13 +25,21 @@ function pushNetworkToChain(network) {
24
25
  return enums_1.CHAIN.PUSH_TESTNET_DONUT;
25
26
  return enums_1.CHAIN.PUSH_LOCALNET;
26
27
  }
28
+ /** ABCI code 22 / gRPC NotFound from x/ucallback — "no read request with id …: key not found". */
29
+ function isUcallbackNotFound(err) {
30
+ const msg = err instanceof Error ? err.message : String(err);
31
+ return /\(22\)|code = NotFound|key not found/i.test(msg);
32
+ }
27
33
  class PushClient extends evm_client_1.EvmClient {
28
- /** Gas limit for Cosmos transactions on Push Chain */
29
- static COSMOS_GAS_LIMIT = 100000000000;
34
+ /** Conservative SDK ceiling and fallback for gasless Cosmos transactions. */
35
+ static DEFAULT_COSMOS_GAS_LIMIT = 100_000_000;
36
+ static GASLESS_GAS_LIMIT_CACHE_TTL_MS = 60_000;
37
+ static MSG_EXECUTE_PAYLOAD_TYPE_URL = '/uexecutor.v1.MsgExecutePayload';
30
38
  pushChainInfo;
31
39
  ephemeralKey;
32
40
  ephemeralAccount;
33
41
  currentRpcIndex = 0;
42
+ gaslessGasLimitCache;
34
43
  /**
35
44
  * Archive (full-history) endpoints. History-sensitive reads fall back from
36
45
  * the prune RPC to these on a pruned-history miss. Resolved from the chain's
@@ -215,6 +224,7 @@ class PushClient extends evm_client_1.EvmClient {
215
224
  const status = await tmClient.status();
216
225
  const chainId = status.nodeInfo.network;
217
226
  const queryClient = stargate_1.QueryClient.withExtensions(tmClient, stargate_1.setupAuthExtension);
227
+ const cosmosGasLimit = await this.resolveCosmosGasLimit(txBody, queryClient, chainId);
218
228
  let baseAccount = null;
219
229
  try {
220
230
  const accountResp = await queryClient.auth.account(sender);
@@ -245,7 +255,7 @@ class PushClient extends evm_client_1.EvmClient {
245
255
  pubkey: pubkeyEncoded,
246
256
  sequence: baseAccount ? Number(baseAccount.sequence) : 0,
247
257
  },
248
- ], [], PushClient.COSMOS_GAS_LIMIT, undefined, undefined);
258
+ ], [], cosmosGasLimit, undefined, undefined);
249
259
  const txBodyBytes = tx_2.TxBody.encode(txBody).finish();
250
260
  const signDoc = (0, proto_signing_1.makeSignDoc)(txBodyBytes, authInfoBytes, chainId, baseAccount ? Number(baseAccount.accountNumber) : 0);
251
261
  const digest = (0, viem_1.keccak256)(tx_2.SignDoc.encode(signDoc).finish());
@@ -257,6 +267,42 @@ class PushClient extends evm_client_1.EvmClient {
257
267
  });
258
268
  }, 'signCosmosTx');
259
269
  }
270
+ async resolveCosmosGasLimit(txBody, queryClient, chainId) {
271
+ const isGaslessExecutePayload = txBody.messages.length > 0 &&
272
+ txBody.messages.every(({ typeUrl }) => typeUrl === PushClient.MSG_EXECUTE_PAYLOAD_TYPE_URL);
273
+ if (!isGaslessExecutePayload) {
274
+ return PushClient.DEFAULT_COSMOS_GAS_LIMIT;
275
+ }
276
+ const cached = this.gaslessGasLimitCache;
277
+ if (cached?.chainId === chainId && cached.expiresAt > Date.now()) {
278
+ return cached.gasLimit;
279
+ }
280
+ try {
281
+ const rpc = (0, stargate_1.createProtobufRpcClient)(queryClient);
282
+ const request = query_1.QueryParamsRequest.fromPartial({});
283
+ const responseBytes = await rpc.request('uexecutor.v1.Query', 'Params', query_1.QueryParamsRequest.encode(request).finish());
284
+ const response = query_1.QueryParamsResponse.decode(responseBytes);
285
+ const onChainMaximum = response.params?.maxGaslessTxGas ?? BigInt(0);
286
+ if (onChainMaximum > BigInt(0)) {
287
+ const selected = onChainMaximum < BigInt(PushClient.DEFAULT_COSMOS_GAS_LIMIT)
288
+ ? onChainMaximum
289
+ : BigInt(PushClient.DEFAULT_COSMOS_GAS_LIMIT);
290
+ const gasLimit = Number(selected);
291
+ if (Number.isSafeInteger(gasLimit) && gasLimit > 0) {
292
+ this.gaslessGasLimitCache = {
293
+ chainId,
294
+ gasLimit,
295
+ expiresAt: Date.now() + PushClient.GASLESS_GAS_LIMIT_CACHE_TTL_MS,
296
+ };
297
+ return gasLimit;
298
+ }
299
+ }
300
+ }
301
+ catch {
302
+ // Older/unavailable query endpoints fall back to the current safe cap.
303
+ }
304
+ return PushClient.DEFAULT_COSMOS_GAS_LIMIT;
305
+ }
260
306
  async broadcastCosmosTx(txRaw) {
261
307
  return this.executeWithRpcFallback(async (rpcUrl) => {
262
308
  const client = await stargate_1.StargateClient.connect(rpcUrl);
@@ -276,6 +322,70 @@ class PushClient extends evm_client_1.EvmClient {
276
322
  return query_1.QueryGetUniversalTxResponse.decode(responseBytes);
277
323
  }, 'getUniversalTxById');
278
324
  }
325
+ /**
326
+ * Queries x/ucallback for one cross-chain read by its requestId (0x-prefixed uint256 hex).
327
+ *
328
+ * Current-state query — an absent `read` is a real "does not exist", so there is
329
+ * deliberately no archive fallback (see getUniversalTxByIdV2 for the rationale).
330
+ */
331
+ async getUniversalRead(requestId) {
332
+ const id = requestId.toLowerCase();
333
+ return this.executeWithRpcFallback(async (rpcUrl) => {
334
+ const tmClient = await tendermint_rpc_1.Tendermint34Client.connect(rpcUrl);
335
+ const queryClient = new stargate_1.QueryClient(tmClient);
336
+ const rpc = (0, stargate_1.createProtobufRpcClient)(queryClient);
337
+ const request = v1_1.QueryUniversalReadRequest.fromPartial({ requestId: id });
338
+ try {
339
+ const responseBytes = await rpc.request(v1_1.UCALLBACK_QUERY_SERVICE, 'UniversalRead', v1_1.QueryUniversalReadRequest.encode(request).finish());
340
+ return v1_1.QueryUniversalReadResponse.decode(responseBytes);
341
+ }
342
+ catch (err) {
343
+ // The node answers an unknown id with ABCI code 22 (sdk NotFound), which cosmjs
344
+ // throws. That is a deterministic miss, not a transport failure — classify it
345
+ // here so the RPC fallback does not retry it on every endpoint.
346
+ if (isUcallbackNotFound(err))
347
+ return v1_1.QueryUniversalReadResponse.fromPartial({});
348
+ throw err;
349
+ }
350
+ }, 'getUniversalRead');
351
+ }
352
+ /**
353
+ * Cosmos `block_results` for one height, raw over the Tendermint HTTP RPC. Push Chain
354
+ * runs CometBFT 0.38 and emits `finalize_block_events`, which the cosmjs 0.33 decoders
355
+ * still drop (they only read `end_block_events`) — hence the direct call. This is the
356
+ * only place the EndBlocker's work is visible: read-state expiry runs there and leaves
357
+ * no EVM-indexed tx or receipt. Attribute values are plain strings.
358
+ */
359
+ async getBlockResultEvents(height) {
360
+ return this.executeWithRpcFallback(async (rpcUrl) => {
361
+ const url = `${rpcUrl.replace(/\/+$/, '')}/block_results?height=${height}`;
362
+ const res = await fetch(url);
363
+ if (!res.ok)
364
+ throw new Error(`HTTP request failed: ${res.status} ${url}`);
365
+ const body = (await res.json());
366
+ if (body.error)
367
+ throw new Error(`block_results ${height}: ${body.error.data ?? body.error.message ?? 'error'}`);
368
+ const events = body.result?.finalize_block_events ?? body.result?.end_block_events ?? [];
369
+ return events;
370
+ }, 'getBlockResultEvents');
371
+ }
372
+ /**
373
+ * Queries x/ucallback for every read a Push transaction requested. One tx can emit
374
+ * several ReadRequested logs; this reassembles that batch. The node keys on the exact
375
+ * lowercase 0x hash, which is what the SDK's own tx responses carry — verified for
376
+ * both EOA and UEA-originated requests.
377
+ */
378
+ async getReadsByTx(txHash) {
379
+ const hash = txHash.toLowerCase();
380
+ return this.executeWithRpcFallback(async (rpcUrl) => {
381
+ const tmClient = await tendermint_rpc_1.Tendermint34Client.connect(rpcUrl);
382
+ const queryClient = new stargate_1.QueryClient(tmClient);
383
+ const rpc = (0, stargate_1.createProtobufRpcClient)(queryClient);
384
+ const request = v1_1.QueryReadsByTxRequest.fromPartial({ txHash: hash });
385
+ const responseBytes = await rpc.request(v1_1.UCALLBACK_QUERY_SERVICE, 'ReadsByTx', v1_1.QueryReadsByTxRequest.encode(request).finish());
386
+ return v1_1.QueryReadsByTxResponse.decode(responseBytes);
387
+ }, 'getReadsByTx');
388
+ }
279
389
  /**
280
390
  * Queries Push Chain's uexecutor v2 gRPC service for a UniversalTx by its ID.
281
391
  * V2 returns expanded OutboundTx fields and repeated outbound_tx array.
@@ -356,7 +466,10 @@ class PushClient extends evm_client_1.EvmClient {
356
466
  throw error;
357
467
  const archiveClient = this.archivePublicClient;
358
468
  const tx = await archiveClient.getTransaction({ hash: txHash });
359
- const wait = async (confirmations = 1) => archiveClient.waitForTransactionReceipt({ hash: txHash, confirmations });
469
+ const wait = async (confirmations = 1) => archiveClient.waitForTransactionReceipt({
470
+ hash: txHash,
471
+ confirmations,
472
+ });
360
473
  return { ...tx, wait };
361
474
  }
362
475
  }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Gas price assumed when the chain reports 0 (dev chains). Donut charges 1 gwei per
3
+ * gas on module fulfil txs (verified: 118,289 gas → 118,289 × 10⁹ wei burned).
4
+ */
5
+ export declare const MIN_CALLBACK_BUDGET_GAS_PRICE = 1000000000n;
6
+ /**
7
+ * Size the callback budget escrowed on top of the protocol fee.
8
+ *
9
+ * msg.value = protocolFee + callbackBudget
10
+ *
11
+ * The node refuses to fulfil a read whose budget cannot cover its callback
12
+ * ("callback budget too small") and lets it expire with the fee already spent, so a
13
+ * zero or thin budget is a guaranteed loss. This errs high by `buffer`; whatever the
14
+ * callback does not burn is pushed back to `refundTo` at settlement.
15
+ */
16
+ export declare function sizeCallbackBudget(callbackGasLimit: bigint, pushGasPrice: bigint, buffer?: number): bigint;
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MIN_CALLBACK_BUDGET_GAS_PRICE = void 0;
4
+ exports.sizeCallbackBudget = sizeCallbackBudget;
5
+ const read_state_1 = require("../constants/read-state");
6
+ const errors_1 = require("./errors");
7
+ /**
8
+ * Gas price assumed when the chain reports 0 (dev chains). Donut charges 1 gwei per
9
+ * gas on module fulfil txs (verified: 118,289 gas → 118,289 × 10⁹ wei burned).
10
+ */
11
+ exports.MIN_CALLBACK_BUDGET_GAS_PRICE = 1000000000n;
12
+ /**
13
+ * Size the callback budget escrowed on top of the protocol fee.
14
+ *
15
+ * msg.value = protocolFee + callbackBudget
16
+ *
17
+ * The node refuses to fulfil a read whose budget cannot cover its callback
18
+ * ("callback budget too small") and lets it expire with the fee already spent, so a
19
+ * zero or thin budget is a guaranteed loss. This errs high by `buffer`; whatever the
20
+ * callback does not burn is pushed back to `refundTo` at settlement.
21
+ */
22
+ function sizeCallbackBudget(callbackGasLimit, pushGasPrice, buffer = read_state_1.CALLBACK_BUDGET_BUFFER) {
23
+ if (callbackGasLimit <= 0n)
24
+ throw new errors_1.InvalidReadQueryError('callbackGasLimit must be > 0');
25
+ if (!Number.isInteger(buffer) || buffer < 1)
26
+ throw new errors_1.InvalidReadQueryError('budget buffer must be an integer ≥ 1');
27
+ const price = pushGasPrice > 0n ? pushGasPrice : exports.MIN_CALLBACK_BUDGET_GAS_PRICE;
28
+ const budget = callbackGasLimit * price * BigInt(buffer);
29
+ // cannot be zero by construction, but the invariant is worth stating
30
+ return budget > 0n ? budget : 1n;
31
+ }
32
+ //# sourceMappingURL=budget.js.map
@@ -0,0 +1,12 @@
1
+ import type { ReadDestination, ResolvedDestination } from './read-state.types';
2
+ /**
3
+ * Split a destination into the pieces the contract, the oracle and the node use.
4
+ *
5
+ * - `ReadSpec.account.chainNamespace` / `chainId` are the two halves.
6
+ * - `UniversalCore.chainHeightByChainNamespace` is keyed by the JOINED `caip2`.
7
+ * - The node routes on `chainNamespace + ":" + chainId`.
8
+ *
9
+ * Passing the joined form as the namespace would produce `eip155:1:1` on the node,
10
+ * so it is rejected here.
11
+ */
12
+ export declare function resolveDestination(dest: ReadDestination): ResolvedDestination;
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveDestination = resolveDestination;
4
+ const read_state_1 = require("../constants/read-state");
5
+ const errors_1 = require("./errors");
6
+ const KNOWN = new Set(Object.values(read_state_1.READ_NAMESPACE));
7
+ /**
8
+ * Split a destination into the pieces the contract, the oracle and the node use.
9
+ *
10
+ * - `ReadSpec.account.chainNamespace` / `chainId` are the two halves.
11
+ * - `UniversalCore.chainHeightByChainNamespace` is keyed by the JOINED `caip2`.
12
+ * - The node routes on `chainNamespace + ":" + chainId`.
13
+ *
14
+ * Passing the joined form as the namespace would produce `eip155:1:1` on the node,
15
+ * so it is rejected here.
16
+ */
17
+ function resolveDestination(dest) {
18
+ let chainNamespace;
19
+ let chainId;
20
+ if ('chain' in dest) {
21
+ const idx = dest.chain.indexOf(':');
22
+ if (idx <= 0) {
23
+ throw new errors_1.UnsupportedReadDestinationError(`chain is not CAIP-2: ${dest.chain}`);
24
+ }
25
+ chainNamespace = dest.chain.slice(0, idx);
26
+ chainId = dest.chain.slice(idx + 1);
27
+ }
28
+ else {
29
+ chainNamespace = dest.chainNamespace;
30
+ chainId = dest.chainId;
31
+ }
32
+ if (!chainNamespace || !chainId) {
33
+ throw new errors_1.UnsupportedReadDestinationError('destination needs both chainNamespace and chainId');
34
+ }
35
+ if (chainNamespace.includes(':')) {
36
+ throw new errors_1.UnsupportedReadDestinationError(`chainNamespace must be the bare namespace, not CAIP-2: ${chainNamespace}`, { hint: 'Pass { chainNamespace: "eip155", chainId: "1" } or { chain: CHAIN.X }.' });
37
+ }
38
+ if (!KNOWN.has(chainNamespace)) {
39
+ throw new errors_1.UnsupportedReadDestinationError(`unsupported read namespace: ${chainNamespace}`, {
40
+ destination: `${chainNamespace}:${chainId}`,
41
+ hint: `Supported: ${[...KNOWN].join(', ')}`,
42
+ });
43
+ }
44
+ return {
45
+ chainNamespace,
46
+ chainId,
47
+ caip2: `${chainNamespace}:${chainId}`,
48
+ namespace: chainNamespace,
49
+ };
50
+ }
51
+ //# sourceMappingURL=destination.js.map
@@ -0,0 +1,24 @@
1
+ import { type Hex } from 'viem';
2
+ import type { EncodedReadQuery, EvmReadQuery } from '../read-state.types';
3
+ /** `universalClient/externalchains/evm/read_envelope.go` — evmQueryType. */
4
+ export declare const EVM_QUERY_TYPE: {
5
+ readonly ACCOUNT_BALANCE: 0;
6
+ readonly CONTRACT_CALL: 1;
7
+ readonly STORAGE_SLOT: 2;
8
+ };
9
+ /** Normalise a storage slot (hex or bigint) to bytes32. */
10
+ export declare function toStorageSlot(slot: Hex | bigint): Hex;
11
+ /**
12
+ * Encode an `eip155` read query. Pure — `blockNumber` is the pinned destination
13
+ * height and is also mirrored into `ReadSpec.blockNumber` by the spec builder.
14
+ */
15
+ export declare function encodeEvmQueryEnvelope(query: EvmReadQuery, options: {
16
+ blockNumber: bigint;
17
+ }): EncodedReadQuery;
18
+ /** Decode an envelope with the exact tuple shape the validator uses. */
19
+ export declare function decodeEvmQueryEnvelope(encoded: Hex): {
20
+ queryType: number;
21
+ refType: number;
22
+ blockNumber: bigint;
23
+ payload: Hex;
24
+ };
@@ -0,0 +1,148 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EVM_QUERY_TYPE = void 0;
4
+ exports.toStorageSlot = toStorageSlot;
5
+ exports.encodeEvmQueryEnvelope = encodeEvmQueryEnvelope;
6
+ exports.decodeEvmQueryEnvelope = decodeEvmQueryEnvelope;
7
+ const viem_1 = require("viem");
8
+ const read_state_1 = require("../../constants/read-state");
9
+ const errors_1 = require("../errors");
10
+ /** `universalClient/externalchains/evm/read_envelope.go` — evmQueryType. */
11
+ exports.EVM_QUERY_TYPE = {
12
+ ACCOUNT_BALANCE: 0,
13
+ CONTRACT_CALL: 1,
14
+ STORAGE_SLOT: 2,
15
+ };
16
+ /** Only AT_NUMBER exists in v1. */
17
+ const EVM_BLOCK_REF_AT_NUMBER = 0;
18
+ /**
19
+ * The validator unpacks `abi.Arguments{ ONE tuple }`. The envelope MUST therefore be
20
+ * `abi.encode(struct)` — a single dynamic tuple with a 0x20 head offset — never the
21
+ * fields as separate parameters. The three-parameter layout parses only by coincidence
22
+ * when queryType == 0 and cost the first live read (2026-09-09).
23
+ */
24
+ const EVM_ENVELOPE_ABI = [
25
+ {
26
+ type: 'tuple',
27
+ components: [
28
+ { name: 'queryType', type: 'uint8' },
29
+ {
30
+ name: 'blockRef',
31
+ type: 'tuple',
32
+ components: [
33
+ { name: 'refType', type: 'uint8' },
34
+ { name: 'blockNumber', type: 'uint64' },
35
+ ],
36
+ },
37
+ { name: 'payload', type: 'bytes' },
38
+ ],
39
+ },
40
+ ];
41
+ const UINT64_MAX = (1n << 64n) - 1n;
42
+ function assertAddress(value, what) {
43
+ if (!(0, viem_1.isAddress)(value))
44
+ throw new errors_1.InvalidReadQueryError(`${what} is not an EVM address: ${value}`);
45
+ }
46
+ function assertBlockNumber(blockNumber) {
47
+ if (blockNumber < 0n || blockNumber > UINT64_MAX) {
48
+ throw new errors_1.InvalidReadQueryError(`blockNumber out of uint64 range: ${blockNumber}`);
49
+ }
50
+ }
51
+ /** Normalise a storage slot (hex or bigint) to bytes32. */
52
+ function toStorageSlot(slot) {
53
+ if (typeof slot === 'bigint') {
54
+ if (slot < 0n || slot >= 1n << 256n)
55
+ throw new errors_1.InvalidReadQueryError(`storage slot out of range: ${slot}`);
56
+ return (0, viem_1.toHex)(slot, { size: 32 });
57
+ }
58
+ if (!(0, viem_1.isHex)(slot))
59
+ throw new errors_1.InvalidReadQueryError(`storage slot is not hex: ${slot}`);
60
+ if ((0, viem_1.size)(slot) > 32)
61
+ throw new errors_1.InvalidReadQueryError(`storage slot longer than 32 bytes: ${slot}`);
62
+ return (0, viem_1.pad)(slot, { size: 32 });
63
+ }
64
+ function encodeEnvelope(queryType, blockNumber, payload) {
65
+ return (0, viem_1.encodeAbiParameters)(EVM_ENVELOPE_ABI, [
66
+ { queryType, blockRef: { refType: EVM_BLOCK_REF_AT_NUMBER, blockNumber }, payload },
67
+ ]);
68
+ }
69
+ /**
70
+ * Encode an `eip155` read query. Pure — `blockNumber` is the pinned destination
71
+ * height and is also mirrored into `ReadSpec.blockNumber` by the spec builder.
72
+ */
73
+ function encodeEvmQueryEnvelope(query, options) {
74
+ const { blockNumber } = options;
75
+ assertBlockNumber(blockNumber);
76
+ assertAddress(query.target, 'target');
77
+ let queryType;
78
+ let payload;
79
+ let resultShape;
80
+ switch (query.type) {
81
+ case 'accountBalance': {
82
+ queryType = exports.EVM_QUERY_TYPE.ACCOUNT_BALANCE;
83
+ // abi.encode(address) — 32 bytes. The raw 20-byte address is rejected by validators.
84
+ payload = (0, viem_1.encodeAbiParameters)([{ type: 'address' }], [query.target]);
85
+ resultShape = { kind: 'uint256' };
86
+ break;
87
+ }
88
+ case 'contractCall': {
89
+ queryType = exports.EVM_QUERY_TYPE.CONTRACT_CALL;
90
+ let callData;
91
+ if ('callData' in query) {
92
+ if (!(0, viem_1.isHex)(query.callData) || (0, viem_1.size)(query.callData) < 4) {
93
+ throw new errors_1.InvalidReadQueryError('callData must be hex with at least a 4-byte selector');
94
+ }
95
+ callData = query.callData;
96
+ resultShape = { kind: 'raw' };
97
+ }
98
+ else {
99
+ const item = (0, viem_1.getAbiItem)({
100
+ abi: query.abi,
101
+ name: query.functionName,
102
+ args: query.args,
103
+ });
104
+ if (!item || item.type !== 'function') {
105
+ throw new errors_1.InvalidReadQueryError(`function not found in abi: ${query.functionName}`);
106
+ }
107
+ if (item.stateMutability !== 'view' && item.stateMutability !== 'pure') {
108
+ throw new errors_1.InvalidReadQueryError(`${query.functionName} is ${item.stateMutability}; reads must target view/pure functions`);
109
+ }
110
+ callData = (0, viem_1.encodeFunctionData)({
111
+ abi: query.abi,
112
+ functionName: query.functionName,
113
+ args: query.args,
114
+ });
115
+ resultShape = { kind: 'evmCall', abi: [item], functionName: query.functionName };
116
+ }
117
+ payload = (0, viem_1.encodeAbiParameters)([{ type: 'address' }, { type: 'bytes' }], [query.target, callData]);
118
+ break;
119
+ }
120
+ case 'storageSlot': {
121
+ queryType = exports.EVM_QUERY_TYPE.STORAGE_SLOT;
122
+ payload = (0, viem_1.encodeAbiParameters)([{ type: 'address' }, { type: 'bytes32' }], [query.target, toStorageSlot(query.slot)]);
123
+ resultShape = { kind: 'bytes32' };
124
+ break;
125
+ }
126
+ default:
127
+ throw new errors_1.InvalidReadQueryError(`unknown EVM query type: ${query.type}`);
128
+ }
129
+ return {
130
+ namespace: read_state_1.READ_NAMESPACE.EVM,
131
+ queryType,
132
+ encoded: encodeEnvelope(queryType, blockNumber, payload),
133
+ blockRef: blockNumber,
134
+ resultShape,
135
+ warnings: [],
136
+ };
137
+ }
138
+ /** Decode an envelope with the exact tuple shape the validator uses. */
139
+ function decodeEvmQueryEnvelope(encoded) {
140
+ const [env] = (0, viem_1.decodeAbiParameters)(EVM_ENVELOPE_ABI, encoded);
141
+ return {
142
+ queryType: env.queryType,
143
+ refType: env.blockRef.refType,
144
+ blockNumber: env.blockRef.blockNumber,
145
+ payload: env.payload,
146
+ };
147
+ }
148
+ //# sourceMappingURL=evm.js.map
@@ -0,0 +1,12 @@
1
+ import type { EncodedReadQuery, ReadDestination, ReadQuery } from '../read-state.types';
2
+ export * from './evm';
3
+ export * from './svm';
4
+ export * from './web2';
5
+ /**
6
+ * Encode any read query for its destination. Rejects a query kind that does not
7
+ * belong to the destination's namespace (e.g. a `storageSlot` read against Solana).
8
+ */
9
+ export declare function encodeReadQuery(destination: ReadDestination, query: ReadQuery, options?: {
10
+ blockNumber?: bigint;
11
+ minSlot?: bigint;
12
+ }): EncodedReadQuery;
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.encodeReadQuery = encodeReadQuery;
4
+ const tslib_1 = require("tslib");
5
+ const read_state_1 = require("../../constants/read-state");
6
+ const destination_1 = require("../destination");
7
+ const errors_1 = require("../errors");
8
+ const evm_1 = require("./evm");
9
+ const svm_1 = require("./svm");
10
+ const web2_1 = require("./web2");
11
+ tslib_1.__exportStar(require("./evm"), exports);
12
+ tslib_1.__exportStar(require("./svm"), exports);
13
+ tslib_1.__exportStar(require("./web2"), exports);
14
+ const EVM_TYPES = new Set(['accountBalance', 'contractCall', 'storageSlot']);
15
+ const SVM_TYPES = new Set(['lamportBalance', 'splTokenAccount', 'rawAccountData']);
16
+ /**
17
+ * Encode any read query for its destination. Rejects a query kind that does not
18
+ * belong to the destination's namespace (e.g. a `storageSlot` read against Solana).
19
+ */
20
+ function encodeReadQuery(destination, query, options = {}) {
21
+ const dest = (0, destination_1.resolveDestination)(destination);
22
+ switch (dest.namespace) {
23
+ case read_state_1.READ_NAMESPACE.EVM: {
24
+ if (!EVM_TYPES.has(query.type))
25
+ throw new errors_1.InvalidReadQueryError(`query type ${query.type} is not valid for ${dest.caip2}`);
26
+ if (options.blockNumber === undefined) {
27
+ throw new errors_1.InvalidReadQueryError('EVM reads need options.blockNumber (the pinned destination height)');
28
+ }
29
+ return (0, evm_1.encodeEvmQueryEnvelope)(query, { blockNumber: options.blockNumber });
30
+ }
31
+ case read_state_1.READ_NAMESPACE.SVM: {
32
+ if (!SVM_TYPES.has(query.type))
33
+ throw new errors_1.InvalidReadQueryError(`query type ${query.type} is not valid for ${dest.caip2}`);
34
+ return (0, svm_1.encodeSvmQueryEnvelope)(query, { minSlot: options.minSlot });
35
+ }
36
+ case read_state_1.READ_NAMESPACE.WEB2: {
37
+ if (query.type !== 'http')
38
+ throw new errors_1.InvalidReadQueryError(`query type ${query.type} is not valid for web2`);
39
+ return (0, web2_1.encodeWeb2QueryEnvelope)(query);
40
+ }
41
+ default:
42
+ throw new errors_1.UnsupportedReadDestinationError(`unsupported namespace: ${dest.namespace}`);
43
+ }
44
+ }
45
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,32 @@
1
+ import { PublicKey } from '@solana/web3.js';
2
+ import { type Hex } from 'viem';
3
+ import type { EncodedReadQuery, SvmReadQuery } from '../read-state.types';
4
+ /** `universalClient/externalchains/svm/read_envelope.go` — solanaQueryType. */
5
+ export declare const SVM_QUERY_TYPE: {
6
+ readonly LAMPORT_BALANCE: 0;
7
+ readonly SPL_TOKEN_ACCOUNT: 1;
8
+ readonly RAW_ACCOUNT_DATA: 2;
9
+ };
10
+ export declare const TOKEN_PROGRAM_ID: PublicKey;
11
+ export declare const TOKEN_2022_PROGRAM_ID: PublicKey;
12
+ export declare const ASSOCIATED_TOKEN_PROGRAM_ID: PublicKey;
13
+ /**
14
+ * The raw 32-byte pubkey the validator requires in `ReadSpec.account.owner`
15
+ * (`svm/read_executor.go:29-32` hard-rejects any other length).
16
+ */
17
+ export declare function svmOwnerBytes(account: string | Uint8Array): Hex;
18
+ /** Deterministic, offline. Same derivation as @solana/spl-token getAssociatedTokenAddressSync. */
19
+ export declare function deriveAssociatedTokenAddress(owner: string | PublicKey, mint: string | PublicKey, tokenProgramId?: PublicKey): PublicKey;
20
+ /**
21
+ * Encode a `solana` read query. `minSlot` is a staleness FLOOR at finalized
22
+ * commitment, not a pin — Solana cannot read at an exact past slot. The validator
23
+ * uses `max(envelope.minSlot, ReadSpec.blockNumber)`.
24
+ */
25
+ export declare function encodeSvmQueryEnvelope(query: SvmReadQuery, options?: {
26
+ minSlot?: bigint;
27
+ }): EncodedReadQuery;
28
+ export declare function decodeSvmQueryEnvelope(encoded: Hex): {
29
+ queryType: number;
30
+ minSlot: bigint;
31
+ payload: Hex;
32
+ };
@@ -0,0 +1,107 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ASSOCIATED_TOKEN_PROGRAM_ID = exports.TOKEN_2022_PROGRAM_ID = exports.TOKEN_PROGRAM_ID = exports.SVM_QUERY_TYPE = void 0;
4
+ exports.svmOwnerBytes = svmOwnerBytes;
5
+ exports.deriveAssociatedTokenAddress = deriveAssociatedTokenAddress;
6
+ exports.encodeSvmQueryEnvelope = encodeSvmQueryEnvelope;
7
+ exports.decodeSvmQueryEnvelope = decodeSvmQueryEnvelope;
8
+ const tslib_1 = require("tslib");
9
+ const web3_js_1 = require("@solana/web3.js");
10
+ const bs58_1 = tslib_1.__importDefault(require("bs58"));
11
+ const viem_1 = require("viem");
12
+ const read_state_1 = require("../../constants/read-state");
13
+ const errors_1 = require("../errors");
14
+ /** `universalClient/externalchains/svm/read_envelope.go` — solanaQueryType. */
15
+ exports.SVM_QUERY_TYPE = {
16
+ LAMPORT_BALANCE: 0,
17
+ SPL_TOKEN_ACCOUNT: 1,
18
+ RAW_ACCOUNT_DATA: 2,
19
+ };
20
+ /** Single tuple, like EVM. Payload is empty for every v1 type. */
21
+ const SVM_ENVELOPE_ABI = [
22
+ {
23
+ type: 'tuple',
24
+ components: [
25
+ { name: 'queryType', type: 'uint8' },
26
+ { name: 'slotRef', type: 'tuple', components: [{ name: 'minSlot', type: 'uint64' }] },
27
+ { name: 'payload', type: 'bytes' },
28
+ ],
29
+ },
30
+ ];
31
+ exports.TOKEN_PROGRAM_ID = new web3_js_1.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA');
32
+ exports.TOKEN_2022_PROGRAM_ID = new web3_js_1.PublicKey('TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb');
33
+ exports.ASSOCIATED_TOKEN_PROGRAM_ID = new web3_js_1.PublicKey('ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL');
34
+ const UINT64_MAX = (1n << 64n) - 1n;
35
+ /**
36
+ * The raw 32-byte pubkey the validator requires in `ReadSpec.account.owner`
37
+ * (`svm/read_executor.go:29-32` hard-rejects any other length).
38
+ */
39
+ function svmOwnerBytes(account) {
40
+ let bytes;
41
+ if (typeof account === 'string') {
42
+ try {
43
+ bytes = bs58_1.default.decode(account);
44
+ }
45
+ catch {
46
+ throw new errors_1.InvalidReadQueryError(`account is not a base58 Solana pubkey: ${account}`);
47
+ }
48
+ }
49
+ else {
50
+ bytes = account;
51
+ }
52
+ if (bytes.length !== 32) {
53
+ throw new errors_1.InvalidReadQueryError(`Solana account must be 32 bytes, got ${bytes.length}`);
54
+ }
55
+ return (0, viem_1.bytesToHex)(bytes);
56
+ }
57
+ /** Deterministic, offline. Same derivation as @solana/spl-token getAssociatedTokenAddressSync. */
58
+ function deriveAssociatedTokenAddress(owner, mint, tokenProgramId = exports.TOKEN_PROGRAM_ID) {
59
+ const o = typeof owner === 'string' ? new web3_js_1.PublicKey(owner) : owner;
60
+ const m = typeof mint === 'string' ? new web3_js_1.PublicKey(mint) : mint;
61
+ const [ata] = web3_js_1.PublicKey.findProgramAddressSync([o.toBuffer(), tokenProgramId.toBuffer(), m.toBuffer()], exports.ASSOCIATED_TOKEN_PROGRAM_ID);
62
+ return ata;
63
+ }
64
+ /**
65
+ * Encode a `solana` read query. `minSlot` is a staleness FLOOR at finalized
66
+ * commitment, not a pin — Solana cannot read at an exact past slot. The validator
67
+ * uses `max(envelope.minSlot, ReadSpec.blockNumber)`.
68
+ */
69
+ function encodeSvmQueryEnvelope(query, options = {}) {
70
+ const minSlot = options.minSlot ?? 0n;
71
+ if (minSlot < 0n || minSlot > UINT64_MAX)
72
+ throw new errors_1.InvalidReadQueryError(`minSlot out of uint64 range: ${minSlot}`);
73
+ let queryType;
74
+ let resultShape;
75
+ switch (query.type) {
76
+ case 'lamportBalance':
77
+ queryType = exports.SVM_QUERY_TYPE.LAMPORT_BALANCE;
78
+ resultShape = { kind: 'uint256' };
79
+ break;
80
+ case 'splTokenAccount':
81
+ queryType = exports.SVM_QUERY_TYPE.SPL_TOKEN_ACCOUNT;
82
+ resultShape = { kind: 'uint256' };
83
+ break;
84
+ case 'rawAccountData':
85
+ queryType = exports.SVM_QUERY_TYPE.RAW_ACCOUNT_DATA;
86
+ resultShape = { kind: 'raw' };
87
+ break;
88
+ default:
89
+ throw new errors_1.InvalidReadQueryError(`unknown SVM query type: ${query.type}`);
90
+ }
91
+ const ownerBytes = svmOwnerBytes(query.account);
92
+ const encoded = (0, viem_1.encodeAbiParameters)(SVM_ENVELOPE_ABI, [{ queryType, slotRef: { minSlot }, payload: '0x' }]);
93
+ return {
94
+ namespace: read_state_1.READ_NAMESPACE.SVM,
95
+ queryType,
96
+ encoded,
97
+ blockRef: minSlot,
98
+ resultShape,
99
+ ownerBytes,
100
+ warnings: [],
101
+ };
102
+ }
103
+ function decodeSvmQueryEnvelope(encoded) {
104
+ const [env] = (0, viem_1.decodeAbiParameters)(SVM_ENVELOPE_ABI, encoded);
105
+ return { queryType: env.queryType, minSlot: env.slotRef.minSlot, payload: env.payload };
106
+ }
107
+ //# sourceMappingURL=svm.js.map