sememe 0.0.0-stage → 0.4.21

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 +2667 -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 +205 -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 +1069 -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 +145 -0
  29. package/src/discriminators.mjs +177 -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 +450 -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 +250 -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,747 @@
1
+ // Derive the Raydium AMM v4 wire table from the VENDORED UPSTREAM RUST.
2
+ //
3
+ // Same contract as src/native-interface.mjs and src/token-interface.mjs, one
4
+ // level up in complexity: this program is a LEGACY (pre-Anchor) Rust program
5
+ // whose interface is neither an Anchor IDL nor a borsh enum. It publishes no
6
+ // IDL account at either address sememe can pull from (measured 2026-11-02 on
7
+ // mainnet), and its wire form is a hand-written `pack()`/`unpack()` pair with
8
+ // a SPARSE u8 tag set.
9
+ //
10
+ // Why derive instead of transcribe: the tag set is sparse AND the declaration
11
+ // order does NOT equal the wire order. Upstream declares 19 variants; only 12
12
+ // are reachable on the wire, and their tags are 1,3,4,6,7,9,11,14,15,16,17,18
13
+ // — a hand-written table would shift every tag and mislabel money movement,
14
+ // exactly the defect the SPL Token table was built to remove.
15
+ //
16
+ // The rules, each one upstream's own text:
17
+ // - the wire tag is the literal integer upstream's `pack()` arm pushes, and
18
+ // the `unpack()` arm that BINDS that same integer must name the same
19
+ // variant — the two are cross-checked, so a tag only one side knows
20
+ // throws;
21
+ // - the arg sequence is the sequence of byte writes in the `pack()` arm,
22
+ // cross-checked against the sequence of reads in the `unpack()` arm —
23
+ // by COUNT and WIDTH, because upstream names the same value differently on
24
+ // the two sides (pack writes `owner`, unpack reads `pubkey`);
25
+ // - a trailing OPTIONAL group (upstream's `if X.is_some()` write guarded by
26
+ // an `if rest.len() >= N` read) is present only when N bytes remain — it
27
+ // is NOT a 1-byte C-style option tag, and the threshold is upstream's;
28
+ // - a BRANCHED variant (upstream's `match param { 0 | 1 => .., 2 => .. }`)
29
+ // is selected by a discriminator byte upstream itself names, and the
30
+ // branch's value set is upstream's own enum text (AmmParams) or its own
31
+ // numeric literal;
32
+ // - `Fees` is a packed struct (8 x u64, LEN 64) declared in the upstream
33
+ // state module, so its bytes come from upstream's field order;
34
+ // - the variants upstream itself marks "not supported" are named with
35
+ // `wire: 'rejected'` — upstream PANICS on them (`unimplemented!()`), so a
36
+ // decoder that named them would disagree with the chain;
37
+ // - every byte write in a `pack()` arm must be covered by exactly one
38
+ // pinned rule, and every read in the matching `unpack()` arm must be
39
+ // covered by exactly one pinned write — an upstream change that nobody
40
+ // re-reviewed throws here instead of decoding as "unknown".
41
+ //
42
+ // Dependency-free on purpose: it is imported by the decode path.
43
+
44
+ import { readFileSync } from 'node:fs';
45
+ import { createHash } from 'node:crypto';
46
+ import path from 'node:path';
47
+ import { fileURLToPath } from 'node:url';
48
+
49
+ const HERE = path.dirname(fileURLToPath(import.meta.url));
50
+ export const REPO_ROOT = path.resolve(HERE, '..');
51
+
52
+ export const RAYDIUM_PINS = {
53
+ ammV4: {
54
+ repo: 'raydium-io/raydium-amm',
55
+ dir: 'raydium-amm',
56
+ pin: '91739d8d9f5cf347e0ce14ebe4d2bb7069fb31b6',
57
+ instructionFile: 'program/src/instruction.rs',
58
+ stateFile: 'program/src/state.rs',
59
+ },
60
+ };
61
+
62
+ export const RAYDIUM_AMM_V4 = '675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8';
63
+
64
+ const stripComments = (src) => src.replace(/\/\*[\s\S]*?\*\//g, '').replace(/\/\/[^\n]*/g, '');
65
+ const stripAttributes = (src) => src.replace(/#\[[\s\S]*?\]\s*/g, '');
66
+
67
+ /** The body of `fn ...` from comment-stripped source. */
68
+ function fnBody(src, signature) {
69
+ const at = src.indexOf(signature);
70
+ if (at < 0) throw Error(`function not found: ${signature}`);
71
+ const open = src.indexOf('{', at);
72
+ let depth = 0;
73
+ for (let i = open; i < src.length; i++) {
74
+ if (src[i] === '{') depth++;
75
+ else if (src[i] === '}') {
76
+ depth--;
77
+ if (depth === 0) return src.slice(open + 1, i);
78
+ }
79
+ }
80
+ throw Error(`${signature}: body never closes`);
81
+ }
82
+
83
+ /** The `{ ... }` block starting at `open`, plus where it ends. */
84
+ export function blockAt(text, open) {
85
+ let depth = 0;
86
+ for (let i = open; i < text.length; i++) {
87
+ if (text[i] === '{') depth++;
88
+ else if (text[i] === '}') {
89
+ depth--;
90
+ if (depth === 0) return { inner: text.slice(open + 1, i), end: i + 1 };
91
+ }
92
+ }
93
+ throw Error('block never closes');
94
+ }
95
+
96
+ /** Brace depth of `text` at `index`. */
97
+ function braceDepth(text, index) {
98
+ let depth = 0;
99
+ for (let i = 0; i < index; i++) {
100
+ if (text[i] === '{') depth++;
101
+ else if (text[i] === '}') depth--;
102
+ }
103
+ return depth;
104
+ }
105
+
106
+ /** Read a call expression `name(...)` starting at `at`, returning its text. */
107
+ function readCall(text, at) {
108
+ const open = text.indexOf('(', at);
109
+ if (open < 0) throw Error(`call without arguments at ${at}`);
110
+ let depth = 0;
111
+ for (let i = open; i < text.length; i++) {
112
+ if (text[i] === '(') depth++;
113
+ else if (text[i] === ')') {
114
+ depth--;
115
+ if (depth === 0) return { text: text.slice(at, i + 1), end: i + 1 };
116
+ }
117
+ }
118
+ throw Error('call never closes');
119
+ }
120
+
121
+ /** Split a block body on its TOP-LEVEL commas. */
122
+ function splitTopLevel(body) {
123
+ const parts = [];
124
+ let depth = 0;
125
+ let start = 0;
126
+ for (let i = 0; i < body.length; i++) {
127
+ const ch = body[i];
128
+ if (ch === '{' || ch === '(' || ch === '[') depth++;
129
+ else if (ch === '}' || ch === ')' || ch === ']') depth--;
130
+ else if (ch === ',' && depth === 0) {
131
+ parts.push(body.slice(start, i));
132
+ start = i + 1;
133
+ }
134
+ }
135
+ parts.push(body.slice(start));
136
+ return parts.map((part) => part.trim()).filter(Boolean);
137
+ }
138
+
139
+ /**
140
+ * The arms of a `match` block, at the TOP level of that block only.
141
+ *
142
+ * Upstream writes `1 => { ... }`, `0 | 2 | 5 => { ... }`, a brace-less
143
+ * `7 => Self::WithdrawPnl,` and a final `_ => { ... }`. A nested match (up
144
+ * writes `match param { ... }` inside a tag arm, and `match fees { ... }`
145
+ * inside a pack arm) must NOT be read as an arm of the outer match — the
146
+ * first version of this parser did exactly that and bound tag 0 and tag 1 to
147
+ * SetParams' inner arms.
148
+ */
149
+ export function parseArmsAtTopLevel(inner) {
150
+ const arms = [];
151
+ let i = 0;
152
+ while (i < inner.length) {
153
+ while (i < inner.length && /[\s,]/.test(inner[i])) i++;
154
+ if (i >= inner.length) break;
155
+ const headerStart = i;
156
+ let depth = 0;
157
+ let arrow = -1;
158
+ for (let j = i; j < inner.length; j++) {
159
+ const ch = inner[j];
160
+ if (ch === '{' || ch === '(' || ch === '[') depth++;
161
+ else if (ch === '}' || ch === ')' || ch === ']') depth--;
162
+ else if (ch === '=' && inner[j + 1] === '>' && depth === 0) { arrow = j; break; }
163
+ }
164
+ if (arrow < 0) throw Error('match arm without =>');
165
+ const header = inner.slice(headerStart, arrow).trim();
166
+ let k = arrow + 2;
167
+ while (k < inner.length && /\s/.test(inner[k])) k++;
168
+ if (inner[k] !== '{') {
169
+ // A brace-less arm (`7 => Self::WithdrawPnl,`) carries no args.
170
+ const end = inner.indexOf(',', k);
171
+ arms.push({ header, body: end < 0 ? inner.slice(k) : inner.slice(k, end), braceless: true });
172
+ i = end < 0 ? inner.length : end + 1;
173
+ continue;
174
+ }
175
+ const block = blockAt(inner, k);
176
+ arms.push({ header, body: block.inner });
177
+ i = block.end;
178
+ }
179
+ return arms;
180
+ }
181
+
182
+ /** `pub struct NAME { ... }` declarations with their field types. */
183
+ export function parseStructs(src) {
184
+ const structs = new Map();
185
+ const re = /pub struct\s+([A-Za-z0-9_]+)\s*\{/g;
186
+ let m;
187
+ while ((m = re.exec(src))) {
188
+ const body = blockAt(src, src.indexOf('{', m.index)).inner;
189
+ const fields = [];
190
+ for (const raw of splitTopLevel(body)) {
191
+ const line = raw.trim();
192
+ if (!line) continue;
193
+ const field = line.match(/^pub\s+([a-z_][a-z0-9_]*)\s*:\s*(\[[A-Za-z0-9_]+;\s*[A-Za-z0-9_]+\]|[A-Za-z0-9_<>:]+)$/);
194
+ if (!field) throw Error(`struct ${m[1]}: unreadable field declaration "${line}"`);
195
+ fields.push({ name: field[1], type: field[2] });
196
+ }
197
+ structs.set(m[1], fields);
198
+ }
199
+ return structs;
200
+ }
201
+
202
+ /** The variant list of `pub enum AmmInstruction`, in declaration order. */
203
+ export function parseAmmVariants(src) {
204
+ const at = src.indexOf('pub enum AmmInstruction');
205
+ if (at < 0) throw Error('AmmInstruction enum not found');
206
+ const body = blockAt(src, src.indexOf('{', at)).inner;
207
+ const variants = [];
208
+ for (const part of splitTopLevel(stripAttributes(body))) {
209
+ const match = part.match(/^([A-Z][A-Za-z0-9_]*)\s*(?:\(\s*([A-Za-z0-9_]+)\s*(?:\{[^}]*\})?\s*\))?$/);
210
+ if (!match) throw Error(`AmmInstruction: unreadable variant "${part}"`);
211
+ variants.push({ name: match[1], struct: match[2] ?? null });
212
+ }
213
+ if (!variants.length) throw Error('AmmInstruction yielded no variants');
214
+ return variants;
215
+ }
216
+
217
+ /** The `match tag { ... }` arms of `unpack()`. */
218
+ export function parseUnpackArms(unpackBody) {
219
+ const at = unpackBody.indexOf('match tag {');
220
+ if (at < 0) throw Error('unpack(): no match on the tag');
221
+ const block = blockAt(unpackBody, unpackBody.indexOf('{', at));
222
+ const arms = parseArmsAtTopLevel(block.inner);
223
+ if (!arms.length) throw Error('unpack(): no arms');
224
+ return arms;
225
+ }
226
+
227
+ /** The `pack()` arm for one variant: its destructure and its body. */
228
+ export function parsePackArm(packBody, variant) {
229
+ // Tuple variants destructure as `Self::Name(Type { .. })`; unit variants
230
+ // write `Self::Name => { ... }` with no parens at all.
231
+ let at = packBody.indexOf(`Self::${variant}(`);
232
+ let close = -1;
233
+ if (at >= 0) {
234
+ close = packBody.indexOf(')', at);
235
+ if (close < 0) throw Error(`pack(): arm for ${variant} never closes its destructure`);
236
+ } else {
237
+ at = packBody.search(new RegExp(`Self::${variant}\\s*=>`));
238
+ if (at < 0) throw Error(`pack(): no arm for variant ${variant}`);
239
+ close = at + `Self::${variant}`.length - 1;
240
+ }
241
+ const arrow = packBody.indexOf('=>', close);
242
+ if (arrow < 0) throw Error(`pack(): arm for ${variant} has no =>`);
243
+ const braceAt = packBody.indexOf('{', arrow);
244
+ const block = blockAt(packBody, braceAt);
245
+ return { destructure: packBody.slice(at, close + 1), body: block.inner };
246
+ }
247
+
248
+ /**
249
+ * The byte writes of one `pack()` arm, as a structured recipe.
250
+ *
251
+ * Three node kinds, matching the three shapes upstream actually uses:
252
+ * write one byte write (`buf.push` / `buf.extend_from_slice`)
253
+ * optional a write group guarded by `if X.is_some()`
254
+ * branch a `match` over a discriminator byte
255
+ */
256
+ export function parsePackRecipe(body) {
257
+ const nodes = [];
258
+ const keywordRe = /buf\.push\(|buf\.extend_from_slice\(|\bif\b|\bmatch\b/g;
259
+ let m;
260
+ while ((m = keywordRe.exec(body))) {
261
+ if (braceDepth(body, m.index) !== 0) continue;
262
+ const keyword = m[0];
263
+ if (keyword === 'if') {
264
+ const braceAt = body.indexOf('{', m.index);
265
+ const condition = body.slice(m.index + 2, braceAt).trim();
266
+ const block = blockAt(body, braceAt);
267
+ nodes.push({ kind: 'optional', condition, body: block.inner });
268
+ keywordRe.lastIndex = block.end;
269
+ continue;
270
+ }
271
+ if (keyword === 'match') {
272
+ const braceAt = body.indexOf('{', m.index);
273
+ const subject = body.slice(m.index + 5, braceAt).trim();
274
+ const block = blockAt(body, braceAt);
275
+ nodes.push({ kind: 'branch', subject, body: block.inner });
276
+ keywordRe.lastIndex = block.end;
277
+ continue;
278
+ }
279
+ const call = readCall(body, m.index);
280
+ nodes.push({ kind: 'write', expr: call.text });
281
+ keywordRe.lastIndex = call.end;
282
+ }
283
+ return nodes;
284
+ }
285
+
286
+ /** One byte write of a `pack()` arm, as a pinned op. */
287
+ export function parseWrite(expr) {
288
+ // The tag itself is a literal push (`buf.push(14)`), not a field write.
289
+ const literal = expr.match(/^buf\.push\(\s*(\d+)\s*\)$/);
290
+ if (literal) return { name: literal[1], kind: 'literal', value: Number(literal[1]) };
291
+ const push = expr.match(/^buf\.push\(\s*\*?([a-z_][a-z0-9_]*)\s*\)$/);
292
+ if (push) return { name: push[1], kind: 'u8' };
293
+ const int = expr.match(/^buf\.extend_from_slice\(\s*&([a-z_][a-z0-9_]*)(?:\.unwrap\(\))?\.to_le_bytes\(\)\s*\)$/);
294
+ if (int) return { name: int[1], kind: 'le_int' };
295
+ const key = expr.match(/^buf\.extend_from_slice\(\s*&([a-z_][a-z0-9_]*)\.to_bytes\(\)\s*\)$/);
296
+ if (key) return { name: key[1], kind: 'pubkey' };
297
+ const raw = expr.match(/^buf\.extend_from_slice\(\s*&([a-z_][a-z0-9_]*)\s*\)$/);
298
+ if (raw) return { name: raw[1], kind: 'raw' };
299
+ throw Error(`pack(): byte write not covered by a pinned rule: ${expr}`);
300
+ }
301
+
302
+ /** The reads of an `unpack()` arm body, as pinned ops. */
303
+ export function parseReads(body) {
304
+ const reads = [];
305
+ const intRe = /let\s+\(\s*([a-z_][a-z0-9_]*)\s*,\s*[a-z_][a-z0-9_]*\s*\)\s*=\s*Self::unpack_u(\d+)\(/g;
306
+ for (const m of body.matchAll(intRe)) {
307
+ reads.push({ name: m[1], kind: 'le_int', width: Number(m[2]) });
308
+ }
309
+ const keyRe = /let\s+([a-z_][a-z0-9_]*)\s*=\s*array_ref!\[\s*[a-z_][a-z0-9_]*\s*,\s*\d+\s*,\s*(\d+)\s*\]/g;
310
+ for (const m of body.matchAll(keyRe)) {
311
+ reads.push({ name: m[1], kind: 'pubkey', width: Number(m[2]) });
312
+ }
313
+ const feesRe = /let\s+([a-z_][a-z0-9_]*)\s*=\s*Fees::unpack_from_slice\(/g;
314
+ for (const m of body.matchAll(feesRe)) {
315
+ reads.push({ name: m[1], kind: 'fees' });
316
+ }
317
+ return reads;
318
+ }
319
+
320
+ /** The `if rest.len() >= N { ... }` guards of an `unpack()` arm. */
321
+ export function parseLengthGuards(body) {
322
+ const guards = [];
323
+ const re = /if\s+rest\.len\(\)\s*>=\s*([A-Za-z0-9_:]+)\s*\{/g;
324
+ let m;
325
+ while ((m = re.exec(body))) {
326
+ const braceAt = body.indexOf('{', m.index);
327
+ const block = blockAt(body, braceAt);
328
+ guards.push({ threshold: m[1], body: block.inner, start: m.index, end: block.end });
329
+ }
330
+ return guards;
331
+ }
332
+
333
+ /** The same body with every length-guard block removed (for the flat reads). */
334
+ function withoutGuards(body, guards) {
335
+ let out = body;
336
+ for (const guard of [...guards].sort((a, b) => b.start - a.start)) {
337
+ out = out.slice(0, guard.start) + out.slice(guard.end);
338
+ }
339
+ return out;
340
+ }
341
+
342
+ /** The `match <subject> { ... }` of a body, with its arms. */
343
+ export function parseMatchArms(body) {
344
+ const at = body.search(/\bmatch\s/);
345
+ if (at < 0) return null;
346
+ const braceAt = body.indexOf('{', at);
347
+ const subject = body.slice(at + 5, braceAt).trim();
348
+ const block = blockAt(body, braceAt);
349
+ return { subject, arms: parseArmsAtTopLevel(block.inner) };
350
+ }
351
+
352
+ /** The numeric value of every `AmmParams` variant, from the upstream state module. */
353
+ export function parseAmmParams(stateSrc) {
354
+ const at = stateSrc.indexOf('pub enum AmmParams');
355
+ if (at < 0) throw Error('AmmParams enum not found');
356
+ const body = blockAt(stateSrc, stateSrc.indexOf('{', at)).inner;
357
+ const pairs = [...body.matchAll(/([A-Za-z0-9_]+)\s*=\s*(\d+)u64/g)].map((m) => [m[1], Number(m[2])]);
358
+ if (!pairs.length) throw Error('AmmParams yielded no variants');
359
+ return new Map(pairs);
360
+ }
361
+
362
+ /** The `Fees` packed struct: field names, types and byte length. */
363
+ export function parseFees(stateSrc) {
364
+ const structs = parseStructs(stateSrc);
365
+ const fields = structs.get('Fees');
366
+ if (!fields) throw Error('Fees struct not found in the upstream state module');
367
+ const lenRe = /const LEN:\s*usize\s*=\s*(\d+)/.exec(stateSrc);
368
+ if (!lenRe) throw Error('Fees::LEN not found');
369
+ const len = Number(lenRe[1]);
370
+ const total = fields.reduce((acc, field) => acc + (field.type === 'u64' ? 8 : 0), 0);
371
+ if (total !== len) throw Error(`Fees::LEN is ${len} but its fields sum to ${total}`);
372
+ return { fields, len };
373
+ }
374
+
375
+ const INT_WIDTH = { u8: 1, u16: 2, u32: 4, u64: 8 };
376
+
377
+ /** Derive one program's interface document from its vendored upstream source. */
378
+ export function deriveRaydiumInterface(spec, { root = REPO_ROOT } = {}) {
379
+ const base = path.join(root, 'vendor', spec.vendor.dir, spec.vendor.pin);
380
+ const instructionPath = path.join(base, spec.vendor.instructionFile);
381
+ const statePath = path.join(base, spec.vendor.stateFile);
382
+ const instructionRaw = readFileSync(instructionPath);
383
+ const stateRaw = readFileSync(statePath);
384
+ const instructionSha256 = `sha256:${createHash('sha256').update(instructionRaw).digest('hex')}`;
385
+ const stateSha256 = `sha256:${createHash('sha256').update(stateRaw).digest('hex')}`;
386
+ const src = stripAttributes(stripComments(instructionRaw.toString('utf8')));
387
+ const stateSrc = stripAttributes(stripComments(stateRaw.toString('utf8')));
388
+
389
+ const structs = parseStructs(src);
390
+ const variants = parseAmmVariants(src);
391
+ const unpackBody = fnBody(src, 'pub fn unpack(input');
392
+ const packBody = fnBody(src, 'pub fn pack(&self)');
393
+ const ammParams = parseAmmParams(stateSrc);
394
+ const fees = parseFees(stateSrc);
395
+
396
+ // 1. What the wire BUILDER writes: variant -> tag, from the pack() arms.
397
+ const packTags = new Map();
398
+ for (const variant of variants) {
399
+ let arm;
400
+ try {
401
+ arm = parsePackArm(packBody, variant.name);
402
+ } catch {
403
+ continue; // upstream has no pack() arm for this variant
404
+ }
405
+ const recipe = parsePackRecipe(arm.body);
406
+ if (!recipe.length || recipe[0].kind !== 'write') throw Error(`${variant.name}: pack() arm does not start with a write`);
407
+ const tagWrite = parseWrite(recipe[0].expr);
408
+ if (tagWrite.kind !== 'literal') throw Error(`${variant.name}: pack() arm does not open with the literal tag push`);
409
+ packTags.set(variant.name, tagWrite.value);
410
+ }
411
+
412
+ // 2. What the wire READER accepts: tag -> variant, from the unpack() arms.
413
+ const unpackArms = parseUnpackArms(unpackBody);
414
+ const unsupported = new Set();
415
+ const unpackTags = new Map();
416
+ for (const arm of unpackArms) {
417
+ if (arm.header === '_') continue;
418
+ const tags = arm.header.split('|').map((part) => Number(part.trim()));
419
+ for (const tag of tags) {
420
+ if (!Number.isInteger(tag)) throw Error(`unpack(): arm ${arm.header} is not a numeric tag`);
421
+ }
422
+ if (arm.body.includes('unimplemented!')) {
423
+ for (const tag of tags) unsupported.add(tag);
424
+ continue;
425
+ }
426
+ const variantMatch = arm.body.match(/Self::([A-Z][A-Za-z0-9_]*)/);
427
+ if (!variantMatch) throw Error(`unpack(): tag ${arm.header} names no variant`);
428
+ for (const tag of tags) {
429
+ if (unpackTags.has(tag)) throw Error(`unpack(): tag ${tag} is bound twice`);
430
+ unpackTags.set(tag, { variant: variantMatch[1], body: arm.body });
431
+ }
432
+ }
433
+
434
+ // 3. Cross-check: pack() and unpack() must agree on every reachable tag.
435
+ for (const [variant, tag] of packTags) {
436
+ const bound = unpackTags.get(tag);
437
+ if (!bound) throw Error(`${variant}: pack() writes tag ${tag} but unpack() binds no such tag`);
438
+ if (bound.variant !== variant) throw Error(`tag ${tag}: pack() writes ${variant}, unpack() binds ${bound.variant}`);
439
+ }
440
+ for (const [tag, bound] of unpackTags) {
441
+ if (!packTags.has(bound.variant)) throw Error(`${bound.variant}: unpack() binds tag ${tag} but pack() never writes it`);
442
+ }
443
+
444
+ // 4. The variants upstream marks unsupported: their tag is upstream's own
445
+ // declaration index, which upstream spells out in the same arm
446
+ // (`0 | 2 | 5 | 8 | 10 | 12 | 13 => unimplemented!()`).
447
+ //
448
+ // Why this is derived rather than assumed: those seven tags have no
449
+ // pack() arm to read a number from, so position is the only source. The
450
+ // rule is therefore CHECKED on every variant that DOES have one — pack()
451
+ // writes the literal integer, unpack() binds that same integer, and both
452
+ // equal the variant's declaration index (measured on all twelve reachable
453
+ // variants 2026-11-03). If upstream ever reorders the enum so that a
454
+ // reachable tag stops matching its position, this throws instead of
455
+ // handing a wrong name to a refused tag.
456
+ //
457
+ // The refusal itself does not depend on the naming: upstream panics on
458
+ // ALL of tags 0, 2, 5, 8, 10, 12, 13 alike (unimplemented!()), so a
459
+ // decoder that refuses those tags agrees with the chain whichever
460
+ // variant name sits behind them.
461
+ const declared = variants.map((variant) => variant.name);
462
+ for (const variant of variants) {
463
+ const index = declared.indexOf(variant.name);
464
+ if (packTags.has(variant.name) && packTags.get(variant.name) !== index) {
465
+ throw Error(
466
+ `${variant.name}: pack() writes tag ${packTags.get(variant.name)} but it is declared at `
467
+ + `index ${index} — upstream no longer declares this enum in wire order, so a refused `
468
+ + `variant's tag could not be derived from its declaration index`,
469
+ );
470
+ }
471
+ }
472
+ const instructions = [];
473
+ for (const variant of variants) {
474
+ const index = declared.indexOf(variant.name);
475
+ if (packTags.has(variant.name)) {
476
+ const tag = packTags.get(variant.name);
477
+ const bound = unpackTags.get(tag);
478
+ instructions.push(buildInstruction({
479
+ variant, tag, unpackBody: bound.body, packBody, structs, ammParams, fees,
480
+ }));
481
+ continue;
482
+ }
483
+ if (!unsupported.has(index)) {
484
+ throw Error(`${variant.name}: upstream neither writes nor marks this variant unsupported`);
485
+ }
486
+ instructions.push({
487
+ tag: index,
488
+ name: variant.name,
489
+ wire: 'rejected',
490
+ reason: 'upstream marks this variant unsupported (unimplemented!() in unpack(); InvalidInstructionData in pack())',
491
+ args: [],
492
+ });
493
+ }
494
+ // Every tag upstream lists as unsupported must be a declared variant.
495
+ for (const tag of unsupported) {
496
+ if (!instructions.some((entry) => entry.tag === tag && entry.wire === 'rejected')) {
497
+ throw Error(`upstream lists tag ${tag} as unsupported, but no declared variant carries it`);
498
+ }
499
+ }
500
+ instructions.sort((a, b) => a.tag - b.tag);
501
+
502
+ return {
503
+ program: spec.program,
504
+ label: spec.label,
505
+ scheme: 'legacy-rust',
506
+ source: {
507
+ repo: spec.vendor.repo,
508
+ pin: spec.vendor.pin,
509
+ files: [
510
+ { path: spec.vendor.instructionFile, sha256: instructionSha256 },
511
+ { path: spec.vendor.stateFile, sha256: stateSha256 },
512
+ ],
513
+ vendoredAt: `vendor/${spec.vendor.dir}/${spec.vendor.pin}`,
514
+ derivation: "src/raydium-interface.mjs: tag = the literal integer upstream's pack() arm pushes, cross-checked against the unpack() arm that binds it; args = the pack() arm's byte-write sequence, cross-checked against unpack()'s reads by count and width; optional groups carry upstream's own rest.len() >= N threshold; branches carry upstream's own discriminator values (AmmParams enum text or numeric literal)",
515
+ },
516
+ runtime: spec.runtime,
517
+ selector: { width: 1, encoding: 'u8 enum tag (explicit, sparse — upstream declares 19 variants, 12 are reachable)' },
518
+ argEncoding: "u8/u16/u32/u64 LE; pubkey 32 raw bytes; optional groups are PRESENT only when upstream's rest.len() >= N threshold holds (no C-style option tag); amm_fees = upstream's packed Fees struct (8 x u64 LE)",
519
+ ammFees: { len: fees.len, fields: fees.fields.map((field) => ({ ...field })) },
520
+ instructions,
521
+ };
522
+ }
523
+
524
+ /** The byte width of one pinned arg type. */
525
+ function widthOf(type, fees) {
526
+ if (type === 'pubkey') return 32;
527
+ if (type === 'amm_fees') return fees.len;
528
+ if (type in INT_WIDTH) return INT_WIDTH[type];
529
+ throw Error(`unpinned type ${type}`);
530
+ }
531
+
532
+ /** Build one instruction entry from the upstream pack()/unpack() pair. */
533
+ function buildInstruction({ variant, tag, unpackBody, packBody, structs, ammParams, fees }) {
534
+ const { body: packArmBody, destructure } = parsePackArm(packBody, variant.name);
535
+ const structMatch = destructure.match(/Self::[A-Za-z0-9_]+\(\s*([A-Za-z0-9_]+)/);
536
+ const structName = structMatch ? structMatch[1] : null;
537
+
538
+ const recipe = parsePackRecipe(packArmBody);
539
+ const first = recipe[0];
540
+ const tagWrite = parseWrite(first.expr);
541
+ if (tagWrite.kind !== 'literal' || tagWrite.value !== tag) {
542
+ throw Error(`${variant.name}: pack() arm does not open with tag ${tag}`);
543
+ }
544
+
545
+ const fields = (name) => {
546
+ if (!structName) throw Error(`${variant.name}: no struct to read the field type from`);
547
+ const type = fieldType(structs, structName, name);
548
+ if (type === 'Pubkey') return 'pubkey';
549
+ if (type in INT_WIDTH) return type;
550
+ if (type === 'Fees') return 'amm_fees';
551
+ throw Error(`${structName}.${name} has unpinned type ${type}`);
552
+ };
553
+
554
+ const guards = parseLengthGuards(unpackBody);
555
+ const guardFor = (count) => {
556
+ for (const guard of guards) {
557
+ const reads = parseReads(guard.body);
558
+ if (reads.length !== count) continue;
559
+ if (!/^\d+$/.test(guard.threshold)) throw Error(`${variant.name}: unpinned length threshold ${guard.threshold}`);
560
+ return { threshold: Number(guard.threshold), reads };
561
+ }
562
+ throw Error(`${variant.name}: an optional group of ${count} field(s) has no matching rest.len() >= N guard upstream`);
563
+ };
564
+
565
+ const base = [];
566
+ let optional = null;
567
+ const branches = [];
568
+ for (const node of recipe.slice(1)) {
569
+ if (node.kind === 'write') {
570
+ const write = parseWrite(node.expr);
571
+ base.push({ name: write.name, type: pinnedType(write, fields, variant) });
572
+ continue;
573
+ }
574
+ if (node.kind === 'optional') {
575
+ const writes = parsePackRecipe(node.body).filter((child) => child.kind === 'write').map((child) => parseWrite(child.expr));
576
+ const guard = guardFor(writes.length);
577
+ const args = writes.map((write) => ({ name: write.name, type: pinnedType(write, fields, variant) }));
578
+ // The guard's reads and the optional group's writes must agree in width.
579
+ for (let index = 0; index < args.length; index++) {
580
+ const readWidth = readWidthOf(guard.reads[index]);
581
+ const writeWidth = widthOf(args[index].type, fees);
582
+ if (readWidth !== writeWidth) {
583
+ throw Error(`${variant.name}: optional field ${args[index].name} is ${writeWidth} byte(s) wide, upstream reads ${readWidth}`);
584
+ }
585
+ }
586
+ optional = { presentWhenRemainingAtLeast: guard.threshold, args };
587
+ continue;
588
+ }
589
+ // A branch over a discriminator byte: the unpack() side names the values.
590
+ const match = parseMatchArms(unpackBody);
591
+ if (!match) throw Error(`${variant.name}: pack() branches but unpack() does not`);
592
+ const packArms = parseArmsAtTopLevel(node.body);
593
+ if (!packArms.length) throw Error(`${variant.name}: pack() branch has no match arms`);
594
+ const values = branchValues(match, ammParams);
595
+ if (packArms.length !== values.length) {
596
+ throw Error(`${variant.name}: pack() has ${packArms.length} branch arms, unpack() names ${values.length}`);
597
+ }
598
+ for (let index = 0; index < values.length; index++) {
599
+ const writes = parsePackRecipe(packArms[index].body)
600
+ .filter((child) => child.kind === 'write')
601
+ .map((child) => parseWrite(child.expr));
602
+ const reads = parseReads(match.arms[index].body);
603
+ if (writes.length !== reads.length) {
604
+ throw Error(`${variant.name}: branch ${match.arms[index].header} writes ${writes.length} field(s), unpack() reads ${reads.length}`);
605
+ }
606
+ for (let slot = 0; slot < writes.length; slot++) {
607
+ const writeWidth = widthOf(pinnedType(writes[slot], fields, variant), fees);
608
+ const readWidth = readWidthOf(reads[slot]);
609
+ if (writeWidth !== readWidth) {
610
+ throw Error(`${variant.name}: branch field ${writes[slot].name} is ${writeWidth} byte(s) wide, upstream reads ${readWidth}`);
611
+ }
612
+ }
613
+ if (values[index] === null) {
614
+ // A wildcard branch: upstream refuses these values (it returns an
615
+ // error instead of writing bytes). Named, never decoded.
616
+ branches.push({
617
+ on: 'param',
618
+ values: null,
619
+ wire: 'rejected',
620
+ reason: 'upstream refuses this discriminator value',
621
+ args: [],
622
+ });
623
+ continue;
624
+ }
625
+ branches.push({
626
+ on: 'param',
627
+ values: values[index],
628
+ args: writes.map((write) => ({ name: write.name, type: pinnedType(write, fields, variant) })),
629
+ });
630
+ }
631
+ }
632
+
633
+ // The unpack() reads OUTSIDE the length guards and OUTSIDE the branches
634
+ // must be covered by the pack() base writes: no read may be unexplained.
635
+ const unpackMatch = parseMatchArms(unpackBody);
636
+ const sequential = parseReads(withoutGuards(unpackBody, guards));
637
+ const branchNames = new Set();
638
+ if (unpackMatch) {
639
+ for (const arm of unpackMatch.arms) for (const read of parseReads(arm.body)) branchNames.add(read.name);
640
+ }
641
+ const baseReads = sequential.filter((read) => !branchNames.has(read.name));
642
+ const known = base.length;
643
+ if (baseReads.length !== known) {
644
+ throw Error(`${variant.name}: unpack() reads ${baseReads.length} field(s) outside the optional/branched groups, pack() writes ${known}`);
645
+ }
646
+ for (let index = 0; index < baseReads.length; index++) {
647
+ const readWidth = readWidthOf(baseReads[index]);
648
+ const writeWidth = widthOf(base[index].type, fees);
649
+ if (readWidth !== writeWidth) {
650
+ throw Error(`${variant.name}: field ${base[index].name} is ${writeWidth} byte(s) wide, upstream reads ${readWidth}`);
651
+ }
652
+ }
653
+
654
+ return {
655
+ tag,
656
+ name: variant.name,
657
+ wire: 'accepted',
658
+ args: base,
659
+ ...(optional ? { optional } : {}),
660
+ ...(branches.length ? { branches } : {}),
661
+ };
662
+ }
663
+
664
+ /** The byte width of one unpack() read. */
665
+ function readWidthOf(read) {
666
+ if (read.kind === 'le_int') return read.width / 8;
667
+ if (read.kind === 'pubkey') return read.width;
668
+ if (read.kind === 'fees') return 64;
669
+ throw Error(`unpinned read kind ${read.kind}`);
670
+ }
671
+
672
+ /** The pinned type of one write, from the upstream struct declaration. */
673
+ function pinnedType(write, fields, variant) {
674
+ if (write.kind === 'u8') return fields(write.name);
675
+ if (write.kind === 'le_int') {
676
+ const type = fields(write.name);
677
+ if (!(type in INT_WIDTH)) throw Error(`${variant.name}.${write.name}: unpinned integer type ${type}`);
678
+ return type;
679
+ }
680
+ if (write.kind === 'pubkey') return 'pubkey';
681
+ if (write.kind === 'raw') {
682
+ // The only raw write upstream performs is the packed Fees struct, whose
683
+ // bytes come from the upstream state module's own field order.
684
+ if (write.name !== 'fees_slice') throw Error(`${variant.name}: raw write ${write.name} is not covered by a pinned rule`);
685
+ return 'amm_fees';
686
+ }
687
+ throw Error(`${variant.name}: write kind ${write.kind} is not covered by a pinned rule`);
688
+ }
689
+
690
+ /** The declared width of a field, from the upstream struct declaration. */
691
+ function fieldType(structs, structName, fieldName) {
692
+ const fields = structs.get(structName);
693
+ if (!fields) throw Error(`struct ${structName} not found upstream`);
694
+ const field = fields.find((entry) => entry.name === fieldName);
695
+ if (!field) throw Error(`${structName}: field ${fieldName} is not declared upstream`);
696
+ return field.type.replace(/^Option<(.*)>$/, '$1');
697
+ }
698
+
699
+ /** The discriminator values of each `unpack()` branch, in arm order. */
700
+ function branchValues(match, ammParams) {
701
+ const claimed = [];
702
+ const out = [];
703
+ const usesAmmParams = match.arms.some((arm) => /AmmParams::/.test(arm.header));
704
+ for (const arm of match.arms) {
705
+ if (arm.header === '_') {
706
+ out.push(null);
707
+ continue;
708
+ }
709
+ const parts = arm.header.split('|').map((part) => part.trim());
710
+ const values = [];
711
+ for (const part of parts) {
712
+ const name = part.replace(/^AmmParams::/, '');
713
+ if (/^\d+$/.test(name)) values.push(Number(name));
714
+ else if (ammParams.has(name)) values.push(ammParams.get(name));
715
+ else throw Error(`branch pattern ${part} is not a number and not an upstream AmmParams variant`);
716
+ }
717
+ claimed.push(...values);
718
+ out.push(values.sort((a, b) => a - b));
719
+ }
720
+ if (!usesAmmParams) return out;
721
+ // With AmmParams as the subject, a wildcard arm takes every value no other
722
+ // arm claims — upstream's own enum text is the value set.
723
+ const all = [...ammParams.values()].sort((a, b) => a - b);
724
+ const wildcard = all.filter((value) => !claimed.includes(value));
725
+ return out.map((values) => (values === null ? wildcard : values));
726
+ }
727
+
728
+ /** The pinned Raydium AMM v4 spec. */
729
+ export const RAYDIUM_SPECS = [
730
+ {
731
+ program: RAYDIUM_AMM_V4,
732
+ label: 'Raydium AMM v4',
733
+ vendor: RAYDIUM_PINS.ammV4,
734
+ runtime: 'raydium-io/raydium-amm at this pin: unpack() binds an explicit sparse u8 tag and reads with unpack_u8/unpack_u64; an unknown tag is InvalidInstructionData; the variants upstream marks unsupported panic (unimplemented!())',
735
+ },
736
+ ];
737
+
738
+ /** Derive every pinned Raydium interface. */
739
+ export function deriveAllRaydiumInterfaces(options) {
740
+ return RAYDIUM_SPECS.map((spec) => deriveRaydiumInterface(spec, options));
741
+ }
742
+
743
+ /** sha256 of the vendored upstream instruction file, as `sha256:<hex>`. */
744
+ export function vendoredRaydiumHash(spec, { root = REPO_ROOT } = {}) {
745
+ const raw = readFileSync(path.join(root, 'vendor', spec.vendor.dir, spec.vendor.pin, spec.vendor.instructionFile));
746
+ return `sha256:${createHash('sha256').update(raw).digest('hex')}`;
747
+ }