@pushchain/core 6.0.25 → 6.0.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/package.json +1 -1
  3. package/src/lib/constants/chain.js +1 -1
  4. package/src/lib/constants/index.d.ts +5 -3
  5. package/src/lib/constants/index.js +1 -0
  6. package/src/lib/constants/read-state.d.ts +6 -2
  7. package/src/lib/constants/read-state.js +7 -3
  8. package/src/lib/index.d.ts +1 -1
  9. package/src/lib/index.js +3 -2
  10. package/src/lib/progress-hook/progress-hook.js +21 -0
  11. package/src/lib/progress-hook/progress-hook.types.d.ts +4 -1
  12. package/src/lib/progress-hook/progress-hook.types.js +3 -0
  13. package/src/lib/push-chain/push-chain.d.ts +2 -1
  14. package/src/lib/push-chain/push-chain.js +4 -3
  15. package/src/lib/push-client/push-client.d.ts +7 -0
  16. package/src/lib/push-client/push-client.js +41 -15
  17. package/src/lib/read-state/envelopes/evm.js +2 -3
  18. package/src/lib/read-state/envelopes/svm.js +11 -1
  19. package/src/lib/read-state/read-executor.js +4 -0
  20. package/src/lib/read-state/read-params.d.ts +68 -23
  21. package/src/lib/read-state/read-params.js +57 -13
  22. package/src/lib/read-state/read-state.types.d.ts +41 -0
  23. package/src/lib/read-state/read-state.types.js +23 -1
  24. package/src/lib/read-state/read-tracker.d.ts +6 -1
  25. package/src/lib/read-state/read-tracker.js +81 -15
  26. package/src/lib/read-state/registry.js +2 -3
  27. package/src/lib/read-state/result-decoder.js +6 -0
  28. package/src/lib/read-state/spec-builder.js +1 -1
  29. package/src/lib/read-state/svm-account.d.ts +20 -0
  30. package/src/lib/read-state/svm-account.js +114 -0
  31. package/src/lib/read-state/svm-pda.d.ts +13 -0
  32. package/src/lib/read-state/svm-pda.js +147 -0
@@ -3,6 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.decodeReadResult = decodeReadResult;
4
4
  const viem_1 = require("viem");
5
5
  const errors_1 = require("./errors");
6
+ const buffer_1 = require("buffer");
7
+ const svm_account_1 = require("./svm-account");
6
8
  const WEB2_ABI_TYPE = {
7
9
  uint256: 'uint256',
8
10
  int256: 'int256',
@@ -29,6 +31,10 @@ function decodeReadResult(resultData, shape) {
29
31
  const len = (0, viem_1.size)(resultData);
30
32
  try {
31
33
  switch (shape.kind) {
34
+ case 'svmAccount': {
35
+ const { accountName, value } = (0, svm_account_1.decodeIdlAccount)(shape.idl, buffer_1.Buffer.from(resultData.slice(2), 'hex'), shape.accountName);
36
+ return { kind: 'svmAccount', value, accountName };
37
+ }
32
38
  case 'uint256': {
33
39
  if (len !== 32)
34
40
  throw new errors_1.ReadDecodeError(`expected 32 bytes for uint256, got ${len}`);
@@ -185,7 +185,7 @@ function toCallData(prepared, target) {
185
185
  */
186
186
  async function simulateRead(deps, prepared, opts) {
187
187
  const age = Date.now() - prepared.preflight.fetchedAt;
188
- if (age > (opts.staleAfterMs ?? 60_000)) {
188
+ if (age > (opts.staleAfterMs ?? read_state_1.READ_PREFLIGHT_STALE_MS)) {
189
189
  prepared.warnings.push(`preflight is ${Math.round(age / 1000)} s old — pushBlockNumber moves every block; rebuild before sending`);
190
190
  }
191
191
  try {
@@ -0,0 +1,20 @@
1
+ import { type Idl } from '@coral-xyz/anchor';
2
+ import { PublicKey } from '@solana/web3.js';
3
+ import type { CHAIN } from '../constants/enums';
4
+ /** snake_case, camelCase and PascalCase spellings of one name share a key. */
5
+ export declare function idlNameKey(name: string): string;
6
+ /** Structural checks the coder does not make, so a bad IDL fails at prepare time. */
7
+ export declare function validateIdl(idl: Idl): void;
8
+ /** The IDL's own spelling of an account layout name given in any case style. */
9
+ export declare function resolveAccountName(idl: Idl, name: string): string;
10
+ /**
11
+ * Decode account bytes with the IDL. With `accountName`, the data's discriminator
12
+ * must match that layout; without it, the layout is the one whose discriminator
13
+ * prefixes the data.
14
+ */
15
+ export declare function decodeIdlAccount(idl: Idl, data: Uint8Array, accountName?: string): {
16
+ accountName: string;
17
+ value: unknown;
18
+ };
19
+ /** Inspect the mint owner; the chain alone cannot distinguish SPL Token and Token-2022. */
20
+ export declare function detectTokenProgram(mint: string, chain: CHAIN, rpcUrls?: string[]): Promise<PublicKey>;
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.idlNameKey = idlNameKey;
4
+ exports.validateIdl = validateIdl;
5
+ exports.resolveAccountName = resolveAccountName;
6
+ exports.decodeIdlAccount = decodeIdlAccount;
7
+ exports.detectTokenProgram = detectTokenProgram;
8
+ const anchor_1 = require("@coral-xyz/anchor");
9
+ const web3_js_1 = require("@solana/web3.js");
10
+ const chain_1 = require("../constants/chain");
11
+ const buffer_1 = require("buffer");
12
+ const errors_1 = require("./errors");
13
+ const svm_1 = require("./envelopes/svm");
14
+ /** snake_case, camelCase and PascalCase spellings of one name share a key. */
15
+ function idlNameKey(name) {
16
+ return name.replace(/_/g, '').toLowerCase();
17
+ }
18
+ /** Structural checks the coder does not make, so a bad IDL fails at prepare time. */
19
+ function validateIdl(idl) {
20
+ try {
21
+ if (!idl || typeof idl !== 'object')
22
+ throw new Error('idl must be an Anchor IDL object');
23
+ new web3_js_1.PublicKey(idl.address);
24
+ if (!idl.accounts?.length)
25
+ throw new Error('the IDL declares no accounts');
26
+ for (const a of idl.accounts) {
27
+ const d = a.discriminator;
28
+ if (!Array.isArray(d) || d.length === 0 || d.some(b => !Number.isInteger(b) || b < 0 || b > 255)) {
29
+ throw new Error(`account ${a.name}: discriminator must be a non-empty byte array`);
30
+ }
31
+ }
32
+ new anchor_1.BorshAccountsCoder(idl);
33
+ }
34
+ catch (error) {
35
+ throw new errors_1.InvalidReadQueryError(`invalid Solana account IDL: ${error.message}`);
36
+ }
37
+ }
38
+ /** The IDL's own spelling of an account layout name given in any case style. */
39
+ function resolveAccountName(idl, name) {
40
+ const accounts = idl.accounts ?? [];
41
+ const exact = accounts.find(a => a.name === name);
42
+ if (exact)
43
+ return exact.name;
44
+ const key = idlNameKey(name);
45
+ const matches = accounts.filter(a => idlNameKey(a.name) === key);
46
+ if (matches.length === 1)
47
+ return matches[0].name;
48
+ const known = accounts.map(a => a.name).join(', ') || 'none';
49
+ throw new errors_1.InvalidReadQueryError(matches.length > 1 ? `functionName ${name} is ambiguous in the IDL (${matches.map(a => a.name).join(', ')})` : `functionName ${name} is not an account in the IDL (accounts: ${known})`);
50
+ }
51
+ function hasDiscriminator(a, data) {
52
+ return data.length >= a.discriminator.length && a.discriminator.every((b, i) => data[i] === b);
53
+ }
54
+ /**
55
+ * Decode account bytes with the IDL. With `accountName`, the data's discriminator
56
+ * must match that layout; without it, the layout is the one whose discriminator
57
+ * prefixes the data.
58
+ */
59
+ function decodeIdlAccount(idl, data, accountName) {
60
+ const accounts = idl.accounts ?? [];
61
+ let def;
62
+ if (accountName !== undefined) {
63
+ def = accounts.find(a => a.name === resolveAccountName(idl, accountName));
64
+ if (def && !hasDiscriminator(def, data)) {
65
+ throw new errors_1.ReadDecodeError(`account data discriminator does not match ${def.name}`);
66
+ }
67
+ }
68
+ else {
69
+ const found = accounts.filter(a => hasDiscriminator(a, data));
70
+ // Longest match wins if one discriminator prefixes another.
71
+ def = found.sort((x, y) => y.discriminator.length - x.discriminator.length)[0];
72
+ if (!def) {
73
+ const head = buffer_1.Buffer.from(data.subarray(0, 8)).toString('hex');
74
+ throw new errors_1.ReadDecodeError(`no IDL account discriminator matches the account data (0x${head})`);
75
+ }
76
+ }
77
+ if (!def)
78
+ throw new errors_1.ReadDecodeError('account not found in the IDL');
79
+ return { accountName: def.name, value: new anchor_1.BorshAccountsCoder(idl).decode(def.name, buffer_1.Buffer.from(data)) };
80
+ }
81
+ /** Inspect the mint owner; the chain alone cannot distinguish SPL Token and Token-2022. */
82
+ async function detectTokenProgram(mint, chain, rpcUrls) {
83
+ const address = new web3_js_1.PublicKey(mint);
84
+ const urls = rpcUrls ?? chain_1.CHAIN_INFO[chain]?.defaultRPC;
85
+ if (!urls?.length)
86
+ throw new errors_1.InvalidReadQueryError(`no Solana RPC configured for ${chain}`);
87
+ let lastError;
88
+ for (const url of urls) {
89
+ let info;
90
+ try {
91
+ info = await new web3_js_1.Connection(url, 'finalized').getAccountInfo(address, 'finalized');
92
+ }
93
+ catch (error) {
94
+ lastError = error;
95
+ continue;
96
+ }
97
+ if (!info)
98
+ throw new errors_1.InvalidReadQueryError(`token mint does not exist: ${mint}`);
99
+ if (!info.owner.equals(svm_1.TOKEN_PROGRAM_ID) && !info.owner.equals(svm_1.TOKEN_2022_PROGRAM_ID)) {
100
+ throw new errors_1.InvalidReadQueryError('mint is not owned by SPL Token or Token-2022');
101
+ }
102
+ // Mint base layout: 82 bytes, initialized flag at byte 45. Token-2022
103
+ // extensions pad to 165 bytes and carry AccountType.Mint (1) at byte 165.
104
+ if (info.executable || info.data.length < 82 || info.data[45] !== 1 ||
105
+ (info.owner.equals(svm_1.TOKEN_PROGRAM_ID) && info.data.length !== 82) ||
106
+ (info.owner.equals(svm_1.TOKEN_2022_PROGRAM_ID) && info.data.length !== 82 &&
107
+ (info.data.length < 166 || info.data[165] !== 1))) {
108
+ throw new errors_1.InvalidReadQueryError('token address is not an initialized mint');
109
+ }
110
+ return info.owner;
111
+ }
112
+ throw lastError;
113
+ }
114
+ //# sourceMappingURL=svm-account.js.map
@@ -0,0 +1,13 @@
1
+ /**
2
+ * PDA derivation for `read(programId, { idl, functionName, args })`.
3
+ *
4
+ * The seed layout comes from the IDL, never from a guessed encoding: Anchor
5
+ * records each PDA's seeds on the instruction accounts that use it. `args` fill
6
+ * the non-constant seeds in order, encoded as the IDL declares them.
7
+ */
8
+ import type { Idl } from '@coral-xyz/anchor';
9
+ import { PublicKey } from '@solana/web3.js';
10
+ /** Derive the PDA of account layout `functionName` from its IDL seed template and positional `args`. */
11
+ export declare function deriveIdlPda(idl: Idl, functionName: string, args?: readonly unknown[]): PublicKey;
12
+ /** Whether `subject` is the IDL's program id (base58 or 0x-hex). */
13
+ export declare function isProgramSubject(idl: Idl, subject: string): boolean;
@@ -0,0 +1,147 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.deriveIdlPda = deriveIdlPda;
4
+ exports.isProgramSubject = isProgramSubject;
5
+ const web3_js_1 = require("@solana/web3.js");
6
+ const buffer_1 = require("buffer");
7
+ const normalize_address_1 = require("../orchestrator/svm-idl/normalize-address");
8
+ const errors_1 = require("./errors");
9
+ const svm_account_1 = require("./svm-account");
10
+ /** Every instruction account named like `name` that carries PDA seeds. */
11
+ function findSeedTemplates(idl, name) {
12
+ const key = (0, svm_account_1.idlNameKey)(name);
13
+ const out = [];
14
+ const visit = (instruction, accounts) => {
15
+ for (const a of accounts) {
16
+ if (a.accounts)
17
+ visit(instruction, a.accounts); // composite account groups
18
+ else if (a.pda && (0, svm_account_1.idlNameKey)(a.name) === key)
19
+ out.push({ pda: a.pda, instruction });
20
+ }
21
+ };
22
+ for (const ix of idl.instructions ?? [])
23
+ visit(ix, ix.accounts);
24
+ return out;
25
+ }
26
+ function toPublicKey(value, what) {
27
+ if (value instanceof web3_js_1.PublicKey)
28
+ return value;
29
+ try {
30
+ return new web3_js_1.PublicKey(buffer_1.Buffer.from((0, normalize_address_1.toSvmHexAddress)(value).slice(2), 'hex'));
31
+ }
32
+ catch (e) {
33
+ throw new errors_1.InvalidReadQueryError(`${what} must be a Solana pubkey (base58, 0x 32-byte hex, or PublicKey): ${e.message}`);
34
+ }
35
+ }
36
+ const INT_WIDTH = {
37
+ u8: [1, false], i8: [1, true], u16: [2, false], i16: [2, true], u32: [4, false], i32: [4, true],
38
+ u64: [8, false], i64: [8, true], u128: [16, false], i128: [16, true],
39
+ };
40
+ function encodeInt(value, type, what) {
41
+ const [bytes, signed] = INT_WIDTH[type];
42
+ let n;
43
+ try {
44
+ n = typeof value === 'bigint' ? value : BigInt(value);
45
+ }
46
+ catch {
47
+ throw new errors_1.InvalidReadQueryError(`${what} must be an integer for ${type}`);
48
+ }
49
+ const bits = BigInt(bytes * 8);
50
+ const min = signed ? -(1n << (bits - 1n)) : 0n;
51
+ const max = signed ? (1n << (bits - 1n)) - 1n : (1n << bits) - 1n;
52
+ if (n < min || n > max)
53
+ throw new errors_1.InvalidReadQueryError(`${what} out of ${type} range: ${n}`);
54
+ let u = n < 0n ? (1n << bits) + n : n;
55
+ const out = buffer_1.Buffer.alloc(bytes);
56
+ for (let i = 0; i < bytes; i++) {
57
+ out[i] = Number(u & 0xffn);
58
+ u >>= 8n;
59
+ }
60
+ return out;
61
+ }
62
+ function encodeSeed(value, type, what) {
63
+ if (type === 'pubkey')
64
+ return toPublicKey(value, what).toBuffer();
65
+ if (typeof type === 'string' && type in INT_WIDTH)
66
+ return encodeInt(value, type, what);
67
+ if (type === 'bool') {
68
+ if (typeof value !== 'boolean')
69
+ throw new errors_1.InvalidReadQueryError(`${what} must be a boolean`);
70
+ return buffer_1.Buffer.from([value ? 1 : 0]);
71
+ }
72
+ if (type === 'string') {
73
+ if (typeof value !== 'string')
74
+ throw new errors_1.InvalidReadQueryError(`${what} must be a string`);
75
+ return buffer_1.Buffer.from(value, 'utf8');
76
+ }
77
+ const isBytes = type === 'bytes' || (typeof type === 'object' && type !== null && 'array' in type && type.array[0] === 'u8');
78
+ if (isBytes) {
79
+ if (value instanceof Uint8Array)
80
+ return buffer_1.Buffer.from(value);
81
+ if (typeof value === 'string' && /^0x([0-9a-fA-F]{2})*$/.test(value))
82
+ return buffer_1.Buffer.from(value.slice(2), 'hex');
83
+ throw new errors_1.InvalidReadQueryError(`${what} must be bytes (Uint8Array or 0x-hex)`);
84
+ }
85
+ throw new errors_1.InvalidReadQueryError(`${what}: seed type ${JSON.stringify(type)} is not supported`);
86
+ }
87
+ function argType(ix, path) {
88
+ if (path.includes('.'))
89
+ throw new errors_1.InvalidReadQueryError(`seed arg path ${path} reads a struct field; only top-level instruction args are supported`);
90
+ const arg = ix.args.find(a => a.name === path) ?? ix.args.find(a => (0, svm_account_1.idlNameKey)(a.name) === (0, svm_account_1.idlNameKey)(path));
91
+ if (!arg)
92
+ throw new errors_1.InvalidReadQueryError(`seed arg ${path} is not an argument of ${ix.name}`);
93
+ return arg.type;
94
+ }
95
+ function describeSeeds(t) {
96
+ return t.pda.seeds.map(s => (s.kind === 'const' ? `"${buffer_1.Buffer.from(s.value).toString('utf8')}"` : `${s.kind}:${s.path}`)).join(', ');
97
+ }
98
+ /** Same seeds, same order: two instructions describing one PDA agree. */
99
+ function sameLayout(a, b) {
100
+ return JSON.stringify([a.pda.seeds, a.pda.program]) === JSON.stringify([b.pda.seeds, b.pda.program]);
101
+ }
102
+ /** Derive the PDA of account layout `functionName` from its IDL seed template and positional `args`. */
103
+ function deriveIdlPda(idl, functionName, args = []) {
104
+ const templates = findSeedTemplates(idl, functionName);
105
+ if (templates.length === 0) {
106
+ throw new errors_1.InvalidReadQueryError(`the IDL has no PDA seeds for ${functionName}; pass the account address as the subject instead of the program id`);
107
+ }
108
+ const [template] = templates;
109
+ if (templates.some(t => !sameLayout(t, template))) {
110
+ throw new errors_1.InvalidReadQueryError(`the IDL describes ${functionName} with different seeds in different instructions; pass the account address as the subject`);
111
+ }
112
+ const dynamic = template.pda.seeds.filter(s => s.kind !== 'const');
113
+ if (args.length !== dynamic.length) {
114
+ throw new errors_1.InvalidReadQueryError(`${functionName} is derived from seeds [${describeSeeds(template)}]: expected ${dynamic.length} args, got ${args.length}`);
115
+ }
116
+ let i = 0;
117
+ const seeds = template.pda.seeds.map((seed) => {
118
+ if (seed.kind === 'const')
119
+ return buffer_1.Buffer.from(seed.value);
120
+ const what = `args[${i}] (${seed.kind}:${seed.path})`;
121
+ const value = args[i++];
122
+ if (seed.kind === 'account') {
123
+ if (seed.path.includes('.'))
124
+ throw new errors_1.InvalidReadQueryError(`seed ${seed.path} reads another account's field; pass the account address as the subject`);
125
+ return encodeSeed(value, 'pubkey', what);
126
+ }
127
+ return encodeSeed(value, argType(template.instruction, seed.path), what);
128
+ });
129
+ let programId = new web3_js_1.PublicKey(idl.address);
130
+ const program = template.pda.program;
131
+ if (program) {
132
+ if (program.kind !== 'const')
133
+ throw new errors_1.InvalidReadQueryError(`${functionName} is derived under a non-constant program; pass the account address as the subject`);
134
+ programId = new web3_js_1.PublicKey(buffer_1.Buffer.from(program.value));
135
+ }
136
+ return web3_js_1.PublicKey.findProgramAddressSync(seeds, programId)[0];
137
+ }
138
+ /** Whether `subject` is the IDL's program id (base58 or 0x-hex). */
139
+ function isProgramSubject(idl, subject) {
140
+ try {
141
+ return (0, normalize_address_1.toSvmHexAddress)(subject) === (0, normalize_address_1.toSvmHexAddress)(idl.address);
142
+ }
143
+ catch {
144
+ return false;
145
+ }
146
+ }
147
+ //# sourceMappingURL=svm-pda.js.map