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,394 @@
1
+ // Derive the SPL Token wire table from the VENDORED UPSTREAM RUST.
2
+ //
3
+ // Same contract as src/native-interface.mjs, one level up in complexity: the
4
+ // token family has NO IDL account to pull (measured 2026-11-02 at both
5
+ // pullable addresses), yet it carries 43% of real watched mainnet traffic.
6
+ // The upstream Rust interface crates ARE the interface, and their wire rule
7
+ // lives in hand-written pack()/unpack() pairs rather than in a Borsh enum — so
8
+ // this module derives each variant's wire shape from the upstream TEXT and
9
+ // throws on anything it cannot pin.
10
+ //
11
+ // Why derive instead of transcribe: the System/ComputeBudget table was once
12
+ // hand-typed, and the transcription carried a defect (a u32 string header
13
+ // where the chain carries u64) that survived review and named 0 of 63 live
14
+ // payloads. The fix that held was removing the transcription step. The same
15
+ // applies here at higher risk: nearly fifty variants, and one wrong field
16
+ // order silently mislabels money movement.
17
+ //
18
+ // The rules, each one upstream's own text:
19
+ // - the wire tag is the integer upstream WRITES in pack() and MATCHES in
20
+ // unpack() — NOT the declaration index: TokenInstruction uses explicit
21
+ // tags, so WithdrawExcessLamports is 38, UnwrapLamports 45, Batch 255,
22
+ // - the arg sequence is the sequence of byte writes in the pack arm,
23
+ // - `u8`/`u16`/`u64` are fixed-width LE, `pubkey` is 32 raw bytes,
24
+ // `coption_pubkey`/`coption_u64` are the C-style option upstream's own
25
+ // pack_pubkey_option/pack_u64_option writes (1 tag byte + payload, NOT
26
+ // a bincode option wrapper),
27
+ // - `authority_type` is the upstream AuthorityType enum byte, whose variant
28
+ // list is derived from the same vendored file,
29
+ // - `utf8_rest` is the whole remaining payload as utf8 (upstream's
30
+ // UiAmountToAmount writes the string with NO length prefix),
31
+ // - `u16_vec` is a run of 2-byte extension-type ids,
32
+ // - `bytes` is the raw remainder (Batch),
33
+ // - a variant must appear in BOTH the pack and the unpack arm sets before
34
+ // it can enter the table, and the set of variants upstream DECLARES must
35
+ // equal the set this table names — an upstream addition that nobody
36
+ // re-reviewed fails loudly instead of decoding as "unknown".
37
+ //
38
+ // Dependency-free on purpose: it is imported by the decode path.
39
+
40
+ import { readFileSync } from 'node:fs';
41
+ import { createHash } from 'node:crypto';
42
+ import path from 'node:path';
43
+ import { fileURLToPath } from 'node:url';
44
+
45
+ const HERE = path.dirname(fileURLToPath(import.meta.url));
46
+ export const REPO_ROOT = path.resolve(HERE, '..');
47
+
48
+ export const TOKEN_PINS = {
49
+ splToken: {
50
+ repo: 'solana-program/token',
51
+ dir: 'spl-token',
52
+ pin: '737788ff5bcc3f1f44cfb222f25483187650be76',
53
+ file: 'interface/src/instruction.rs',
54
+ },
55
+ splToken2022: {
56
+ repo: 'solana-program/token-2022',
57
+ dir: 'spl-token-2022',
58
+ pin: '5251dcc07ecbcb721d6c4f0231090defadf00311',
59
+ file: 'interface/src/instruction.rs',
60
+ },
61
+ };
62
+
63
+ export const SPL_TOKEN_PROGRAM = 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA';
64
+ export const SPL_TOKEN_2022_PROGRAM = 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb';
65
+
66
+ const stripComments = (src) => src.replace(/\/\*[\s\S]*?\*\//g, '').replace(/\/\/[^\n]*/g, '');
67
+
68
+ /** The body of `fn ...` from comment-stripped source. */
69
+ function fnBody(src, signature) {
70
+ const at = src.indexOf(signature);
71
+ if (at < 0) throw Error(`function not found: ${signature}`);
72
+ const open = src.indexOf('{', at);
73
+ let depth = 0;
74
+ for (let i = open; i < src.length; i++) {
75
+ if (src[i] === '{') depth++;
76
+ else if (src[i] === '}') {
77
+ depth--;
78
+ if (depth === 0) return src.slice(open + 1, i);
79
+ }
80
+ }
81
+ throw Error(`${signature}: body never closes`);
82
+ }
83
+
84
+ /**
85
+ * The variant declarations of `pub enum TokenInstruction`: name -> declared
86
+ * field names, in declaration order.
87
+ *
88
+ * Both field forms appear upstream and BOTH must be read: `decimals: u8`
89
+ * (annotated) and `owner` (shorthand, which is what InitializeAccount2 uses).
90
+ * A parser that only understood the annotated form would silently drop the
91
+ * owner pubkey from three of the most frequent instructions.
92
+ */
93
+ export function parseTokenVariants(src) {
94
+ // Upstream annotates many fields with multi-line `#[cfg_attr(...)]`
95
+ // attributes; they are not field declarations and must go before the split.
96
+ src = src.replace(/#\[[\s\S]*?\]\s*/g, '');
97
+ const at = src.indexOf('pub enum TokenInstruction');
98
+ if (at < 0) throw Error('TokenInstruction enum not found');
99
+ const open = src.indexOf('{', at);
100
+ let depth = 0, end = -1;
101
+ for (let i = open; i < src.length; i++) {
102
+ if (src[i] === '{') depth++;
103
+ else if (src[i] === '}') {
104
+ depth--;
105
+ if (depth === 0) { end = i; break; }
106
+ }
107
+ }
108
+ const body = src.slice(open + 1, end);
109
+ const variants = new Map();
110
+ let i = 0;
111
+ const IDENT = /[A-Za-z0-9_]/;
112
+ const skip = (chars) => { while (i < body.length && chars.includes(body[i])) i++; };
113
+ while (i < body.length) {
114
+ skip(',\n\r\t ');
115
+ if (i >= body.length) break;
116
+ const start = i;
117
+ while (i < body.length && IDENT.test(body[i])) i++;
118
+ const name = body.slice(start, i);
119
+ if (!/^[A-Z]/.test(name)) throw Error(`unexpected token in TokenInstruction body: ${name || body[i]}`);
120
+ skip(' \n\r\t');
121
+ const fields = [];
122
+ if (body[i] === '{') {
123
+ const open2 = i;
124
+ let d = 0;
125
+ for (; i < body.length; i++) {
126
+ if (body[i] === '{') d++;
127
+ else if (body[i] === '}') { d--; if (d === 0) { i++; break; } }
128
+ }
129
+ const inner = body.slice(open2 + 1, i - 1);
130
+ for (const part of inner.split(',')) {
131
+ const trimmed = part.trim();
132
+ if (!trimmed) continue;
133
+ const annotated = trimmed.match(/^([a-z_][a-z0-9_]*)\s*:\s*(?:&'?a?\s*)?(?:\[u8\]|str|[A-Za-z0-9_<>:]+)$/);
134
+ if (annotated) { fields.push(annotated[1]); continue; }
135
+ const shorthand = trimmed.match(/^([a-z_][a-z0-9_]*)$/);
136
+ if (shorthand) { fields.push(shorthand[1]); continue; }
137
+ throw Error(`TokenInstruction::${name}: unreadable field declaration "${trimmed}"`);
138
+ }
139
+ }
140
+ // `UnwrapLamports { .. } = 45` puts the tag after the field block, so
141
+ // whitespace must be skipped again before looking for '='.
142
+ skip(' \n\r\t');
143
+ let explicitTag = null;
144
+ if (body[i] === '=') {
145
+ // `WithdrawExcessLamports = 38` / `UnwrapLamports { .. } = 45` /
146
+ // `Batch = 255` — an explicit tag, consumed with its variant.
147
+ const m = body.slice(i + 1).match(/^\s*(\d+)/);
148
+ if (!m) throw Error(`TokenInstruction::${name}: '=' is not followed by a numeric tag`);
149
+ explicitTag = Number(m[1]);
150
+ i += 1 + m[0].length;
151
+ }
152
+ variants.set(name, { explicitTag, fields });
153
+ }
154
+ if (!variants.size) throw Error('TokenInstruction yielded no variants');
155
+ return variants;
156
+ }
157
+
158
+ /**
159
+ * The wire writes of ONE pack arm, in order, as pinned op records.
160
+ *
161
+ * Every op is matched EXACTLY once; an unrecognized write throws instead of
162
+ * being skipped. That is the whole point of this module: a silent skip here
163
+ * would produce a table that decodes the wrong number of bytes and labels
164
+ * money movement with the wrong amount.
165
+ */
166
+ function packArmOps(packBody, tag) {
167
+ const at = packBody.indexOf(`buf.push(${tag})`);
168
+ if (at < 0) throw Error(`pack arm for tag ${tag} not found`);
169
+ const arrowAt = packBody.lastIndexOf('=>', at);
170
+ if (arrowAt < 0) throw Error(`pack arm ${tag}: no match arm`);
171
+ const between = packBody.slice(arrowAt + 2, at);
172
+ if (!between.includes('{')) {
173
+ // A brace-less arm (`Self::Revoke => buf.push(5),`) carries no args.
174
+ return [];
175
+ }
176
+ const armOpen = arrowAt + between.indexOf('{');
177
+ let depth = 0, end = -1;
178
+ for (let i = armOpen; i < packBody.length; i++) {
179
+ if (packBody[i] === '{') depth++;
180
+ else if (packBody[i] === '}') {
181
+ depth--;
182
+ if (depth === 0) { end = i; break; }
183
+ }
184
+ }
185
+ if (end < 0) throw Error(`pack arm ${tag}: body never closes`);
186
+ const arm = packBody.slice(armOpen + 1, end);
187
+ const ops = [];
188
+ // The tag write itself is not an arg.
189
+ const rest = arm.slice(arm.indexOf(`buf.push(${tag})`) + `buf.push(${tag})`.length);
190
+ const patterns = [
191
+ // for extension_type in extension_types { buf.extend_from_slice(&<[u8; 2]>::from(*extension_type)); }
192
+ { re: /for\s+([a-z_]+)\s+in\s+([a-z_]+)\s*\{[^}]*buf\.extend_from_slice\(&<\[u8;\s*2\]>::from\(\*([a-z_]+)\)\);[^}]*\}/g,
193
+ op: (m) => ({ kind: 'u16_vec', name: m[2] }) },
194
+ // Self::pack_pubkey_option(x, &mut buf)
195
+ { re: /Self::pack_pubkey_option\(&?([a-z_]+)\s*,\s*&mut buf\)/g,
196
+ op: (m) => ({ kind: 'coption_pubkey', name: m[1] }) },
197
+ // Self::pack_u64_option(x, &mut buf)
198
+ { re: /Self::pack_u64_option\(&?([a-z_]+)\s*,\s*&mut buf\)/g,
199
+ op: (m) => ({ kind: 'coption_u64', name: m[1] }) },
200
+ // buf.extend_from_slice(&amount.to_le_bytes())
201
+ { re: /buf\.extend_from_slice\(&([a-z_]+)\.to_le_bytes\(\)\)/g,
202
+ op: (m) => ({ kind: 'le_int', name: m[1] }) },
203
+ // buf.extend_from_slice(mint_authority.as_ref())
204
+ { re: /buf\.extend_from_slice\(([a-z_]+)\.as_ref\(\)\)/g,
205
+ op: (m) => ({ kind: 'pubkey', name: m[1] }) },
206
+ // buf.extend_from_slice(ui_amount.as_bytes())
207
+ { re: /buf\.extend_from_slice\(([a-z_]+)\.as_bytes\(\)\)/g,
208
+ op: (m) => ({ kind: 'utf8_rest', name: m[1] }) },
209
+ // buf.extend_from_slice(data)
210
+ { re: /buf\.extend_from_slice\(([a-z_]+)\)/g,
211
+ op: (m) => ({ kind: 'bytes', name: m[1] }) },
212
+ // buf.push(authority_type.into())
213
+ { re: /buf\.push\(([a-z_]+)\.into\(\)\)/g,
214
+ op: (m) => ({ kind: 'authority_type', name: m[1] }) },
215
+ // buf.push(decimals) / buf.push(m)
216
+ { re: /buf\.push\(([a-z_]+)\)/g,
217
+ op: (m) => ({ kind: 'u8', name: m[1] }) },
218
+ ];
219
+ for (const { re, op } of patterns) {
220
+ for (const m of rest.matchAll(re)) ops.push({ at: m.index, ...op(m) });
221
+ }
222
+ ops.sort((a, b) => a.at - b.at);
223
+ // Every byte write in the arm must be accounted for by exactly one op.
224
+ const writes = [...rest.matchAll(/buf\.(?:push|extend_from_slice)\(|Self::pack_(?:pubkey|u64)_option\(/g)];
225
+ if (writes.length !== ops.length) {
226
+ throw Error(
227
+ `pack arm ${tag}: ${writes.length} byte write(s) but ${ops.length} pinned op(s) — ` +
228
+ 'an upstream write is not covered by a pinned rule; re-review the pin');
229
+ }
230
+ return ops;
231
+ }
232
+
233
+ /** The fixed width of a `le_int` write, from the upstream `.to_le_bytes()` type. */
234
+ function leIntWidth(src, name, variant) {
235
+ const re = new RegExp(`${variant}[^}]*?\\b${name}\\s*:\\s*([A-Za-z0-9_]+)`, 's');
236
+ const m = src.match(re);
237
+ if (!m) throw Error(`${variant}: field ${name} is not declared upstream`);
238
+ const type = m[1];
239
+ if (type === 'u8') return 1;
240
+ if (type === 'u16') return 2;
241
+ if (type === 'u64') return 8;
242
+ throw Error(`${variant}: field ${name} has unpinned integer type ${type}`);
243
+ }
244
+
245
+ /** Derive one token program's interface document from its vendored upstream source. */
246
+ export function deriveTokenInterface(spec, { root = REPO_ROOT } = {}) {
247
+ const file = path.join(root, 'vendor', spec.vendor.dir, spec.vendor.pin, spec.vendor.file);
248
+ const raw = readFileSync(file);
249
+ const fileSha256 = `sha256:${createHash('sha256').update(raw).digest('hex')}`;
250
+ const src = stripComments(raw.toString('utf8'));
251
+
252
+ const declared = parseTokenVariants(src);
253
+ const packBody = fnBody(src, 'pub fn pack(&self)');
254
+ const unpackBody = src.includes('pub(crate) fn unpack_with_rest')
255
+ ? fnBody(src, 'pub(crate) fn unpack_with_rest')
256
+ : fnBody(src, 'pub fn unpack(input');
257
+
258
+ const packTags = [...packBody.matchAll(/buf\.push\((\d+)\)/g)].map((m) => Number(m[1]));
259
+ const unpackTags = [...unpackBody.matchAll(/^\s*((?:\d+\s*\|\s*)*\d+)\s*=>/gm)]
260
+ .flatMap((m) => m[1].split('|').map((t) => Number(t.trim())));
261
+
262
+ const packSet = new Set(packTags);
263
+ const unpackSet = new Set(unpackTags);
264
+ for (const tag of spec.expectedTags) {
265
+ if (!packSet.has(tag)) throw Error(`${spec.label}: tag ${tag} is missing from upstream pack()`);
266
+ if (!unpackSet.has(tag)) throw Error(`${spec.label}: tag ${tag} is missing from upstream unpack()`);
267
+ }
268
+ const extraPack = [...packSet].filter((t) => !spec.expectedTags.includes(t));
269
+ const extraUnpack = [...unpackSet].filter((t) => !spec.expectedTags.includes(t));
270
+ if (extraPack.length) throw Error(`${spec.label}: upstream pack() has tag(s) ${extraPack} this table does not name`);
271
+ if (extraUnpack.length) throw Error(`${spec.label}: upstream unpack() has tag(s) ${extraUnpack} this table does not name`);
272
+
273
+ const instructions = spec.expectedTags.map((tag) => {
274
+ const meta = spec.variants[tag];
275
+ if (!meta) throw Error(`${spec.label}: tag ${tag} has no declared variant metadata`);
276
+ const variant = typeof meta === 'string' ? meta : meta.variant;
277
+ if (!declared.has(variant)) throw Error(`${spec.label}: variant ${variant} is not declared upstream`);
278
+ const fields = declared.get(variant).fields;
279
+ const ops = packArmOps(packBody, tag);
280
+ const args = [];
281
+ for (const op of ops) {
282
+ if (!fields.includes(op.name)) {
283
+ throw Error(`${spec.label} tag ${tag} (${variant}): wire write "${op.name}" is not a declared field of the variant`);
284
+ }
285
+ let type = op.kind;
286
+ if (op.kind === 'le_int') type = { u8: 'u8', u16: 'u16', u64: 'u64' }[leIntWidth(src, op.name, variant)] ?? 'u64';
287
+ args.push({ name: op.name, type });
288
+ }
289
+ const names = args.map((a) => a.name);
290
+ if (new Set(names).size !== names.length) throw Error(`${spec.label} tag ${tag}: duplicate arg names ${names}`);
291
+ return { tag, name: variant, wire: 'accepted', args };
292
+ });
293
+
294
+ // Every variant upstream declares must be in the table. An upstream
295
+ // addition nobody re-reviewed must fail here, not decode as "unknown".
296
+ const named = new Set(instructions.map((ix) => ix.name));
297
+ for (const variant of declared.keys()) {
298
+ if (!named.has(variant)) throw Error(`${spec.label}: upstream declares ${variant}, this table does not name it`);
299
+ }
300
+ for (const ix of instructions) {
301
+ if (!declared.has(ix.name)) throw Error(`${spec.label}: table names ${ix.name}, upstream does not declare it`);
302
+ }
303
+
304
+ return {
305
+ program: spec.program,
306
+ label: spec.label,
307
+ scheme: 'native-token',
308
+ source: {
309
+ repo: spec.vendor.repo,
310
+ pin: spec.vendor.pin,
311
+ file: spec.vendor.file,
312
+ enum: 'TokenInstruction (explicit tags, not declaration order)',
313
+ fileSha256,
314
+ vendoredAt: `vendor/${spec.vendor.dir}/${spec.vendor.pin}`,
315
+ derivation: "src/token-interface.mjs: tag = the integer upstream pack() writes and unpack() matches; args = the pack arm's byte-write sequence; option encoding = upstream pack_pubkey_option/pack_u64_option (1 tag byte + payload)",
316
+ },
317
+ runtime: spec.runtime,
318
+ selector: { width: 1, encoding: 'u8 enum tag (explicit, sparse)' },
319
+ argEncoding: 'u8/u16/u64 LE; pubkey 32 raw bytes; COption = 1 tag byte (0 none / 1 some) + payload; utf8_rest = whole remainder; u16_vec = run of 2-byte extension-type ids; bytes = raw remainder',
320
+ authorityType: deriveAuthorityTypes(spec, { root }),
321
+ instructions,
322
+ };
323
+ }
324
+
325
+ /** The AuthorityType enum byte rule, derived from the vendored source. */
326
+ export function deriveAuthorityTypes(spec, { root = REPO_ROOT } = {}) {
327
+ const file = path.join(root, 'vendor', spec.vendor.dir, spec.vendor.pin, spec.vendor.file);
328
+ const src = stripComments(readFileSync(file, 'utf8'));
329
+ const fromBody = fnBody(src, 'fn from(index: u8)');
330
+ const pairs = [...fromBody.matchAll(/(\d+)\s*=>\s*Ok\(AuthorityType::([A-Za-z0-9_]+)\)/g)]
331
+ .map((m) => [Number(m[1]), m[2]]);
332
+ if (pairs.length !== spec.authorityType.count) {
333
+ throw Error(`${spec.label}: AuthorityType derived ${pairs.length} variants, expected ${spec.authorityType.count}`);
334
+ }
335
+ return pairs.map(([byte, name]) => ({ byte, name }));
336
+ }
337
+
338
+ /** The two pinned token specs. The tag -> variant map is the opinion layer; the
339
+ * derivation checks it against upstream's own declaration list in both
340
+ * directions, so an upstream change fails loudly instead of silently. */
341
+ export const TOKEN_SPECS = [
342
+ {
343
+ program: SPL_TOKEN_PROGRAM,
344
+ label: 'SPL Token',
345
+ vendor: TOKEN_PINS.splToken,
346
+ runtime: 'solana-program/token program at this pin: unpack matches the same explicit tag set; an unknown tag is InvalidInstruction',
347
+ expectedTags: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 38, 45, 255],
348
+ variants: {
349
+ 0: 'InitializeMint', 1: 'InitializeAccount', 2: 'InitializeMultisig', 3: 'Transfer',
350
+ 4: 'Approve', 5: 'Revoke', 6: 'SetAuthority', 7: 'MintTo', 8: 'Burn', 9: 'CloseAccount',
351
+ 10: 'FreezeAccount', 11: 'ThawAccount', 12: 'TransferChecked', 13: 'ApproveChecked',
352
+ 14: 'MintToChecked', 15: 'BurnChecked', 16: 'InitializeAccount2', 17: 'SyncNative',
353
+ 18: 'InitializeAccount3', 19: 'InitializeMultisig2', 20: 'InitializeMint2',
354
+ 21: 'GetAccountDataSize', 22: 'InitializeImmutableOwner', 23: 'AmountToUiAmount',
355
+ 24: 'UiAmountToAmount', 38: 'WithdrawExcessLamports', 45: 'UnwrapLamports', 255: 'Batch',
356
+ },
357
+ authorityType: { count: 4 },
358
+ },
359
+ {
360
+ program: SPL_TOKEN_2022_PROGRAM,
361
+ label: 'Token-2022',
362
+ vendor: TOKEN_PINS.splToken2022,
363
+ runtime: 'solana-program/token-2022 program at this pin: the pod_instruction unpack matches the same explicit tag set; an unknown tag is InvalidInstruction; tags 26/27/28/30/33/34/36/37/39/40/41/42/43/44/46 dispatch into extension processors on input[1..]; 255 (Batch) is refused by the processor',
364
+ expectedTags: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 255],
365
+ variants: {
366
+ 0: 'InitializeMint', 1: 'InitializeAccount', 2: 'InitializeMultisig', 3: 'Transfer',
367
+ 4: 'Approve', 5: 'Revoke', 6: 'SetAuthority', 7: 'MintTo', 8: 'Burn', 9: 'CloseAccount',
368
+ 10: 'FreezeAccount', 11: 'ThawAccount', 12: 'TransferChecked', 13: 'ApproveChecked',
369
+ 14: 'MintToChecked', 15: 'BurnChecked', 16: 'InitializeAccount2', 17: 'SyncNative',
370
+ 18: 'InitializeAccount3', 19: 'InitializeMultisig2', 20: 'InitializeMint2',
371
+ 21: 'GetAccountDataSize', 22: 'InitializeImmutableOwner', 23: 'AmountToUiAmount',
372
+ 24: 'UiAmountToAmount', 25: 'InitializeMintCloseAuthority', 26: 'TransferFeeExtension',
373
+ 27: 'ConfidentialTransferExtension', 28: 'DefaultAccountStateExtension', 29: 'Reallocate',
374
+ 30: 'MemoTransferExtension', 31: 'CreateNativeMint', 32: 'InitializeNonTransferableMint',
375
+ 33: 'InterestBearingMintExtension', 34: 'CpiGuardExtension', 35: 'InitializePermanentDelegate',
376
+ 36: 'TransferHookExtension', 37: 'ConfidentialTransferFeeExtension', 38: 'WithdrawExcessLamports',
377
+ 39: 'MetadataPointerExtension', 40: 'GroupPointerExtension', 41: 'GroupMemberPointerExtension',
378
+ 42: 'ConfidentialMintBurnExtension', 43: 'ScaledUiAmountExtension', 44: 'PausableExtension',
379
+ 45: 'UnwrapLamports', 46: 'PermissionedBurnExtension', 255: 'Batch',
380
+ },
381
+ authorityType: { count: 18 },
382
+ },
383
+ ];
384
+
385
+ /** Derive both pinned token interfaces. */
386
+ export function deriveAllTokenInterfaces(options) {
387
+ return TOKEN_SPECS.map((spec) => deriveTokenInterface(spec, options));
388
+ }
389
+
390
+ /** sha256 of a vendored upstream file, as `sha256:<hex>`. */
391
+ export function vendoredTokenHash(spec, { root = REPO_ROOT } = {}) {
392
+ const raw = readFileSync(path.join(root, 'vendor', spec.vendor.dir, spec.vendor.pin, spec.vendor.file));
393
+ return `sha256:${createHash('sha256').update(raw).digest('hex')}`;
394
+ }