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,276 @@
1
+ // Derive the SPL Associated Token Account wire table from the VENDORED
2
+ // UPSTREAM RUST.
3
+ //
4
+ // Same contract as src/native-interface.mjs / src/token-interface.mjs /
5
+ // src/raydium-interface.mjs: this program publishes NO IDL account to pull
6
+ // (measured 2026-11-11 over 6 finalized mainnet blocks: 1,410 payloads, the
7
+ // single largest acquisition gap in research/ecosystem-coverage-2026-11-11.json,
8
+ // acquirable=false at both pullable addresses). Its interface is the upstream
9
+ // Rust sources, and its wire rule is small enough to be pinned exactly:
10
+ //
11
+ // - interface/src/instruction.rs declares `AssociatedTokenAccountInstruction`
12
+ // as a borsh UNIT enum: Create, CreateIdempotent, RecoverNested. The
13
+ // instruction payload is borsh's own enum encoding — the variant index,
14
+ // ONE byte, and no args — and the borsh serialise arms are the ground
15
+ // truth for the tag: an index pushed with `push(idx)` in serialise is
16
+ // the wire byte, and a declaration that no arm pushes is an upstream
17
+ // addition nobody re-reviewed (it fails loudly instead of decoding as
18
+ // "unknown").
19
+ // - program/src/processor.rs is the RUNTIME verdict: `input.is_empty()` is
20
+ // executed as `Create` (the empty payload the measurement saw 4 times is
21
+ // a real instruction the chain runs, not a refusal), everything else is
22
+ // judged by `try_from_slice`, and an unjudged input is
23
+ // ProgramError::InvalidInstructionData — a named refusal, never a guess.
24
+ //
25
+ // Why derive instead of transcribe: the System/ComputeBudget table was once
26
+ // hand-typed, and the transcription carried a defect that survived review.
27
+ // Three variants is small, but the rule that held is the same: the table is
28
+ // derived from upstream's own text, both directions are cross-checked, and
29
+ // every vendored file is hashed so a swapped source refuses the load.
30
+ //
31
+ // Dependency-free on purpose: it is imported by the decode path.
32
+
33
+ import { readFileSync } from 'node:fs';
34
+ import { createHash } from 'node:crypto';
35
+ import path from 'node:path';
36
+ import { fileURLToPath } from 'node:url';
37
+
38
+ const HERE = path.dirname(fileURLToPath(import.meta.url));
39
+ export const REPO_ROOT = path.resolve(HERE, '..');
40
+
41
+ export const ATA_PINS = {
42
+ interface: {
43
+ repo: 'solana-program/associated-token-account',
44
+ dir: 'spl-associated-token-account',
45
+ pin: 'fbf5379e7398ac8b231c73ee8a9ff677b43dd0ec',
46
+ license: 'Apache-2.0 (LICENSE vendored beside the sources)',
47
+ files: [
48
+ { file: 'interface/src/instruction.rs', role: 'AssociatedTokenAccountInstruction: borsh unit enum, serialise arms carry the wire byte' },
49
+ { file: 'program/src/processor.rs', role: 'runtime verdict: empty input falls back to Create, borsh try_from_slice judges the rest, InvalidInstructionData refuses' },
50
+ { file: 'interface/src/lib.rs', role: 'the program id (declare_id), read from upstream, not re-typed' },
51
+ ],
52
+ },
53
+ };
54
+
55
+ export const ATA_PROGRAM = 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL';
56
+
57
+ const stripComments = (src) => src.replace(/\/\*[\s\S]*?\*\//g, '').replace(/\/\/[^\n]*/g, '');
58
+
59
+ /** The body of a `fn ...` / `impl ...` block from comment-stripped source. */
60
+ function blockBody(src, signature) {
61
+ const at = src.indexOf(signature);
62
+ if (at < 0) throw Error(`block not found: ${signature}`);
63
+ const open = src.indexOf('{', at);
64
+ let depth = 0;
65
+ for (let i = open; i < src.length; i++) {
66
+ if (src[i] === '{') depth++;
67
+ else if (src[i] === '}') {
68
+ depth--;
69
+ if (depth === 0) return src.slice(open + 1, i);
70
+ }
71
+ }
72
+ throw Error(`${signature}: body never closes`);
73
+ }
74
+
75
+ /**
76
+ * The declared variants of `pub enum AssociatedTokenAccountInstruction`, in
77
+ * declaration order, with the declared field list (a unit variant has none).
78
+ * A tuple or struct variant is an upstream shape this module does not pin:
79
+ * it throws instead of inventing an encoding for it.
80
+ */
81
+ export function parseAtaVariants(src) {
82
+ // Upstream annotates variants with multi-line `#[cfg_attr(...)]` and doc
83
+ // attributes; they are not declarations and must go before the split.
84
+ src = src.replace(/#\[[\s\S]*?\]\s*/g, '');
85
+ const at = src.indexOf('pub enum AssociatedTokenAccountInstruction');
86
+ if (at < 0) throw Error('AssociatedTokenAccountInstruction enum not found');
87
+ const open = src.indexOf('{', at);
88
+ let depth = 0, end = -1;
89
+ for (let i = open; i < src.length; i++) {
90
+ if (src[i] === '{') depth++;
91
+ else if (src[i] === '}') {
92
+ depth--;
93
+ if (depth === 0) { end = i; break; }
94
+ }
95
+ }
96
+ const body = src.slice(open + 1, end);
97
+ const variants = [];
98
+ for (const part of body.split(',')) {
99
+ const trimmed = part.trim();
100
+ if (!trimmed) continue;
101
+ const unit = trimmed.match(/^([A-Z][A-Za-z0-9_]*)$/);
102
+ if (unit) { variants.push({ name: unit[1], fields: [] }); continue; }
103
+ if (/^[A-Z][A-Za-z0-9_]*\s*[({]/.test(trimmed)) {
104
+ throw Error(`AssociatedTokenAccountInstruction: variant "${trimmed}" is not a unit variant — this module pins unit enums only; re-review the pin`);
105
+ }
106
+ throw Error(`AssociatedTokenAccountInstruction: unreadable variant "${trimmed}"`);
107
+ }
108
+ if (!variants.length) throw Error('AssociatedTokenAccountInstruction yielded no variants');
109
+ return variants;
110
+ }
111
+
112
+ /**
113
+ * The wire byte of each variant, read from upstream's OWN builders.
114
+ *
115
+ * The borsh `serialise` is macro-generated, so it is not in the source text —
116
+ * but upstream's instruction builders are, and each one writes the payload
117
+ * byte it means to send together with the variant it means:
118
+ *
119
+ * data: vec![2], // AssociatedTokenAccountInstruction::RecoverNested
120
+ * ... build_associated_token_account_instruction(..., 0, // ...::Create
121
+ *
122
+ * So the wire byte is upstream's own literal, cross-checked against the
123
+ * declaration index upstream's borsh derive would use (the index in the
124
+ * enum's declaration order). Both must agree: a builder that writes a byte
125
+ * the declaration index does not carry is an upstream shape this module does
126
+ * not pin, and it fails loudly instead of decoding as "unknown".
127
+ */
128
+ function deriveWireTags(rawSrc, variantNames) {
129
+ const src = rawSrc;
130
+ const wires = new Map();
131
+ // (a) the literal payload bytes of the builders, with the named variant
132
+ const push = (byte, name, how) => {
133
+ if (wires.has(name)) {
134
+ if (wires.get(name).tag !== byte) {
135
+ throw Error(`upstream writes ${name} with two different bytes (${wires.get(name).tag} and ${byte})`);
136
+ }
137
+ return;
138
+ }
139
+ wires.set(name, { tag: byte, how });
140
+ };
141
+ for (const m of src.matchAll(
142
+ /data:\s*vec!\[(\d+)\]\s*,?\s*\/\/\s*AssociatedTokenAccountInstruction::([A-Za-z0-9_]+)/g,
143
+ )) {
144
+ push(Number(m[1]), m[2], 'data: vec![byte] in the instruction builder');
145
+ }
146
+ for (const m of src.matchAll(
147
+ /(\d+)\s*,\s*\/\/\s*AssociatedTokenAccountInstruction::([A-Za-z0-9_]+)/g,
148
+ )) {
149
+ push(Number(m[1]), m[2], 'the instruction byte passed to the builder');
150
+ }
151
+
152
+ // Every declared variant must be written by upstream somewhere, and every
153
+ // written name must be a declared variant.
154
+ for (const [index, name] of variantNames.entries()) {
155
+ if (!wires.has(name)) {
156
+ throw Error(`variant ${name} (index ${index}) is declared upstream but no builder writes it — upstream added a variant this table does not pin`);
157
+ }
158
+ if (wires.get(name).tag !== index) {
159
+ throw Error(`upstream writes ${name} with byte ${wires.get(name).tag}, but its declaration index is ${index} — this module pins the borsh index encoding only`);
160
+ }
161
+ }
162
+ for (const name of wires.keys()) {
163
+ if (!variantNames.includes(name)) {
164
+ throw Error(`upstream writes ${name}, which the enum does not declare`);
165
+ }
166
+ }
167
+ return wires;
168
+ }
169
+
170
+ /** The processor's own runtime verdict, read out of the vendored text. */
171
+ function deriveRuntime(src, variantNames) {
172
+ const processBody = blockBody(src, 'pub fn process_instruction');
173
+
174
+ // `let instruction = if input.is_empty() { <Fallback> } else { ... }`
175
+ const fallback = processBody.match(
176
+ /if input\.is_empty\(\)\s*\{\s*AssociatedTokenAccountInstruction::([A-Za-z0-9_]+)\s*\}/,
177
+ );
178
+ if (!fallback) throw Error('processor: the empty-input branch is not the shape this module pins');
179
+ const emptyInputFallback = fallback[1];
180
+ if (!variantNames.includes(emptyInputFallback)) {
181
+ throw Error(`processor: empty-input fallback ${emptyInputFallback} is not a declared variant`);
182
+ }
183
+
184
+ // The judge: borsh `try_from_slice`, refusal mapped to InvalidInstructionData.
185
+ if (!/try_from_slice\(input\)/.test(processBody)) {
186
+ throw Error('processor: borsh try_from_slice is not the judge this module pins');
187
+ }
188
+ if (!/InvalidInstructionData/.test(processBody)) {
189
+ throw Error('processor: the unjudged-input refusal (InvalidInstructionData) is missing');
190
+ }
191
+
192
+ return {
193
+ judge: 'borsh AssociatedTokenAccountInstruction::try_from_slice(input)',
194
+ emptyInputFallback,
195
+ unknownInputRefusal: 'ProgramError::InvalidInstructionData',
196
+ source: 'program/src/processor.rs at the pin',
197
+ };
198
+ }
199
+
200
+ /** The program id, read from the vendored `declare_id!`, not re-typed. */
201
+ function deriveProgramId(src) {
202
+ const m = src.match(/declare_id!\("([1-9A-HJ-NP-Za-km-z]{32,44})"\)/);
203
+ if (!m) throw Error('interface/src/lib.rs: no declare_id! found');
204
+ return m[1];
205
+ }
206
+
207
+ /** Derive the ATA interface document from the vendored upstream sources. */
208
+ export function deriveAtaInterface(spec, { root = REPO_ROOT } = {}) {
209
+ const base = path.join(root, 'vendor', ATA_PINS.interface.dir, ATA_PINS.interface.pin);
210
+ const files = ATA_PINS.interface.files.map((entry) => {
211
+ const raw = readFileSync(path.join(base, entry.file));
212
+ return { ...entry, sha256: `sha256:${createHash('sha256').update(raw).digest('hex')}` };
213
+ });
214
+
215
+ const instructionRaw = readFileSync(path.join(base, 'interface/src/instruction.rs'), 'utf8');
216
+ const instructionSrc = stripComments(instructionRaw);
217
+ const processorSrc = stripComments(
218
+ readFileSync(path.join(base, 'program/src/processor.rs'), 'utf8'),
219
+ );
220
+ const libSrc = readFileSync(path.join(base, 'interface/src/lib.rs'), 'utf8');
221
+
222
+ const program = deriveProgramId(libSrc);
223
+ if (program !== spec.expectedProgram) {
224
+ throw Error(`upstream declares program ${program}, this table pins ${spec.expectedProgram}`);
225
+ }
226
+
227
+ const variants = parseAtaVariants(instructionSrc);
228
+ const wires = deriveWireTags(instructionRaw, variants.map((v) => v.name));
229
+ const runtime = deriveRuntime(processorSrc, variants.map((v) => v.name));
230
+
231
+ const instructions = variants.map((variant) => ({
232
+ tag: wires.get(variant.name).tag,
233
+ name: variant.name,
234
+ wire: 'borsh-unit-enum',
235
+ args: [],
236
+ }));
237
+
238
+ return {
239
+ program,
240
+ label: spec.label,
241
+ scheme: 'native-ata',
242
+ source: {
243
+ repo: ATA_PINS.interface.repo,
244
+ pin: ATA_PINS.interface.pin,
245
+ license: ATA_PINS.interface.license,
246
+ enum: 'AssociatedTokenAccountInstruction (borsh unit enum: the variant index is the one-byte payload)',
247
+ programSource: 'vendored interface/src/lib.rs declare_id',
248
+ files,
249
+ vendoredAt: `vendor/${ATA_PINS.interface.dir}/${ATA_PINS.interface.pin}`,
250
+ derivation: 'src/ata-interface.mjs: tag = the declaration index upstream\'s borsh serialise arm pushes; args = none (unit enum); runtime verdicts read from the processor (empty input falls back to Create, borsh try_from_slice judges, InvalidInstructionData refuses)',
251
+ },
252
+ runtime,
253
+ selector: {
254
+ width: 1,
255
+ encoding: 'borsh enum index, u8; empty input is the processor\'s own Create fallback, not an unknown selector',
256
+ },
257
+ argEncoding: 'unit enum: payload is exactly the one index byte; no argument bytes',
258
+ instructions,
259
+ };
260
+ }
261
+
262
+ /** The pinned ATA spec. The expected program is the opinion layer; the
263
+ * derivation checks it against upstream's own declare_id. */
264
+ export const ATA_SPEC = {
265
+ program: ATA_PROGRAM,
266
+ expectedProgram: ATA_PROGRAM,
267
+ label: 'SPL Associated Token Account',
268
+ };
269
+
270
+ /** sha256 of a vendored upstream file, as `sha256:<hex>`. */
271
+ export function vendoredAtaHash(file, { root = REPO_ROOT } = {}) {
272
+ const raw = readFileSync(
273
+ path.join(root, 'vendor', ATA_PINS.interface.dir, ATA_PINS.interface.pin, file),
274
+ );
275
+ return `sha256:${createHash('sha256').update(raw).digest('hex')}`;
276
+ }
package/src/ata.mjs ADDED
@@ -0,0 +1,223 @@
1
+ // The SPL Associated Token Account decoder: the vendored-upstream path for
2
+ // the program with the LARGEST measured acquisition gap.
3
+ //
4
+ // Why this file exists: measured 2026-11-11 across 6 finalized mainnet
5
+ // blocks, ATokenGPvbd... carried 1,410 watched payloads and answered at
6
+ // NEITHER pullable IDL address — research/ecosystem-coverage-2026-11-11.json
7
+ // lists it first in its acquisition queue: "no on-chain IDL at either
8
+ // address; needs a first-party published copy or a vendored upstream source".
9
+ // Its interface is the upstream Rust, so the wire table is derived from the
10
+ // vendored sources by src/ata-interface.mjs and committed as a reviewable
11
+ // artifact (`interface/ata-<program>.json`).
12
+ //
13
+ // Ground truth chain, every row derived and then VERIFIED, not typed:
14
+ // 1. INTERFACE solana-program/associated-token-account@fbf5379
15
+ // interface/src/instruction.rs (AssociatedTokenAccountInstruction)
16
+ // program/src/processor.rs (the runtime verdict)
17
+ // interface/src/lib.rs (declare_id -> program id)
18
+ // DERIVED by tools/ata-interface.mjs into
19
+ // interface/ata-<program>.json. This module reads the
20
+ // artifact at load and re-hashes every vendored file against
21
+ // the sha256 the artifact claims; a mismatch REFUSES the load
22
+ // instead of decoding from it — the two-check contract
23
+ // src/native.mjs, src/token.mjs and src/raydium.mjs proved.
24
+ // 2. WIRE borsh's enum encoding of a UNIT enum: the payload is the
25
+ // one-byte variant index, and no argument bytes follow. The
26
+ // index upstream's builders write (data: vec![2], // ...::RecoverNested)
27
+ // is cross-checked against the declaration index at
28
+ // derivation time.
29
+ // 3. RUNTIME the pin's processor: `input.is_empty()` is executed as
30
+ // `Create` — so an EMPTY payload is a named Create, exactly
31
+ // what upstream runs, not an "unknown-selector" refusal;
32
+ // everything else is judged by `try_from_slice`, and an
33
+ // unjudged input is ProgramError::InvalidInstructionData —
34
+ // this module refuses it under the same name.
35
+ //
36
+ // What is deliberately NOT claimed: no account list, no address derivation
37
+ // (see upstream address.rs), no state layout — only instruction data. The
38
+ // table describes the program AT THE PIN.
39
+ //
40
+ // Strictness mirrors src/token.mjs: an unknown selector, a truncated payload
41
+ // or a payload with trailing bytes is a refusal with a named reason — never
42
+ // a confident wrong label.
43
+
44
+ import { readFileSync } from 'node:fs';
45
+ import path from 'node:path';
46
+ import { fileURLToPath } from 'node:url';
47
+ import { deriveAtaInterface, ATA_SPEC, ATA_PINS, ATA_PROGRAM, vendoredAtaHash } from './ata-interface.mjs';
48
+
49
+ const HERE = path.dirname(fileURLToPath(import.meta.url));
50
+ const ROOT = path.resolve(HERE, '..');
51
+
52
+ export { ATA_PROGRAM };
53
+
54
+ /**
55
+ * Load the ATA interface: DERIVE it from the vendored upstream Rust, then
56
+ * require the committed artifact to agree with the derivation byte for byte.
57
+ *
58
+ * Two checks, deliberately (the contract the three sibling modules proved):
59
+ * - the derivation runs against the vendored bytes themselves, so a swapped
60
+ * or edited source file cannot silently change the table it describes;
61
+ * - the committed artifact must equal that derivation, so a tampered or
62
+ * stale artifact refuses the load instead of decoding from it.
63
+ */
64
+ function loadInterface(spec) {
65
+ const artifactPath = path.join(ROOT, 'interface', `ata-${spec.program}.json`);
66
+ const derived = deriveAtaInterface(spec);
67
+ let current;
68
+ try {
69
+ current = readFileSync(artifactPath, 'utf8');
70
+ } catch (error) {
71
+ throw new Error(`ata interface artifact for ${spec.label} is missing: ${error.message}`);
72
+ }
73
+ const expected = `${JSON.stringify(derived, null, 2)}\n`;
74
+ if (current !== expected) {
75
+ const claimed = derived.source.files.map((entry) => entry.sha256).join(', ');
76
+ throw new Error(
77
+ `ata interface for ${spec.label} refuses to load: interface/ata-${spec.program}.json does not equal ` +
78
+ `a fresh derivation of ${derived.source.file ?? 'the vendored sources'} (${claimed}, hashed now: ` +
79
+ `${derived.source.files.map((entry) => vendoredAtaHash(entry.file)).join(', ')})`,
80
+ );
81
+ }
82
+ if (!derived.instructions.length || !derived.selector?.width) {
83
+ throw new Error(`ata interface artifact for ${spec.label} is malformed`);
84
+ }
85
+ return derived;
86
+ }
87
+
88
+ const ARTIFACT = loadInterface(ATA_SPEC);
89
+ const BY_TAG = new Map(ARTIFACT.instructions.map((entry) => [entry.tag, entry]));
90
+ const EMPTY_TAG = ARTIFACT.instructions.find(
91
+ (entry) => entry.name === ARTIFACT.runtime.emptyInputFallback,
92
+ )?.tag;
93
+
94
+ export class AtaCodecError extends Error {
95
+ constructor(reason, detail) {
96
+ super(detail ? `${reason}: ${detail}` : reason);
97
+ this.name = 'AtaCodecError';
98
+ this.reason = reason;
99
+ }
100
+ }
101
+
102
+ /** Whether `program` is the ATA program this module owns. */
103
+ export function isAtaProgram(program) {
104
+ return program === ARTIFACT.program;
105
+ }
106
+
107
+ /** Program ids this module can decode, for callers that enumerate. */
108
+ export function ataPrograms() {
109
+ return [ARTIFACT.program];
110
+ }
111
+
112
+ /** Provenance for the ATA table. */
113
+ export function ataProvenance(program = ATA_PROGRAM) {
114
+ if (!isAtaProgram(program)) return null;
115
+ return {
116
+ label: ARTIFACT.label,
117
+ interface: `${ARTIFACT.source.repo}@${ARTIFACT.source.pin.slice(0, 8)} ${ARTIFACT.source.files[0].file} ${ARTIFACT.source.enum}`,
118
+ encoding: ARTIFACT.argEncoding,
119
+ runtime: `${ARTIFACT.runtime.judge}; ${ARTIFACT.runtime.emptyInputFallback} = the empty-input fallback; unjudged input is ${ARTIFACT.runtime.unknownInputRefusal}`,
120
+ license: ARTIFACT.source.license,
121
+ variants: ARTIFACT.instructions.length,
122
+ };
123
+ }
124
+
125
+ /**
126
+ * Decode one ATA instruction payload.
127
+ *
128
+ * Returns `{ refused: 'not-ata' }` when the caller was never supposed to ask,
129
+ * a named result when the bytes are upstream's own, and throws AtaCodecError
130
+ * with a named reason when the bytes cannot honestly be named — the same
131
+ * tri-state contract as src/token.mjs.
132
+ */
133
+ export function decodeAtaInstruction(program, bytes, { trailing = 'report' } = {}) {
134
+ if (!isAtaProgram(program)) return { refused: 'not-ata' };
135
+ if (!(bytes instanceof Uint8Array)) {
136
+ throw new AtaCodecError('empty-payload', `${program}: no instruction data`);
137
+ }
138
+
139
+ // Upstream's own runtime rule, read from the pin's processor: an empty
140
+ // input IS a Create. Refusing it as "unknown-selector" would be a true
141
+ // statement about a table and a false one about the program.
142
+ if (bytes.length === 0) {
143
+ if (EMPTY_TAG === undefined) {
144
+ throw new AtaCodecError('empty-payload', `${program}: no instruction data and no empty-input fallback is pinned`);
145
+ }
146
+ return {
147
+ name: ARTIFACT.runtime.emptyInputFallback,
148
+ tag: EMPTY_TAG,
149
+ selectorWidth: 0,
150
+ args: {},
151
+ consumed: 0,
152
+ bytes: 0,
153
+ trailing: { count: 0 },
154
+ roundTripByteExact: EMPTY_TAG !== undefined && encodeAtaInstruction(program, ARTIFACT.runtime.emptyInputFallback).equals(Buffer.from(bytes)),
155
+ scheme: 'native-ata',
156
+ source: 'pinned-vendored-upstream',
157
+ note: 'empty input is upstream\'s own Create fallback (processor at the pin)',
158
+ };
159
+ }
160
+
161
+ const tag = bytes[0];
162
+ const entry = BY_TAG.get(tag);
163
+ if (!entry) {
164
+ // The pin's judge: borsh try_from_slice rejects an index it does not
165
+ // declare, and the processor maps that to InvalidInstructionData. The
166
+ // name below is upstream's own refusal, not a guess.
167
+ throw new AtaCodecError(
168
+ 'unknown-selector',
169
+ `${program}: tag ${tag} is not in the pinned ${ARTIFACT.label} interface (upstream refuses it as ${ARTIFACT.runtime.unknownInputRefusal})`,
170
+ );
171
+ }
172
+
173
+ const trailingBytes = bytes.length - 1;
174
+ if (trailingBytes > 0 && trailing === 'error') {
175
+ throw new AtaCodecError('trailing-bytes', `${program}: ${entry.name} explains 1 of ${bytes.length} byte(s)`);
176
+ }
177
+
178
+ return {
179
+ name: entry.name,
180
+ tag,
181
+ selectorWidth: 1,
182
+ args: {},
183
+ consumed: 1,
184
+ bytes: bytes.length,
185
+ trailing: { count: trailingBytes },
186
+ roundTripByteExact: encodeAtaInstruction(program, entry.name).equals(Buffer.from(bytes)),
187
+ scheme: 'native-ata',
188
+ source: 'pinned-vendored-upstream',
189
+ };
190
+ }
191
+
192
+ /** Why the last `decodeAtaInstruction` refused, or null if it did not. */
193
+ export function ataRefusal(program, bytes) {
194
+ try {
195
+ const result = decodeAtaInstruction(program, bytes);
196
+ return result.refused ? result.refused : null;
197
+ } catch (error) {
198
+ return error instanceof AtaCodecError ? error.reason : `thrown: ${error.message}`;
199
+ }
200
+ }
201
+
202
+ /** Re-encode one ATA instruction from a name (a unit enum carries no args). */
203
+ export function encodeAtaInstruction(program, name) {
204
+ if (!isAtaProgram(program)) throw new AtaCodecError('not-ata', program);
205
+ const entry = ARTIFACT.instructions.find((ix) => ix.name === name);
206
+ if (!entry) throw new AtaCodecError('unknown-variant', `${program}: ${name}`);
207
+ return Buffer.from([entry.tag]);
208
+ }
209
+
210
+ /** Names the table can claim for `program` (coverage callers). */
211
+ export function ataNames(program) {
212
+ if (!isAtaProgram(program)) return [];
213
+ return ARTIFACT.instructions.map((entry) => entry.name);
214
+ }
215
+
216
+ /** Whether `name` belongs to `program`'s ATA table. */
217
+ export function isAtaName(program, name) {
218
+ if (!isAtaProgram(program)) return false;
219
+ return ARTIFACT.instructions.some((entry) => entry.name === name);
220
+ }
221
+
222
+ /** The vendored pins, for callers that want to show the source. */
223
+ export { ATA_PINS };
package/src/base58.mjs ADDED
@@ -0,0 +1,96 @@
1
+ // Base58 (Bitcoin alphabet) — encode and decode, no dependencies.
2
+ //
3
+ // The decode core of sememe has to run inside repositories that have no
4
+ // npm dependencies at all (a Solana repo that wants to decode instructions
5
+ // without installing a toolkit). `@solana/web3.js` was the only third-party
6
+ // import in the decode path, and it was used for exactly two things:
7
+ // turning 32 raw bytes into a base58 string and back.
8
+ //
9
+ // This module is that part, on its own. It is deliberately small and
10
+ // deliberately boring: the same arithmetic as every other base58
11
+ // implementation, with the edge cases (leading zero bytes, invalid
12
+ // characters, oversized input) handled explicitly rather than silently.
13
+ //
14
+ // `decodeBase58` here is also what `src/decode.mjs` uses for payloads; the
15
+ // public export there is kept for compatibility.
16
+
17
+ const ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
18
+ const ALPHABET_MAP = new Map([...ALPHABET].map((character, index) => [character, index]));
19
+
20
+ /** Encode bytes as base58. Leading zero bytes become leading '1' characters. */
21
+ export function encodeBase58(bytes) {
22
+ if (!(bytes instanceof Uint8Array)) throw new Error('base58 input must be a Uint8Array/Buffer');
23
+ let zeros = 0;
24
+ while (zeros < bytes.length && bytes[zeros] === 0) zeros++;
25
+ // `digits` holds base58 digits least-significant first. It starts EMPTY on
26
+ // purpose: seeding it with a zero digit would add a spurious leading '1'
27
+ // for an all-zero input (32 zero bytes must encode to 32 '1' characters,
28
+ // not 33).
29
+ const digits = [];
30
+ for (let index = zeros; index < bytes.length; index++) {
31
+ let carry = bytes[index];
32
+ for (let position = 0; position < digits.length; position++) {
33
+ carry += digits[position] << 8;
34
+ digits[position] = carry % 58;
35
+ carry = (carry / 58) | 0;
36
+ }
37
+ while (carry > 0) {
38
+ digits.push(carry % 58);
39
+ carry = (carry / 58) | 0;
40
+ }
41
+ }
42
+ let out = '1'.repeat(zeros);
43
+ for (let index = digits.length - 1; index >= 0; index--) out += ALPHABET[digits[index]];
44
+ return out;
45
+ }
46
+
47
+ /**
48
+ * Decode a base58 string into bytes.
49
+ * Throws on an empty string or an invalid character — a payload that cannot
50
+ * be decoded is a caller error, not an empty result.
51
+ *
52
+ * The result is a `Buffer`, not a plain `Uint8Array`. That is not cosmetic:
53
+ * the decode path reads floats with `readFloatLE`/`readDoubleLE` and renders
54
+ * `bytes` arguments with `toString('base64')`, and it re-encodes and compares
55
+ * with `Buffer.equals`. A plain `Uint8Array` has none of those, so a
56
+ * base58-decoded payload failed in three different places while the SAME
57
+ * bytes as hex (which `decodeHex` produces as a Buffer) decoded fine:
58
+ *
59
+ * - src/union.mjs refused every base58 payload ("unionDecode needs the
60
+ * payload bytes as a Buffer"),
61
+ * - f32/f64 arguments died with "slice.readDoubleLE is not a function",
62
+ * - a `bytes` argument came out as a comma-separated byte list
63
+ * ("1,2,3,255") instead of base64 — corruption, not an error.
64
+ *
65
+ * Measured against the published v0.4.4 install; fixed in v0.4.5.
66
+ * A Buffer IS a Uint8Array, so every existing caller keeps working.
67
+ */
68
+ export function decodeBase58(input) {
69
+ if (typeof input !== 'string' || input.length === 0) {
70
+ throw new Error('base58 payload must be a non-empty string');
71
+ }
72
+ let zeros = 0;
73
+ while (zeros < input.length && input[zeros] === '1') zeros++;
74
+ // Empty accumulator for the same reason as in encodeBase58: an all-'1'
75
+ // input is N zero bytes, not N+1.
76
+ const bytes = [];
77
+ for (let index = zeros; index < input.length; index++) {
78
+ const value = ALPHABET_MAP.get(input[index]);
79
+ if (value === undefined) throw new Error(`invalid base58 character ${JSON.stringify(input[index])}`);
80
+ let carry = value;
81
+ for (let position = 0; position < bytes.length; position++) {
82
+ carry += bytes[position] * 58;
83
+ bytes[position] = carry & 0xff;
84
+ carry >>= 8;
85
+ }
86
+ while (carry > 0) {
87
+ bytes.push(carry & 0xff);
88
+ carry >>= 8;
89
+ }
90
+ }
91
+ const out = new Uint8Array(zeros + bytes.length);
92
+ for (let index = 0; index < bytes.length; index++) out[zeros + index] = bytes[bytes.length - 1 - index];
93
+ // A Buffer VIEW over the same bytes: no copy, and the byte order is
94
+ // exactly what was decoded.
95
+ return Buffer.from(out.buffer, out.byteOffset, out.byteLength);
96
+ }
@@ -0,0 +1,24 @@
1
+ // Canonical JSON — the exact serialization the store fingerprints.
2
+ //
3
+ // Lives in its own module so a CONSUMER can verify a receipt without
4
+ // pulling in the acquisition path: `src/pull.mjs` imports @solana/web3.js
5
+ // (PDA derivation), and a dependency-free repository that only wants to
6
+ // confirm "these bytes are the bytes the receipt claims" must not have to
7
+ // install it. `src/pull.mjs` re-exports this, so nothing that imports
8
+ // `canonicalJson` from there changes.
9
+ //
10
+ // Property order is sorted, arrays keep their order, and every value must be
11
+ // JSON-encodable. Two structurally equal objects therefore always produce
12
+ // the same bytes, which is what makes sha256(canonicalJson(idl) + '\n') a
13
+ // content address rather than a snapshot of whatever JSON.stringify felt
14
+ // like doing that day.
15
+
16
+ export function canonicalJson(value) {
17
+ if (Array.isArray(value)) return `[${value.map(canonicalJson).join(',')}]`;
18
+ if (value && typeof value === 'object') {
19
+ return `{${Object.keys(value).sort().map(k => `${JSON.stringify(k)}:${canonicalJson(value[k])}`).join(',')}}`;
20
+ }
21
+ const encoded = JSON.stringify(value);
22
+ if (encoded === undefined) throw new Error('non-JSON value');
23
+ return encoded;
24
+ }