sememe 0.0.0-stage → 0.4.22

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 (76) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +2777 -2
  3. package/bin/sememe.mjs +556 -0
  4. package/index.mjs +48 -0
  5. package/interface/ata-ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL.json +62 -0
  6. package/interface/native-11111111111111111111111111111111.json +226 -0
  7. package/interface/native-ComputeBudget111111111111111111111111111111.json +72 -0
  8. package/interface/raydium-675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8.json +352 -0
  9. package/interface/token-TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA.json +334 -0
  10. package/interface/token-TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb.json +535 -0
  11. package/interface/token-metadata.json +132 -0
  12. package/package.json +71 -4
  13. package/src/account-list.mjs +308 -0
  14. package/src/account-union.mjs +523 -0
  15. package/src/account.mjs +199 -0
  16. package/src/acquire.mjs +145 -0
  17. package/src/adopt.mjs +206 -0
  18. package/src/ata-interface.mjs +276 -0
  19. package/src/ata.mjs +223 -0
  20. package/src/base58.mjs +96 -0
  21. package/src/canonical.mjs +24 -0
  22. package/src/cli-surface.mjs +300 -0
  23. package/src/codec.mjs +1118 -0
  24. package/src/conformance.mjs +431 -0
  25. package/src/corpus/harvest-compute-budget-2026-10-31.json +19122 -0
  26. package/src/corpus/harvest-system-2026-10-31.json +26090 -0
  27. package/src/decode.mjs +286 -0
  28. package/src/dialect.mjs +180 -0
  29. package/src/discriminators.mjs +192 -0
  30. package/src/drift.mjs +172 -0
  31. package/src/gap.mjs +205 -0
  32. package/src/lighthouse.mjs +168 -0
  33. package/src/lock.mjs +440 -0
  34. package/src/native-audit.mjs +56 -0
  35. package/src/native-corpus.mjs +57 -0
  36. package/src/native-interface.mjs +252 -0
  37. package/src/native.mjs +324 -0
  38. package/src/peer.mjs +95 -0
  39. package/src/program-id.mjs +74 -0
  40. package/src/pull.mjs +117 -0
  41. package/src/rawle.mjs +454 -0
  42. package/src/raydium-interface.mjs +747 -0
  43. package/src/raydium.mjs +391 -0
  44. package/src/receipt-digest.mjs +84 -0
  45. package/src/receipt-name.mjs +128 -0
  46. package/src/receipt-order.mjs +56 -0
  47. package/src/shank.mjs +73 -0
  48. package/src/short-forms.mjs +209 -0
  49. package/src/store-read.mjs +253 -0
  50. package/src/store-write.mjs +46 -0
  51. package/src/token-interface.mjs +394 -0
  52. package/src/token-metadata-interface.mjs +371 -0
  53. package/src/token-metadata.mjs +317 -0
  54. package/src/token.mjs +443 -0
  55. package/src/union.mjs +428 -0
  56. package/vendor/anza-solana-sdk/8db406e2edfafa25939ec782b77b5b6f30de164d/LICENSE.txt +201 -0
  57. package/vendor/anza-solana-sdk/8db406e2edfafa25939ec782b77b5b6f30de164d/compute-budget-interface/src/lib.rs +93 -0
  58. package/vendor/anza-solana-sdk/8db406e2edfafa25939ec782b77b5b6f30de164d/system-interface/src/instruction.rs +1899 -0
  59. package/vendor/lighthouse/4c579479c98635e419b1b167f08be02a71604a71/LICENSE +21 -0
  60. package/vendor/lighthouse/4c579479c98635e419b1b167f08be02a71604a71/lighthouse.json +2769 -0
  61. package/vendor/raydium-amm/91739d8d9f5cf347e0ce14ebe4d2bb7069fb31b6/LICENSE +201 -0
  62. package/vendor/raydium-amm/91739d8d9f5cf347e0ce14ebe4d2bb7069fb31b6/program/src/instruction.rs +1189 -0
  63. package/vendor/raydium-amm/91739d8d9f5cf347e0ce14ebe4d2bb7069fb31b6/program/src/state.rs +1348 -0
  64. package/vendor/spl-associated-token-account/fbf5379e7398ac8b231c73ee8a9ff677b43dd0ec/LICENSE +201 -0
  65. package/vendor/spl-associated-token-account/fbf5379e7398ac8b231c73ee8a9ff677b43dd0ec/interface/src/instruction.rs +169 -0
  66. package/vendor/spl-associated-token-account/fbf5379e7398ac8b231c73ee8a9ff677b43dd0ec/interface/src/lib.rs +12 -0
  67. package/vendor/spl-associated-token-account/fbf5379e7398ac8b231c73ee8a9ff677b43dd0ec/program/src/processor.rs +308 -0
  68. package/vendor/spl-token/737788ff5bcc3f1f44cfb222f25483187650be76/LICENSE.txt +176 -0
  69. package/vendor/spl-token/737788ff5bcc3f1f44cfb222f25483187650be76/interface/src/instruction.rs +1963 -0
  70. package/vendor/spl-token-2022/5251dcc07ecbcb721d6c4f0231090defadf00311/LICENSE.txt +176 -0
  71. package/vendor/spl-token-2022/5251dcc07ecbcb721d6c4f0231090defadf00311/interface/src/instruction.rs +2868 -0
  72. package/vendor/spl-token-metadata-interface/7038969ec74a89f1afdb9bc97c112d4d26fead9a/LICENSE +201 -0
  73. package/vendor/spl-token-metadata-interface/7038969ec74a89f1afdb9bc97c112d4d26fead9a/interface/src/error.rs +58 -0
  74. package/vendor/spl-token-metadata-interface/7038969ec74a89f1afdb9bc97c112d4d26fead9a/interface/src/instruction.rs +596 -0
  75. package/vendor/spl-token-metadata-interface/7038969ec74a89f1afdb9bc97c112d4d26fead9a/interface/src/lib.rs +21 -0
  76. package/vendor/spl-token-metadata-interface/7038969ec74a89f1afdb9bc97c112d4d26fead9a/interface/src/state.rs +229 -0
@@ -0,0 +1,252 @@
1
+ // Derive a native program's instruction table from the VENDORED UPSTREAM RUST.
2
+ //
3
+ // Shared by two callers, deliberately:
4
+ // - `tools/native-interface.mjs` writes the derived table into `interface/native-<program>.json`
5
+ // (the reviewable, committable record of what upstream says),
6
+ // - `src/native.mjs` derives the SAME table at load time and refuses to decode if the
7
+ // vendored bytes do not hash to what the committed artifact claims.
8
+ //
9
+ // Why derive instead of transcribe: `src/native.mjs` was the one table in this repository
10
+ // whose rows were typed in by hand, and the first consumer's hand copy carried a defect
11
+ // (a u32 string-length header where the chain carries a u64) that survived exactly the
12
+ // kind of review a transcription gets — measured on real traffic: 0 of 63 live
13
+ // CreateAccountWithSeed payloads could be named. The fix is not a better review; it is
14
+ // removing the transcription step.
15
+ //
16
+ // The rules below are the pinned upstream's own declarations, not sememe's opinions:
17
+ // - the wire selector of a variant is its DECLARATION INDEX,
18
+ // - a struct variant's fields are in WIRE ORDER,
19
+ // - a tuple variant's payload type is its single argument's type,
20
+ // - `Address` is a 32-byte pubkey, `String` is a bincode u64-length-prefixed utf8.
21
+ //
22
+ // Two things upstream does NOT pin, and which are therefore stated explicitly here
23
+ // rather than inferred (both were verified against the pinned SDK and the chain):
24
+ // - the ARGUMENT NAMES of tuple variants, which the enum leaves anonymous; they come
25
+ // from the pinned SDK constructors (`set_compute_unit_price(micro_lamports)`),
26
+ // - which variants the RUNTIME rejects; ComputeBudget's `Unused` (index 0) is
27
+ // `InvalidInstructionData` on the wire, so it is emitted as wire-rejected.
28
+ //
29
+ // Anything else unexpected — an unknown arg type, a duplicate name, a variant count that
30
+ // does not match, a unit variant nobody declared — throws. A surprise must be loud.
31
+ //
32
+ // Dependency-free on purpose: this module is imported by the decode core, which must run
33
+ // in repositories with no npm packages installed.
34
+
35
+ import { readFileSync } from 'node:fs';
36
+ import { createHash } from 'node:crypto';
37
+ import path from 'node:path';
38
+ import { fileURLToPath } from 'node:url';
39
+
40
+ const HERE = path.dirname(fileURLToPath(import.meta.url));
41
+ export const REPO_ROOT = path.resolve(HERE, '..');
42
+
43
+ export const SDK_PIN = 'anza-xyz/solana-sdk@8db406e2edfafa25939ec782b77b5b6f30de164d';
44
+ export const VENDOR_DIR = 'vendor/anza-solana-sdk/8db406e2edfafa25939ec782b77b5b6f30de164d';
45
+
46
+ export const PROGRAMS = [
47
+ {
48
+ program: 'ComputeBudget111111111111111111111111111111',
49
+ label: 'Compute Budget Program',
50
+ file: 'compute-budget-interface/src/lib.rs',
51
+ enumName: 'ComputeBudgetInstruction',
52
+ selector: { width: 1, encoding: 'u8' },
53
+ argEncoding: 'bare little-endian integer, no bincode wrapper (runtime `to_instruction!`: data[0] = tag, then $num.to_le_bytes())',
54
+ runtime: 'anza-xyz/agave compute-budget-instruction: borsh `try_from_slice_unchecked`; an unclaimed tag or the reserved Unused(0) is InvalidInstructionData — the runtime REJECTS tag 0, so the decoder refuses instead of naming it',
55
+ wireRejected: ['Unused'],
56
+ unitVariants: ['Unused'],
57
+ tupleArgNames: {
58
+ RequestHeapFrame: 'bytes',
59
+ SetComputeUnitLimit: 'units',
60
+ SetComputeUnitPrice: 'micro_lamports',
61
+ SetLoadedAccountsDataSizeLimit: 'bytes',
62
+ },
63
+ expectedCount: 5,
64
+ },
65
+ {
66
+ program: '11111111111111111111111111111111',
67
+ label: 'System Program',
68
+ file: 'system-interface/src/instruction.rs',
69
+ enumName: 'SystemInstruction',
70
+ selector: { width: 4, encoding: 'u32 little-endian bincode enum tag' },
71
+ argEncoding: 'bincode fixint: u32/u64 LE, pubkeys as 32 raw bytes, String as u64 length prefix + utf8',
72
+ runtime: 'anza-xyz/agave system-program: accepts variants 0..13 (12 = UpgradeNonceAccount, 13 = CreateAccountAllowPrefund)',
73
+ wireRejected: [],
74
+ unitVariants: ['AdvanceNonceAccount', 'UpgradeNonceAccount'],
75
+ tupleArgNames: {
76
+ WithdrawNonceAccount: 'lamports',
77
+ InitializeNonceAccount: 'authorized',
78
+ AuthorizeNonceAccount: 'authorized',
79
+ },
80
+ expectedCount: 14,
81
+ },
82
+ ];
83
+
84
+ const ARG_TYPES = { u32: 'u32', u64: 'u64', Address: 'pubkey', String: 'string' };
85
+
86
+ function stripComments(src) {
87
+ return src.replace(/\/\*[\s\S]*?\*\//g, '').replace(/\/\/[^\n]*/g, '');
88
+ }
89
+
90
+ /** The body of `pub enum <name> { ... }` from comment-stripped source. */
91
+ function enumBody(src, enumName) {
92
+ const marker = `pub enum ${enumName}`;
93
+ const at = src.indexOf(marker);
94
+ if (at < 0) throw Error(`enum ${enumName} not found`);
95
+ const open = src.indexOf('{', at);
96
+ if (open < 0) throw Error(`enum ${enumName} never opens`);
97
+ let depth = 0;
98
+ for (let i = open; i < src.length; i++) {
99
+ if (src[i] === '{') depth++;
100
+ else if (src[i] === '}') {
101
+ depth--;
102
+ if (depth === 0) return src.slice(open + 1, i);
103
+ }
104
+ }
105
+ throw Error(`enum ${enumName} body never closes`);
106
+ }
107
+
108
+ /**
109
+ * All variants of the enum body in declaration order, each `{ name, kind, inner }`.
110
+ *
111
+ * A single scanner rather than two regexes: the regex version silently DROPPED unit
112
+ * variants (`Unused,`), which the first run caught as a variant-count mismatch — the
113
+ * right failure (loud) from an implementation that could also miss a row silently.
114
+ */
115
+ function parseVariants(body) {
116
+ const variants = [];
117
+ let i = 0;
118
+ const IDENT = /[A-Za-z0-9_]/;
119
+ const skip = (chars) => { while (i < body.length && chars.includes(body[i])) i++; };
120
+ while (i < body.length) {
121
+ skip(',\n\r\t ');
122
+ if (i >= body.length) break;
123
+ const start = i;
124
+ while (i < body.length && IDENT.test(body[i])) i++;
125
+ const name = body.slice(start, i);
126
+ if (!/^[A-Z]/.test(name)) throw Error(`unexpected token in enum body: ${name || body[i]}`);
127
+ skip(' \n\r\t');
128
+ if (body[i] === '(' || body[i] === '{') {
129
+ const open = body[i];
130
+ const close = open === '(' ? ')' : '}';
131
+ let depth = 0;
132
+ const mark = i;
133
+ for (; i < body.length; i++) {
134
+ if (body[i] === open) depth++;
135
+ else if (body[i] === close) {
136
+ depth--;
137
+ if (depth === 0) { i++; break; }
138
+ }
139
+ }
140
+ if (depth !== 0) throw Error(`variant ${name}: unbalanced ${open}${close}`);
141
+ variants.push({ name, kind: open === '(' ? 'tuple' : 'struct', inner: body.slice(mark + 1, i - 1) });
142
+ } else {
143
+ variants.push({ name, kind: 'unit', inner: '' });
144
+ }
145
+ }
146
+ if (!variants.length) throw Error('enum body yielded no variants');
147
+ return variants;
148
+ }
149
+
150
+ /** Derive one program's interface document from its vendored upstream source. */
151
+ export function deriveInterface(spec, { root = REPO_ROOT } = {}) {
152
+ const file = path.join(root, VENDOR_DIR, spec.file);
153
+ const raw = readFileSync(file);
154
+ const fileSha256 = `sha256:${createHash('sha256').update(raw).digest('hex')}`;
155
+ const body = enumBody(stripComments(raw.toString('utf8')), spec.enumName);
156
+ const derived = parseVariants(body);
157
+
158
+ const names = derived.map((v) => v.name);
159
+ if (new Set(names).size !== names.length) throw Error(`${spec.enumName}: duplicate variant names`);
160
+ if (names.length !== spec.expectedCount) {
161
+ throw Error(
162
+ `${spec.enumName}: derived ${names.length} variants, expected ${spec.expectedCount} — ` +
163
+ 'upstream changed; update the pin and re-review before trusting this table',
164
+ );
165
+ }
166
+ for (const rejected of spec.wireRejected) {
167
+ if (!names.includes(rejected)) throw Error(`${spec.enumName}: expected wire-rejected variant ${rejected} is absent`);
168
+ }
169
+
170
+ const instructions = derived.map(({ name, kind, inner }, selector) => {
171
+ if (spec.wireRejected.includes(name)) {
172
+ return { selector, name, wire: 'rejected', args: [] };
173
+ }
174
+ let args;
175
+ if (kind === 'struct') {
176
+ args = [...inner.matchAll(/([a-z_][a-z0-9_]*)\s*:\s*([A-Za-z0-9_]+)/g)].map(([_, field, type]) => {
177
+ if (!ARG_TYPES[type]) throw Error(`${spec.enumName}::${name}: field ${field} has unpinned type ${type}`);
178
+ return { name: field, type: ARG_TYPES[type] };
179
+ });
180
+ } else if (kind === 'tuple') {
181
+ const type = inner.trim();
182
+ if (!ARG_TYPES[type]) {
183
+ throw Error(`${spec.enumName}::${name}: tuple type ${type} is not a pinned interface type`);
184
+ }
185
+ const argName = spec.tupleArgNames?.[name];
186
+ if (!argName) {
187
+ throw Error(
188
+ `${spec.enumName}::${name}: tuple payload has no declared argument name — ` +
189
+ 'upstream leaves it anonymous; take it from the pinned SDK constructor and declare it',
190
+ );
191
+ }
192
+ args = [{ name: argName, type: ARG_TYPES[type] }];
193
+ } else {
194
+ // A unit variant outside the wire-rejected list. Upstream has one per enum
195
+ // (System's `AdvanceNonceAccount` / `UpgradeNonceAccount`), and their wire shape
196
+ // has to be stated explicitly rather than inferred: this module's job is to
197
+ // notice surprises, not to pass over them.
198
+ if (!spec.unitVariants?.includes(name)) {
199
+ throw Error(
200
+ `${spec.enumName}::${name}: unit variant not declared in unitVariants — ` +
201
+ 'unknown wire shape; declare it explicitly after checking the runtime',
202
+ );
203
+ }
204
+ args = [];
205
+ }
206
+ if (kind !== 'unit' && !args.length) throw Error(`${spec.enumName}::${name}: no fields derived`);
207
+ if (args.some((a) => !a.name || !a.type)) {
208
+ throw Error(`${spec.enumName}::${name}: incomplete arg derivation from ${JSON.stringify(inner)}`);
209
+ }
210
+ if (new Set(args.map((a) => a.name)).size !== args.length) {
211
+ throw Error(`${spec.enumName}::${name}: duplicate arg names`);
212
+ }
213
+ return { selector, name, wire: 'accepted', args };
214
+ });
215
+
216
+ return {
217
+ program: spec.program,
218
+ label: spec.label,
219
+ scheme: 'native',
220
+ source: {
221
+ repo: 'anza-xyz/solana-sdk',
222
+ pin: SDK_PIN,
223
+ file: spec.file,
224
+ enum: spec.enumName,
225
+ fileSha256,
226
+ vendoredAt: VENDOR_DIR,
227
+ derivation: 'src/native-interface.mjs: declaration order = wire selector; struct-variant field order = wire order; tuple type = arg type; tuple arg names from the pinned SDK constructors',
228
+ },
229
+ runtime: spec.runtime,
230
+ selector: spec.selector,
231
+ argEncoding: spec.argEncoding,
232
+ instructions,
233
+ };
234
+ }
235
+
236
+ /** The spec of one program, by its id. */
237
+ export function specFor(program) {
238
+ const spec = PROGRAMS.find((entry) => entry.program === program);
239
+ if (!spec) throw Error(`no pinned native interface for ${program}`);
240
+ return spec;
241
+ }
242
+
243
+ /** Derive every pinned native interface. */
244
+ export function deriveAll(options) {
245
+ return PROGRAMS.map((spec) => deriveInterface(spec, options));
246
+ }
247
+
248
+ /** sha256 of a vendored upstream file, as `sha256:<hex>`. */
249
+ export function vendoredHash(spec, { root = REPO_ROOT } = {}) {
250
+ const raw = readFileSync(path.join(root, VENDOR_DIR, spec.file));
251
+ return `sha256:${createHash('sha256').update(raw).digest('hex')}`;
252
+ }
package/src/native.mjs ADDED
@@ -0,0 +1,324 @@
1
+ // Native (non-Anchor) program decoding: System and ComputeBudget.
2
+ //
3
+ // Why this file exists: the two native programs appear in essentially every
4
+ // Solana transaction, they have no IDL account to pull, and sememe's own first
5
+ // consumer (solana-tx-remix) carried a hand-transcribed copy of their
6
+ // instruction tables — exactly the per-repo hand-build this repository exists
7
+ // to replace. The census that motivated this module (2026-10-31, real mainnet
8
+ // harvest): the hand table could name 0 of 63 on-chain CreateAccountWithSeed
9
+ // payloads because it reads a u32 string length prefix while the chain carries
10
+ // a u64 (bincode fixint) — measured, not argued.
11
+ //
12
+ // Ground truth chain, every row derived and then VERIFIED, not typed:
13
+ // 1. INTERFACE vendored anza-xyz/solana-sdk@8db406e2edfafa25939ec782b77b5b6f30de164d
14
+ // compute-budget-interface/src/lib.rs (ComputeBudgetInstruction, variants 0..4)
15
+ // system-interface/src/instruction.rs (SystemInstruction, variants 0..13)
16
+ // DERIVED by tools/native-interface.mjs into interface/native-<program>.json.
17
+ // This module reads the artifact at load and re-hashes the vendored Rust
18
+ // against the sha256 the artifact claims; a mismatch REFUSES the load
19
+ // instead of decoding from it. The transcription step no longer exists:
20
+ // the rows here were never hand-typed, they are upstream's own field
21
+ // names and declaration order, and the hash is checked on every import.
22
+ // 2. RUNTIME anza-xyz/agave (master, 2026-10-31) compute-budget-instruction:
23
+ // borsh `try_from_slice_unchecked`; an unclaimed tag or the
24
+ // reserved Unused(0) variant is `InvalidInstructionData` —
25
+ // the runtime REJECTS tag 0, so a decoder that names it would
26
+ // disagree with the chain it decodes for.
27
+ // 3. CORROB. @solana/web3.js@1.98.4 (pinned in package.json) SYSTEM_
28
+ // INSTRUCTION_LAYOUTS: u32 index + ns64 + rustString (u32
29
+ // length + u32 lengthPadding). Read as one u64, length and
30
+ // padding ARE the bincode fixint length word — byte-compatible
31
+ // for every length below 2^32, which is the only length the
32
+ // seed rules allow. The defect this replaces was dropping the
33
+ // padding word entirely: the hand table in the first consumer
34
+ // consumed a 4-byte header, shifted every later field by 4,
35
+ // and could name 0 of 63 real CreateAccountWithSeed payloads.
36
+ // (Measured 2026-10-31; the new decoder names and re-encodes
37
+ // all 63.)
38
+ //
39
+ // Divergence worth keeping visible: @solana/web3.js@1.98.4 still encodes
40
+ // legacy `requestUnits` as tag 0 + (u32, u32) — the pre-2023 wire shape the
41
+ // runtime no longer parses. This module refuses tag 0 with a named reason
42
+ // instead of borrowing the SDK's opinion.
43
+ //
44
+ // Strictness mirrors src/codec.mjs: an unknown selector, a truncated payload,
45
+ // an unknown variant, or trailing bytes are refusals with named reasons —
46
+ // never a confident wrong label. The chain already says what it accepts;
47
+ // this table only claims what it can prove.
48
+
49
+ import { readFileSync } from 'node:fs';
50
+ import path from 'node:path';
51
+ import { fileURLToPath } from 'node:url';
52
+ import { decodeBase58, encodeBase58 } from './base58.mjs';
53
+ import { deriveInterface, specFor, vendoredHash, REPO_ROOT } from './native-interface.mjs';
54
+
55
+ const HERE = path.dirname(fileURLToPath(import.meta.url));
56
+ const ROOT = path.resolve(HERE, '..');
57
+ if (ROOT !== REPO_ROOT) throw Error('native.mjs moved; native-interface.mjs resolves the repo root');
58
+
59
+ export const SYSTEM_PROGRAM = '11111111111111111111111111111111';
60
+ export const COMPUTE_BUDGET_PROGRAM = 'ComputeBudget111111111111111111111111111111';
61
+
62
+ const SDK_PIN = 'anza-xyz/solana-sdk@8db406e2edfafa25939ec782b77b5b6f30de164d';
63
+ const RUNTIME_PIN = 'anza-xyz/agave@master (read 2026-10-31)';
64
+
65
+ /**
66
+ * Load one native interface: DERIVE it from the vendored upstream Rust, then require
67
+ * the committed artifact to agree with the derivation byte for byte.
68
+ *
69
+ * Two checks, deliberately:
70
+ * - the derivation runs against the vendored bytes themselves, so a swapped or
71
+ * edited source file cannot silently change the table it describes;
72
+ * - the committed artifact must equal that derivation, so a tampered or stale
73
+ * artifact refuses the load instead of decoding from it.
74
+ *
75
+ * Same contract as the IDL store: provenance is a claim, the recomputed digest is the
76
+ * check. Because this runs at import, a mismatch fails loudly at startup rather than
77
+ * naming instructions the pinned runtime would reject.
78
+ */
79
+ function loadInterface(program) {
80
+ const spec = specFor(program);
81
+ const artifactPath = path.join(ROOT, 'interface', `native-${program}.json`);
82
+ const derived = deriveInterface(spec);
83
+ let current;
84
+ try {
85
+ current = readFileSync(artifactPath, 'utf8');
86
+ } catch (error) {
87
+ throw new Error(`native interface artifact for ${program} is missing: ${error.message}`);
88
+ }
89
+ const expected = `${JSON.stringify(derived, null, 2)}\n`;
90
+ if (current !== expected) {
91
+ throw new Error(
92
+ `native interface for ${program} refuses to load: interface/native-${program}.json does not equal ` +
93
+ `a fresh derivation of ${derived.source.file} (${derived.source.fileSha256}, hashed now: ${vendoredHash(spec)})`,
94
+ );
95
+ }
96
+ if (!derived.instructions.length || !derived.selector?.width) {
97
+ throw new Error(`native interface artifact for ${program} is malformed`);
98
+ }
99
+ return derived;
100
+ }
101
+
102
+ const ARTIFACTS = new Map([SYSTEM_PROGRAM, COMPUTE_BUDGET_PROGRAM].map((program) => [program, loadInterface(program)]));
103
+
104
+ // Arg shapes, as DECLARED BY THE ARTIFACT: `u32`/`u64` fixed-width LE, `pubkey`
105
+ // 32 raw bytes, `string` u64-length-prefixed utf8 (bincode fixint, measured on-chain),
106
+ // `unit` none. The selector width and encoding come from the artifact too — the table
107
+ // is upstream's declaration order; the selector IS the index.
108
+
109
+ const COMPUTE_BUDGET_IXS = ARTIFACTS.get(COMPUTE_BUDGET_PROGRAM).instructions;
110
+ const SYSTEM_IXS = ARTIFACTS.get(SYSTEM_PROGRAM).instructions;
111
+
112
+ export const NATIVE_PROVENANCE = {
113
+ [SYSTEM_PROGRAM]: {
114
+ label: 'System Program',
115
+ interface: `${SDK_PIN} system-interface/src/instruction.rs SystemInstruction (variants 0..13, declaration order)`,
116
+ encoding: 'u32le enum tag + bincode fixint args; string = u64 length prefix + utf8 (web3.js reads the same word as u32 length + u32 zero padding)',
117
+ runtime: `${RUNTIME_PIN}, system-program: accepts variants 0..13 (12 = UpgradeNonceAccount, 13 = CreateAccountAllowPrefund)`,
118
+ corroboration: '@solana/web3.js@1.98.4 SYSTEM_INSTRUCTION_LAYOUTS (u32 index + ns64 + rustString header, byte-compatible with the u64 length word)',
119
+ measured: '2026-10-31 mainnet harvest: 1.828 payloads; 63/63 CreateAccountWithSeed consume ONLY with the u64 string prefix',
120
+ observed: { Transfer: 1026, CreateAccount: 643, AdvanceNonceAccount: 67, CreateAccountWithSeed: 63, Assign: 14, Allocate: 14, InitializeNonceAccount: 1 },
121
+ },
122
+ [COMPUTE_BUDGET_PROGRAM]: {
123
+ label: 'Compute Budget Program',
124
+ interface: `${SDK_PIN} compute-budget-interface/src/lib.rs ComputeBudgetInstruction (variants 0..4, declaration order)`,
125
+ encoding: '1-byte tag + bare LE integer arg (runtime to_instruction! macro: data[0] = tag, then $num.to_le_bytes())',
126
+ runtime: `${RUNTIME_PIN}, compute-budget-instruction: borsh try_from_slice_unchecked; unclaimed tag or Unused(0) => InvalidInstructionData`,
127
+ corroboration: 'Lightprotocol/light-protocol compute_budget.rs (explicit per-variant index)',
128
+ measured: '2026-10-31 mainnet harvest: 1.348 payloads, tags {2: 651, 3: 550, 4: 147}; tags 0 and 1 not observed on live mainnet — consistent with the runtime reject rule. 27 payloads carry trailing bytes after the declared arg; chain adjudication (9 fetched, byte-identical to the harvest) shows the runtime tolerates them (borsh try_from_slice_unchecked ignores the remainder), and every one sat in a transaction that failed at a LATER instruction',
129
+ divergence: '@solana/web3.js@1.98.4 still ENCODES legacy requestUnits as tag 0 + (u32,u32) — the pre-2023 wire shape the runtime no longer parses; this module refuses tag 0 instead of copying the SDK',
130
+ },
131
+ };
132
+
133
+ const TABLES = new Map([
134
+ [SYSTEM_PROGRAM, { width: ARTIFACTS.get(SYSTEM_PROGRAM).selector.width, ixs: SYSTEM_IXS, artifact: ARTIFACTS.get(SYSTEM_PROGRAM) }],
135
+ [COMPUTE_BUDGET_PROGRAM, { width: ARTIFACTS.get(COMPUTE_BUDGET_PROGRAM).selector.width, ixs: COMPUTE_BUDGET_IXS, artifact: ARTIFACTS.get(COMPUTE_BUDGET_PROGRAM) }],
136
+ ]);
137
+
138
+ /** Whether `program` is one of the native programs this module owns. */
139
+ export function isNativeProgram(program) {
140
+ return TABLES.has(program);
141
+ }
142
+
143
+ /** Program ids this module can decode, for callers that enumerate. */
144
+ export function nativePrograms() {
145
+ return [...TABLES.keys()];
146
+ }
147
+
148
+ /** Provenance for one native program, or null. */
149
+ export function nativeProvenance(program) {
150
+ return TABLES.has(program) ? { ...NATIVE_PROVENANCE[program], program } : null;
151
+ }
152
+
153
+ function readLe(bytes, offset, size) {
154
+ let value = 0n;
155
+ for (let index = size - 1; index >= 0; index--) value = (value << 8n) | BigInt(bytes[offset + index]);
156
+ return value;
157
+ }
158
+
159
+ function u64ToNumber(value, what) {
160
+ if (value > BigInt(Number.MAX_SAFE_INTEGER)) throw Error(`${what} exceeds Number.MAX_SAFE_INTEGER`);
161
+ return Number(value);
162
+ }
163
+
164
+ function decodeArg(type, bytes, offset, what) {
165
+ if (type === 'u32') {
166
+ if (offset + 4 > bytes.length) throw new NativeCodecError('truncated-u32', `${what}: truncated u32 at ${offset}`);
167
+ return { value: Number(readLe(bytes, offset, 4)), size: 4 };
168
+ }
169
+ if (type === 'u64') {
170
+ if (offset + 8 > bytes.length) throw new NativeCodecError('truncated-u64', `${what}: truncated u64 at ${offset}`);
171
+ return { value: readLe(bytes, offset, 8), size: 8 };
172
+ }
173
+ if (type === 'pubkey') {
174
+ if (offset + 32 > bytes.length) throw new NativeCodecError('truncated-pubkey', `${what}: truncated pubkey at ${offset}`);
175
+ return { value: encodeBase58(bytes.subarray(offset, offset + 32)), size: 32 };
176
+ }
177
+ if (type === 'string') {
178
+ if (offset + 8 > bytes.length) throw new NativeCodecError('truncated-string-length', `${what}: truncated string length at ${offset}`);
179
+ const length = u64ToNumber(readLe(bytes, offset, 8), `${what} string length`);
180
+ if (offset + 8 + length > bytes.length) throw new NativeCodecError('truncated-string-body', `${what}: truncated string body at ${offset + 8}`);
181
+ return { value: bytes.subarray(offset + 8, offset + 8 + length).toString('utf8'), size: 8 + length };
182
+ }
183
+ throw new NativeCodecError('unknown-arg-type', `unknown arg type ${type}`);
184
+ }
185
+
186
+ function encodeArg(type, value, what) {
187
+ if (type === 'u32') {
188
+ const n = typeof value === 'bigint' ? Number(value) : value;
189
+ if (!Number.isSafeInteger(n) || n < 0 || n > 0xffffffff) throw Error(`${what}: not a u32`);
190
+ const out = Buffer.alloc(4);
191
+ out.writeUInt32LE(n);
192
+ return out;
193
+ }
194
+ if (type === 'u64') {
195
+ const v = typeof value === 'string' && /^-?\d+$/.test(value) ? BigInt(value)
196
+ : typeof value === 'number' ? BigInt(value)
197
+ : typeof value === 'bigint' ? value
198
+ : (() => { throw Error(`${what}: not a u64`); })();
199
+ if (v < 0n || v >= 1n << 64n) throw Error(`${what}: out of u64 range`);
200
+ const out = Buffer.alloc(8);
201
+ for (let index = 0; index < 8; index++) out[index] = Number((v >> BigInt(index * 8)) & 0xffn);
202
+ return out;
203
+ }
204
+ if (type === 'pubkey') {
205
+ const text = String(value);
206
+ const raw = text.startsWith('0x')
207
+ ? Buffer.from(text.slice(2), 'hex')
208
+ : Buffer.from(decodeBase58(text));
209
+ if (raw.length !== 32) throw Error(`${what}: not a 32-byte pubkey`);
210
+ return raw;
211
+ }
212
+ if (type === 'string') {
213
+ const body = Buffer.from(String(value), 'utf8');
214
+ const head = Buffer.alloc(8);
215
+ for (let index = 0; index < 8; index++) head[index] = Number((BigInt(body.length) >> BigInt(index * 8)) & 0xffn);
216
+ return Buffer.concat([head, body]);
217
+ }
218
+ throw Error(`unknown arg type ${type}`);
219
+ }
220
+
221
+ export class NativeCodecError extends Error {
222
+ constructor(reason, detail) {
223
+ super(detail ? `${reason}: ${detail}` : reason);
224
+ this.name = 'NativeCodecError';
225
+ this.reason = reason;
226
+ }
227
+ }
228
+
229
+ /**
230
+ * Decode one native instruction payload.
231
+ *
232
+ * Returns null with a NAMED REASON attached via `nativeRefusal` when the bytes
233
+ * cannot honestly be named (unknown selector, wire-rejected variant, truncated
234
+ * payload, trailing bytes) — the tri-state matters: `program` not native means
235
+ * the caller was never supposed to ask, `null` means the native program's own
236
+ * table refuses, a result means the bytes are named AND re-encodable.
237
+ *
238
+ * `trailing: 'report'` (default) returns the decoded instruction plus the
239
+ * byte count the table does not explain; 'error' refuses instead.
240
+ */
241
+ export function decodeNativeInstruction(program, bytes, { trailing = 'report' } = {}) {
242
+ const table = TABLES.get(program);
243
+ if (!table) return { refused: 'not-native' };
244
+ if (!(bytes instanceof Uint8Array) || bytes.length === 0) {
245
+ throw new NativeCodecError('empty-payload', `${program}: no instruction data`);
246
+ }
247
+ if (bytes.length < table.width) {
248
+ throw new NativeCodecError('truncated-selector', `${program}: ${bytes.length} byte(s) < ${table.width}-byte selector`);
249
+ }
250
+ const selector = Number(readLe(bytes, 0, table.width));
251
+ const entry = table.ixs[selector];
252
+ if (!entry) {
253
+ throw new NativeCodecError('unknown-selector', `${program}: tag ${selector} is not in the pinned interface`);
254
+ }
255
+ if (entry.wire === 'rejected') {
256
+ throw new NativeCodecError('wire-rejected', `${program}: ${entry.name} (tag ${selector}) is InvalidInstructionData on the wire (runtime rule)`);
257
+ }
258
+ const args = {};
259
+ let offset = table.width;
260
+ for (const spec of entry.args) {
261
+ const decoded = decodeArg(spec.type, bytes, offset, entry.name);
262
+ args[spec.name] = decoded.value;
263
+ offset += decoded.size;
264
+ }
265
+ const trailingBytes = bytes.length - offset;
266
+ if (trailingBytes > 0 && trailing === 'error') {
267
+ throw new NativeCodecError('trailing-bytes', `${program}: ${entry.name} explains ${offset} of ${bytes.length} byte(s)`);
268
+ }
269
+ return {
270
+ name: entry.name,
271
+ selector,
272
+ selectorWidth: table.width,
273
+ args,
274
+ consumed: offset,
275
+ bytes: bytes.length,
276
+ trailing: { count: trailingBytes },
277
+ roundTripByteExact: encodeNativeInstruction(program, entry.name, args).equals(Buffer.from(bytes)),
278
+ scheme: 'native',
279
+ source: 'pinned-native',
280
+ };
281
+ }
282
+
283
+ /**
284
+ * Why the last `decodeNativeInstruction` refused, or null if it did not.
285
+ * The refusal reason is part of the claim, so tests can pin it.
286
+ */
287
+ export function nativeRefusal(program, bytes) {
288
+ try {
289
+ const result = decodeNativeInstruction(program, bytes);
290
+ return result.refused ? result.refused : null;
291
+ } catch (error) {
292
+ return error instanceof NativeCodecError ? error.reason : `thrown: ${error.message}`;
293
+ }
294
+ }
295
+
296
+ /** Re-encode one native instruction from a name + args. Throws on mismatch. */
297
+ export function encodeNativeInstruction(program, name, values = {}) {
298
+ const table = TABLES.get(program);
299
+ if (!table) throw new NativeCodecError('not-native', program);
300
+ const selector = table.ixs.findIndex((entry) => entry.name === name);
301
+ if (selector < 0) throw new NativeCodecError('unknown-selector', `${program}: ${name}`);
302
+ const entry = table.ixs[selector];
303
+ const head = Buffer.alloc(table.width);
304
+ for (let index = 0; index < table.width; index++) head[index] = Number((BigInt(selector) >> BigInt(index * 8)) & 0xffn);
305
+ const chunks = [head];
306
+ for (const spec of entry.args) {
307
+ const value = values[spec.name];
308
+ if (value === undefined || value === null) throw new NativeCodecError('missing-arg', `${name}.${spec.name}`);
309
+ chunks.push(encodeArg(spec.type, value, `${name}.${spec.name}`));
310
+ }
311
+ return Buffer.concat(chunks);
312
+ }
313
+
314
+ /** Names the table can claim for `program` (coverage callers). */
315
+ export function nativeNames(program) {
316
+ const table = TABLES.get(program);
317
+ return table ? table.ixs.filter((entry) => entry.wire !== 'rejected').map((entry) => entry.name) : [];
318
+ }
319
+
320
+ /** Whether `name` belongs to `program`'s native table. */
321
+ export function isNativeName(program, name) {
322
+ const table = TABLES.get(program);
323
+ return Boolean(table) && table.ixs.some((entry) => entry.name === name && entry.wire !== 'rejected');
324
+ }
package/src/peer.mjs ADDED
@@ -0,0 +1,95 @@
1
+ // The ONE optional peer the acquisition path needs, loaded so that its ABSENCE
2
+ // is a sentence instead of a Node loader stack trace.
3
+ //
4
+ // WHY THIS FILE EXISTS
5
+ //
6
+ // Measured 2026-11-06 from a bare install of the packed tarball (the bytes a
7
+ // `github:auroraxo/sememe#vX.Y.Z` install receives), with the peer absent:
8
+ //
9
+ // import('sememe/pull') -> ERR_MODULE_NOT_FOUND: Cannot find package
10
+ // '@solana/web3.js' imported from .../node_modules/sememe/src/pull.mjs
11
+ // import('sememe/gap') -> the same, from .../src/acquire.mjs
12
+ //
13
+ // The CLI already refused in sememe's own voice ("pull needs @solana/web3.js:
14
+ // install it next to sememe (npm i @solana/web3.js@1.98.4)"), because
15
+ // bin/sememe.mjs catches the failed import and rewrites it. The LIBRARY
16
+ // surfaces did not: `sememe/pull` and `sememe/gap` are documented export
17
+ // subpaths, and a consumer who writes
18
+ //
19
+ // import { pull } from 'sememe/pull';
20
+ //
21
+ // got a loader stack trace with an ERR_MODULE_NOT_FOUND code — no install
22
+ // command, no product-level name. The repo's own rule says a refusal names
23
+ // the fix; that rule was true for the CLI and false for the library.
24
+ //
25
+ // WHY A DYNAMIC IMPORT
26
+ //
27
+ // A static import of a missing package throws during module EVALUATION with
28
+ // the raw Node error — the very thing this file exists to prevent, and it
29
+ // cannot be intercepted from inside the module. A dynamic import CAN be
30
+ // caught, so the absence is rewritten here.
31
+ //
32
+ // WHY TOP-LEVEL AWAIT
33
+ //
34
+ // The refusal has to happen when the consumer imports the subpath, not when
35
+ // they call a function: `import { pull } from 'sememe/pull'` must not hand
36
+ // back a module whose exports only fail later. Top-level await makes the
37
+ // module's evaluation itself the refusal point, so the error reaches the
38
+ // consumer's import statement.
39
+ //
40
+ // The decode core must stay importable in a dependency-free repository
41
+ // (pinned by test/deps.test.mjs), and this file is NOT reachable from the
42
+ // package entry (`index.mjs`) — so a decoder-only consumer still installs
43
+ // zero packages. The peer import literal lives here, so the release-surface
44
+ // instrument, which derives the peer-needing commands from the import graph,
45
+ // still derives them from the tree.
46
+
47
+ export const PEER_PACKAGE = '@solana/web3.js';
48
+ export const PEER_INSTALL = 'npm i @solana/web3.js@1.98.4';
49
+
50
+ /** The refusal sememe raises when the optional peer is not installed. */
51
+ export class PeerDependencyMissingError extends Error {
52
+ constructor(cause) {
53
+ super(`the acquisition path needs ${PEER_PACKAGE}: install it next to sememe (${PEER_INSTALL})`);
54
+ this.name = 'PeerDependencyMissingError';
55
+ // Deliberately NOT 'ERR_MODULE_NOT_FOUND': a consumer that branches on
56
+ // error codes must be able to tell "sememe refused" from "Node could not
57
+ // resolve something".
58
+ this.code = 'peer_dependency_missing';
59
+ this.peer = PEER_PACKAGE;
60
+ this.install = PEER_INSTALL;
61
+ this.cause = cause;
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Load the optional peer, or refuse with the sentence that fixes it.
67
+ *
68
+ * Only a genuine "the peer itself is not installed" is rewritten. Any other
69
+ * failure (the peer is installed but broken, a network error inside it) is
70
+ * rethrown unchanged: masking it as a missing peer would send a consumer to
71
+ * install a package they already have.
72
+ */
73
+ export async function loadPeer() {
74
+ try {
75
+ return await import('@solana/web3.js');
76
+ } catch (error) {
77
+ const code = error?.code;
78
+ const message = String(error?.message ?? error);
79
+ if (code === 'ERR_MODULE_NOT_FOUND' && message.includes(PEER_PACKAGE)) {
80
+ throw new PeerDependencyMissingError(error);
81
+ }
82
+ throw error;
83
+ }
84
+ }
85
+
86
+ const peer = await loadPeer();
87
+
88
+ // web3.js is CommonJS. Node's interop usually exposes the named exports
89
+ // directly, but the default export is the fallback; if neither carries
90
+ // PublicKey the peer is installed but unusable, which must NOT be reported
91
+ // as "install the peer".
92
+ export const PublicKey = peer?.PublicKey ?? peer?.default?.PublicKey;
93
+ if (typeof PublicKey !== 'function') {
94
+ throw Error(`${PEER_PACKAGE} is installed but does not export PublicKey — the peer is broken, not missing`);
95
+ }