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
package/bin/sememe.mjs ADDED
@@ -0,0 +1,556 @@
1
+ #!/usr/bin/env node
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { readFileSync } from 'node:fs';
5
+ // `pull` is imported LAZILY (inside the branch): it is the only path that
6
+ // needs @solana/web3.js. A consumer who installed the package for DECODING
7
+ // only — the common case — must be able to run `sememe decode` without any
8
+ // package installed at all, so the acquisition dependency is never loaded
9
+ // unless a pull is actually requested.
10
+ import { decodePayload, decodeStoredAccount, decodeUnionPayload, decodeUnionStoredAccount, adopt, parsePayload } from '../src/decode.mjs';
11
+ import {
12
+ decodeNativeInstruction, nativeProvenance, nativePrograms, isNativeProgram,
13
+ } from '../src/native.mjs';
14
+ import {
15
+ decodeTokenInstruction, tokenProvenance, tokenPrograms, isTokenProgram,
16
+ } from '../src/token.mjs';
17
+ import {
18
+ decodeRaydiumInstruction, raydiumProvenance, raydiumPrograms, isRaydiumProgram,
19
+ } from '../src/raydium.mjs';
20
+ import {
21
+ decodeAtaInstruction, ataProvenance, ataPrograms, isAtaProgram,
22
+ } from '../src/ata.mjs';
23
+ import { toJsonSafe } from '../src/codec.mjs';
24
+ import { VERIFIED_SHORT_FORMS, SHORT_FORM_SOURCE } from '../src/short-forms.mjs';
25
+ // The DECLARED surface of every command (which flags it takes, how many
26
+ // positionals it consumes). Imported statically: it has no imports of its
27
+ // own, so it stays inside the decode core's zero-dependency guarantee.
28
+ import { parseCommandArgs, UsageError } from '../src/cli-surface.mjs';
29
+
30
+ const usage = `usage:
31
+ sememe absent-help
32
+ print the chain-verified short wire forms that --absent verified licenses.
33
+ sememe pull <program-id> [--store <directory>] [--rpc <https-url>]
34
+ sememe gap <program-id> [--rpc <https-url>]
35
+ answer the question "why is there no interface here?" BEFORE you pull.
36
+ Probes both on-chain IDL addresses and returns one of:
37
+ pullable an interface exists on chain that this program's
38
+ store simply does not have yet -> run sememe pull
39
+ no-interface-published neither address answers -> pulling it can never
40
+ help: adopt a published copy or derive upstream
41
+ present-undecodable an account exists but its schema is unusable
42
+ vendored answered by sememe's pinned wire table, no IDL
43
+ unknown the RPC failed — deliberately NOT an absence
44
+ Never reports an RPC failure as an absence, and never tells you to pull
45
+ a program that publishes nothing (the fix would never arrive).
46
+ sememe adopt <program-id> <file> [--store <directory>] [--source <label>]
47
+ store a first-party PUBLISHED interface copy next to the on-chain pull, so
48
+ union decode has a second verified source. The receipt says "published":
49
+ it is provenance of bytes, not proof the bytes describe the deployed program.
50
+ sememe adopt-lighthouse [--store <directory>]
51
+ the measured #1-remaining-gap case, wired end to end: adopt Lighthouse
52
+ (L2TExMFK…, 1,214 payloads in the committed 6-block window) from the
53
+ vendored first-party copy (Jac0xb/lighthouse, MIT), hash-checked against
54
+ its pin BEFORE any write; the receipt's origin stays declared.
55
+ sememe lock [--store <directory>] [--out <file>]
56
+ snapshot the WHOLE store into a sememe.lock document: for every program,
57
+ the exact fingerprint its newest verified receipt answered with, plus
58
+ source/slot/receipt provenance. Every receipt is re-checked through the
59
+ store reader first — a store the reader refuses produces NO lock, never
60
+ a lock over damage. Commit the file; CI then runs "sememe verify".
61
+ sememe verify [--store <directory>] [--lock <file>] [--mode error|warn]
62
+ re-check the store against the lock and refuse drift BY NAME:
63
+ drifted the store answers a pinned program with a DIFFERENT
64
+ fingerprint (a newer pull replaced it) — reported with
65
+ both fingerprints
66
+ missing the store has no receipt for a pinned program
67
+ unverified a pinned object fails the store reader's re-hash check
68
+ Extra store programs are reported, never a violation: the lock pins
69
+ minimums, not store equality. --mode error (default) exits 1 when any
70
+ violation exists (the CI gate); --mode warn reports and exits 0.
71
+ sememe decode <program-id> <payload> [--store <directory>] [--trailing report|error] [--absent error|report|verified]
72
+ payload: base58 or hex instruction data (discriminator + args)
73
+ --trailing report (default): decode the IDL-described prefix and report
74
+ the bytes the IDL does not explain under "trailing"; --trailing error
75
+ refuses the payload instead.
76
+ --absent error (default): a payload that ENDS where a declared field
77
+ would start is refused as truncated. --absent report: decode it and
78
+ name the absent fields. --absent verified: decode it only when a
79
+ chain witness covers that exact wire form (see --absent-help).
80
+ sememe decode --union <program-id> <payload> [--store <directory>]
81
+ decode against BOTH stored sources (on-chain + adopted). The result names
82
+ the source that answered, the other source's verdict, and any layout
83
+ drift between them. Byte evidence arbitrates; a name conflict is refused.
84
+ sememe account <program-id> <account-data> [--store <directory>] [--name <account-type>]
85
+ account-data: base64 account bytes (as getMultipleAccounts returns them)
86
+ or hex. Without --name the account's 8-byte discriminator picks the
87
+ account type from the stored IDL.
88
+ sememe account --union <program-id> <account-data> [--store <directory>]
89
+ decode against BOTH stored sources (on-chain + adopted), like
90
+ "sememe decode --union". The result names the source that answered, the
91
+ other source's verdict, and any layout drift between them. Byte evidence
92
+ arbitrates; a name conflict is refused.
93
+ sememe conform <transaction.json> [--store <directory>] [--only <program-id>]
94
+ transaction.json: an RPC getTransaction/getBlock response entry
95
+ (encoding "json") with its meta — as saved by any RPC client.
96
+ Checks EVERY instruction of the transaction against the interface the
97
+ verified store declares for its program: account count, signer flags,
98
+ pinned addresses, and extra accounts. A disagreement is named; an
99
+ unreadable transaction shape is REFUSED, never reported as conforming.
100
+ sememe decode-native <program-id> <payload> [--trailing report|error]
101
+ payload: base58 or hex instruction data. For the NATIVE programs (System,
102
+ ComputeBudget) there is no IDL to pull: their interface is pinned from
103
+ the upstream runtime, and this decodes against that pin. The result names
104
+ the instruction, its arguments, and whether the bytes re-encode exactly.
105
+ sememe native-provenance [program-id]
106
+ print the pinned source, encoding rule and measured traffic for a native
107
+ program (or for all of them).
108
+ sememe decode-token <program-id> <payload> [--trailing report|error]
109
+ payload: base58 or hex instruction data. For SPL TOKEN and TOKEN-2022
110
+ there is no IDL account to pull at either address: their interface is
111
+ pinned from the upstream Rust crates (pack/unpack wire rule), and this
112
+ decodes against that pin. Extension sub-instructions report as trailing.
113
+ Token-2022 payloads whose first byte is no token tag fall through to the
114
+ token-METADATA table (spl-token-metadata-interface, 8-byte
115
+ discriminators) — the same fallback Token-2022's own processor takes.
116
+ sememe token-provenance [program-id]
117
+ print the pinned crate, encoding rule and variant count for a token
118
+ program (or for both).
119
+ sememe decode-raydium <program-id> <payload> [--trailing report|error]
120
+ payload: base58 or hex instruction data. For RAYDIUM AMM V4 there is no
121
+ IDL account to pull at either address: its interface is pinned from the
122
+ upstream program source (sparse u8 tag, pack/unpack wire rule), and this
123
+ decodes against that pin. Variants upstream itself refuses stay refused.
124
+ sememe raydium-provenance [program-id]
125
+ print the pinned upstream commit, encoding rule and variant counts for a
126
+ Raydium program (or for all of them).
127
+ sememe decode-ata <program-id> [<payload>] [--empty] [--trailing report|error]
128
+ payload: base58 or hex instruction data, or --empty for the processor's
129
+ own empty-input Create fallback. For the SPL ASSOCIATED TOKEN ACCOUNT
130
+ program there is no IDL account to pull at either address: its interface
131
+ is pinned from the upstream program source (borsh unit enum, empty-input
132
+ processor fallback), and this decodes against that pin.
133
+ sememe ata-provenance [program-id]
134
+ print the pinned upstream commit, encoding rule and variant counts for
135
+ the SPL Associated Token Account program.
136
+
137
+ Every command declares the flags it accepts and how many positional arguments
138
+ it consumes. A flag the command does not know, a flag given twice, a flag
139
+ without a value, or a surplus positional argument is REFUSED BY NAME before
140
+ any work happens -- no store is read, no file is written, no RPC is
141
+ contacted. The refusal names the closest supported flag:
142
+
143
+ sememe lock --store ./store --o out.lock
144
+ sememe lock: unknown flag "--o" — did you mean "--out"?
145
+ supported flags: --store, --out`;
146
+
147
+ const [command, ...tokens] = process.argv.slice(2);
148
+
149
+ try {
150
+ // Parse the whole command line AGAINST THE SPEC, up front, before any
151
+ // branch runs: an unknown flag, a repeated flag, a missing flag value or a
152
+ // surplus positional argument is refused here, before a store is read, a
153
+ // file is written or an RPC is contacted. Previously the flags were
154
+ // searched for by name, so a typo was silently dropped and the command ran
155
+ // against whatever it would have done without that flag — a lock written to
156
+ // a different path than the one the caller named, a pull that touched the
157
+ // default store after a --stor typo.
158
+ const { flags, positionals } = parseCommandArgs(command, tokens);
159
+ const program = positionals[0];
160
+ // The positionals the branches consume with args.shift(): everything after
161
+ // the first one. Flags are already separated, so no order matters.
162
+ const args = positionals.slice(1);
163
+ const unionRequested = flags['--union'] === true;
164
+ const option = (name, fallback) => (Object.prototype.hasOwnProperty.call(flags, name) ? flags[name] : fallback);
165
+
166
+ if (command === 'absent-help') {
167
+ // The evidence behind --absent verified, printed so a user can see WHICH
168
+ // wire forms are licensed before relying on the flag.
169
+ console.log(JSON.stringify({
170
+ source: SHORT_FORM_SOURCE,
171
+ forms: VERIFIED_SHORT_FORMS,
172
+ }, null, 2));
173
+ } else if (command === 'pull') {
174
+ if (!program) throw Error('pull needs a program id');
175
+ const store = option('--store', path.join(os.homedir(), '.sememe'));
176
+ const rpc = option('--rpc', 'https://api.mainnet-beta.solana.com');
177
+ let pull;
178
+ try {
179
+ ({ pull } = await import('../src/pull.mjs'));
180
+ } catch (error) {
181
+ // The library already refuses with a complete sentence naming the fix
182
+ // (src/peer.mjs); wrapping it again produced a doubled message that
183
+ // said "install it next to sememe" twice. Anything else still gets the
184
+ // contextual wrap with the cause appended.
185
+ if (error?.code === 'peer_dependency_missing') throw error;
186
+ throw Error(`pull needs @solana/web3.js: install it next to sememe (npm i @solana/web3.js@1.98.4) — ${String(error?.message ?? error).slice(0, 120)}`);
187
+ }
188
+ console.log(JSON.stringify(await pull(program, store, rpc), null, 2));
189
+ } else if (command === 'gap') {
190
+ if (!program) throw Error('gap needs a program id');
191
+ const rpc = option('--rpc', 'https://api.mainnet-beta.solana.com');
192
+ let probe, where, nextStep;
193
+ try {
194
+ // Lazily imported like `pull`: the only path that needs web3.js.
195
+ ({ probeGap: probe, nextStep } = await import('../src/gap.mjs'));
196
+ ({ addresses: where } = await import('../src/acquire.mjs'));
197
+ } catch (error) {
198
+ if (error?.code === 'peer_dependency_missing') throw error;
199
+ throw Error(`gap needs @solana/web3.js: install it next to sememe (npm i @solana/web3.js@1.98.4) — ${String(error?.message ?? error).slice(0, 120)}`);
200
+ }
201
+ // The same classifier the qualification report uses, so what a user is
202
+ // told and what the research artifact records are one code path.
203
+ const { rows } = await probe([{ program, payloads: 0 }], { url: rpc });
204
+ const row = rows[0];
205
+ console.log(JSON.stringify({ ...row, addresses: await where(program), next: nextStep(row, program) }, null, 2));
206
+ if (row.verdict === 'unknown') process.exitCode = 3;
207
+ } else if (command === 'lock') {
208
+ if (!program) {
209
+ // No program argument: lock the WHOLE store (the common case). A program
210
+ // id here would otherwise be swallowed silently, so an explicit store
211
+ // wide lock must be chosen by omitting the argument entirely.
212
+ const store = option('--store', path.join(os.homedir(), '.sememe'));
213
+ const out = option('--out', 'sememe.lock');
214
+ const { writeLock } = await import('../src/lock.mjs');
215
+ const doc = await writeLock(store, out);
216
+ console.log(JSON.stringify(doc, null, 2));
217
+ } else {
218
+ throw Error(`lock takes no program id — it snapshots the whole store; got "${program}"`);
219
+ }
220
+ } else if (command === 'verify') {
221
+ const store = option('--store', path.join(os.homedir(), '.sememe'));
222
+ const lockFile = option('--lock', 'sememe.lock');
223
+ const mode = option('--mode', 'error');
224
+ if (mode !== 'error' && mode !== 'warn') throw Error('--mode must be "error" or "warn"');
225
+ if (program) throw Error(`verify takes no program id — it checks the whole lock; got "${program}"`);
226
+ const { checkLock } = await import('../src/lock.mjs');
227
+ const report = await checkLock(store, lockFile, { mode });
228
+ console.log(JSON.stringify(report, null, 2));
229
+ if (report.violations > 0) {
230
+ console.error(JSON.stringify({
231
+ note: `store does not match ${lockFile}: ${report.drifted.length} drifted, ${report.missing.length} missing, ${report.unverified.length} unverified, ${report.relabelled.length} relabelled, ${report.unbound.length} unbound`,
232
+ drifted: report.drifted.map((d) => d.program),
233
+ missing: report.missing,
234
+ unverified: report.unverified.map((u) => (u.scope === 'extra'
235
+ ? `${u.program} (extra: the lock does not pin it — re-pull that program's receipt)`
236
+ : u.program)),
237
+ relabelled: report.relabelled.map((r) => r.program),
238
+ unbound: report.unbound.map((r) => r.program),
239
+ }));
240
+ // The note goes to stderr as a WARNING. The exit stays 0: in error mode
241
+ // checkLock already threw above (that is the gate), so reaching here
242
+ // with violations can only be warn mode — a warning that fails the
243
+ // build is not a warning.
244
+ }
245
+ } else if (command === 'adopt') {
246
+ if (!program) throw Error('adopt needs a program id');
247
+ const file = args.shift();
248
+ if (!file) throw Error('adopt needs the interface file to store');
249
+ const store = option('--store', path.join(os.homedir(), '.sememe'));
250
+ const source = option('--source', 'published');
251
+ const origin = option('--origin', undefined);
252
+ const adopted = await adopt(program, file, store, {
253
+ source,
254
+ ...(origin === undefined ? {} : { origin }),
255
+ });
256
+ // The origin's STATUS travels with the result: a declared label is a
257
+ // claim the caller typed, not a provenance sememe checked. The CLI says
258
+ // so out loud, so an adopter cannot mistake a stored string for a
259
+ // verified one.
260
+ console.log(JSON.stringify({
261
+ ...adopted,
262
+ originStatus: adopted.originStatus ?? 'declared',
263
+ originNote: adopted.originNote
264
+ ?? 'declared origin, not verified: sememe did not check that this repository, commit or file exists — provenance of bytes, not proof they describe the deployed program',
265
+ }, null, 2));
266
+ } else if (command === 'adopt-lighthouse') {
267
+ const store = option('--store', path.join(os.homedir(), '.sememe'));
268
+ const { adoptLighthouse } = await import('../src/lighthouse.mjs');
269
+ const { adopt: adoptFn } = await import('../src/adopt.mjs');
270
+ const result = await adoptLighthouse(store, { adopt: adoptFn });
271
+ console.log(JSON.stringify({
272
+ program: result.receipt.program,
273
+ source: result.receipt.source,
274
+ origin: result.receipt.origin,
275
+ originStatus: result.receipt.originStatus,
276
+ originNote: result.receipt.originNote,
277
+ fingerprint: result.receipt.fingerprint,
278
+ instructionCount: result.receipt.instructionCount,
279
+ path: result.receipt.path,
280
+ receiptPath: result.receipt.receiptPath,
281
+ }, null, 2));
282
+ } else if (command === 'decode') {
283
+ if (!program) throw Error('decode needs a program id');
284
+ const payload = args.shift();
285
+ if (!payload) throw Error('decode needs an instruction payload');
286
+ const store = option('--store', path.join(os.homedir(), '.sememe'));
287
+ const trailing = option('--trailing', undefined);
288
+ if (trailing !== undefined && trailing !== 'report' && trailing !== 'error') {
289
+ throw Error('--trailing must be "report" or "error"');
290
+ }
291
+ const absent = option('--absent', undefined);
292
+ if (absent !== undefined && !['error', 'report', 'verified'].includes(absent)) {
293
+ throw Error('--absent must be "error", "report" or "verified"');
294
+ }
295
+ const union = unionRequested;
296
+ const extra = { ...(trailing === undefined ? {} : { trailing }), ...(absent === undefined ? {} : { absent }) };
297
+ // A program that publishes NO interface — the two token programs, the two
298
+ // native programs, Raydium AMM v4 — has no stored receipt, so the store
299
+ // path cannot answer for it. That is not a missing-pull problem and the
300
+ // error must not say it is: name the vendored path that CAN answer, or a
301
+ // first-time user reads "run sememe pull" as the fix and pulls forever.
302
+ const vendored = (programId) => (isTokenProgram(programId) ? 'decode-token'
303
+ : isNativeProgram(programId) ? 'decode-native'
304
+ : isRaydiumProgram(programId) ? 'decode-raydium'
305
+ : isAtaProgram(programId) ? 'decode-ata' : null);
306
+ let decoded;
307
+ try {
308
+ decoded = union
309
+ ? await decodeUnionPayload(store, program, payload, extra)
310
+ : await decodePayload(store, program, payload, extra);
311
+ } catch (error) {
312
+ const message = String(error?.message ?? error);
313
+ const command = vendored(program);
314
+ if (command && (message.startsWith('no stored IDL for program') || message.startsWith('no sememe store at'))) {
315
+ // The vendored answer is the whole answer: no pull will ever create a
316
+ // receipt for this program, so the "run sememe pull" part of the
317
+ // store's message is dropped rather than left to contradict the hint.
318
+ throw Error(`${program} publishes no interface to pull, so there is nothing to store — ` +
319
+ `its wire table is pinned from upstream instead.\n` +
320
+ ` use: sememe ${command} ${program} <payload>`);
321
+ }
322
+ throw error;
323
+ }
324
+ console.log(JSON.stringify(decoded, null, 2));
325
+ // A short-form decode is a measurement-backed claim, so the evidence is
326
+ // surfaced on stderr as well: a scripted caller should not have to know
327
+ // to look inside the JSON to learn the field was absent and why.
328
+ if (decoded.shortForm) {
329
+ console.error(JSON.stringify({
330
+ note: `${decoded.name} decoded as a chain-verified short form: the wire carries no `
331
+ + `${JSON.stringify(decoded.shortForm.absent)}`,
332
+ verified: decoded.shortForm.verified,
333
+ absent: decoded.shortForm.absent,
334
+ witnesses: decoded.shortForm.witnesses,
335
+ source: decoded.shortForm.source,
336
+ }));
337
+ }
338
+ // Unexplained bytes are a completeness finding, not a crash: report them
339
+ // on stderr so scripted callers notice without losing the decode result.
340
+ if (decoded.trailing.count > 0 && trailing !== 'error') {
341
+ console.error(JSON.stringify({
342
+ note: `${decoded.trailing.count} byte(s) after ${decoded.name} are not described by the stored IDL`,
343
+ consumed: decoded.consumed,
344
+ bytes: decoded.bytes,
345
+ trailing: decoded.trailing,
346
+ }));
347
+ }
348
+ } else if (command === 'conform') {
349
+ // The first positional after `conform` is the file (it lands in `program`
350
+ // like every other command's first argument).
351
+ const file = program ?? args.shift();
352
+ if (!file) throw Error('conform needs a transaction JSON file');
353
+ const store = option('--store', path.join(os.homedir(), '.sememe'));
354
+ const only = option('--only', undefined);
355
+ const envelope = JSON.parse(readFileSync(file, 'utf8'));
356
+ // Accept either a bare transaction entry or the full RPC envelope.
357
+ const transaction = envelope?.result ?? envelope;
358
+ const { checkTransaction } = await import('../src/conformance.mjs');
359
+ const report = await checkTransaction(transaction, store, only ? { only: [only] } : {});
360
+ console.log(JSON.stringify(report, null, 2));
361
+ const disagreement = report.summary.byVerdict;
362
+ const violations = Object.entries(disagreement)
363
+ .filter(([kind]) => ['missing-declared-accounts', 'declared-signer-not-signing', 'fixed-address-mismatch', 'undeclared-signing-account'].includes(kind));
364
+ if (violations.length) {
365
+ console.error(JSON.stringify({ note: 'account-list disagreement(s) found', kinds: violations.map(([kind, count]) => `${kind}:${count}`) }));
366
+ process.exitCode = 1;
367
+ }
368
+ } else if (command === 'decode-native') {
369
+ if (!program) throw Error('decode-native needs a program id');
370
+ const payload = args.shift();
371
+ if (!payload) throw Error('decode-native needs an instruction payload');
372
+ const trailing = option('--trailing', 'report');
373
+ if (trailing !== 'report' && trailing !== 'error') {
374
+ throw Error('--trailing must be "report" or "error"');
375
+ }
376
+ const decoded = decodeNativeInstruction(program, parsePayload(payload), { trailing });
377
+ console.log(JSON.stringify({
378
+ program,
379
+ kind: 'native-instruction',
380
+ name: decoded.name,
381
+ selector: decoded.selector,
382
+ selectorWidth: decoded.selectorWidth,
383
+ args: toJsonSafe(decoded.args),
384
+ bytes: decoded.bytes,
385
+ consumed: decoded.consumed,
386
+ trailing: decoded.trailing,
387
+ roundTripByteExact: decoded.roundTripByteExact,
388
+ interface: nativeProvenance(program),
389
+ }, null, 2));
390
+ } else if (command === 'native-provenance') {
391
+ const wanted = program;
392
+ if (wanted) {
393
+ const provenance = nativeProvenance(wanted);
394
+ if (!provenance) throw Error(`${wanted} is not a native program sememe pins`);
395
+ console.log(JSON.stringify(provenance, null, 2));
396
+ } else {
397
+ console.log(JSON.stringify(nativePrograms().map((id) => nativeProvenance(id)), null, 2));
398
+ }
399
+ } else if (command === 'decode-token') {
400
+ if (!program) throw Error('decode-token needs a program id');
401
+ const payload = args.shift();
402
+ if (!payload) throw Error('decode-token needs an instruction payload');
403
+ const trailing = option('--trailing', 'report');
404
+ if (trailing !== 'report' && trailing !== 'error') {
405
+ throw Error('--trailing must be "report" or "error"');
406
+ }
407
+ const decoded = decodeTokenInstruction(program, parsePayload(payload), { trailing });
408
+ if (decoded.refused === 'not-token') {
409
+ throw Error(`${program} is not a token program sememe pins — try decode-native or decode`);
410
+ }
411
+ console.log(JSON.stringify({
412
+ program,
413
+ kind: 'token-instruction',
414
+ name: decoded.name,
415
+ tag: decoded.tag,
416
+ selectorWidth: decoded.selectorWidth,
417
+ args: toJsonSafe(decoded.args),
418
+ authorityType: decoded.authorityType,
419
+ bytes: decoded.bytes,
420
+ consumed: decoded.consumed,
421
+ trailing: decoded.trailing,
422
+ roundTripByteExact: decoded.roundTripByteExact,
423
+ interface: tokenProvenance(program),
424
+ }, null, 2));
425
+ } else if (command === 'token-provenance') {
426
+ const wanted = program;
427
+ if (wanted) {
428
+ const provenance = tokenProvenance(wanted);
429
+ if (!provenance) throw Error(`${wanted} is not a token program sememe pins`);
430
+ console.log(JSON.stringify(provenance, null, 2));
431
+ } else {
432
+ console.log(JSON.stringify(tokenPrograms().map((id) => tokenProvenance(id)), null, 2));
433
+ }
434
+ } else if (command === 'raydium-provenance') {
435
+ const wanted = program;
436
+ if (wanted) {
437
+ const provenance = raydiumProvenance(wanted);
438
+ if (!provenance) throw Error(`${wanted} is not a Raydium program sememe pins`);
439
+ console.log(JSON.stringify(provenance, null, 2));
440
+ } else {
441
+ console.log(JSON.stringify(raydiumPrograms().map((id) => raydiumProvenance(id)), null, 2));
442
+ }
443
+ } else if (command === 'decode-raydium') {
444
+ if (!program) throw Error('decode-raydium needs a program id');
445
+ const payload = args.shift();
446
+ if (!payload) throw Error('decode-raydium needs an instruction payload');
447
+ const trailing = option('--trailing', 'report');
448
+ if (trailing !== 'report' && trailing !== 'error') {
449
+ throw Error('--trailing must be "report" or "error"');
450
+ }
451
+ const decoded = decodeRaydiumInstruction(program, parsePayload(payload), { trailing });
452
+ if (decoded.refused === 'not-raydium') {
453
+ throw Error(`${program} is not a Raydium program sememe pins — try decode-native, decode-token or decode`);
454
+ }
455
+ console.log(JSON.stringify({
456
+ program,
457
+ kind: 'raydium-instruction',
458
+ name: decoded.name,
459
+ tag: decoded.tag,
460
+ selectorWidth: decoded.selectorWidth,
461
+ args: toJsonSafe(decoded.args),
462
+ absent: decoded.absent,
463
+ bytes: decoded.bytes,
464
+ consumed: decoded.consumed,
465
+ trailing: decoded.trailing,
466
+ roundTripByteExact: decoded.roundTripByteExact,
467
+ interface: raydiumProvenance(program),
468
+ }, null, 2));
469
+ } else if (command === 'decode-ata') {
470
+ if (!program) throw Error('decode-ata needs a program id');
471
+ const trailing = option('--trailing', 'report');
472
+ if (trailing !== 'report' && trailing !== 'error') {
473
+ throw Error('--trailing must be "report" or "error"');
474
+ }
475
+ // The empty payload is a REAL instruction for this program: the pin's
476
+ // processor executes empty input as Create. It therefore needs a way to
477
+ // be said out loud, and `--empty` is that way — the base58/hex parsers
478
+ // have no empty string, by design.
479
+ const empty = flags['--empty'] === true;
480
+ const payloadArg = args.shift();
481
+ if (!payloadArg && !empty) {
482
+ throw Error('decode-ata needs an instruction payload (use --empty for the empty-input Create fallback)');
483
+ }
484
+ if (payloadArg && empty) {
485
+ throw Error('decode-ata: give a payload or --empty, not both');
486
+ }
487
+ const decoded = decodeAtaInstruction(program, empty ? Buffer.alloc(0) : parsePayload(payloadArg), { trailing });
488
+ if (decoded.refused === 'not-ata') {
489
+ throw Error(`${program} is not a program sememe pins with the ATA table — try decode-native, decode-token, decode-raydium or decode`);
490
+ }
491
+ console.log(JSON.stringify({
492
+ program,
493
+ kind: 'ata-instruction',
494
+ name: decoded.name,
495
+ tag: decoded.tag,
496
+ selectorWidth: decoded.selectorWidth,
497
+ args: {},
498
+ bytes: decoded.bytes,
499
+ consumed: decoded.consumed,
500
+ trailing: decoded.trailing,
501
+ roundTripByteExact: decoded.roundTripByteExact,
502
+ note: decoded.note,
503
+ interface: ataProvenance(program),
504
+ }, null, 2));
505
+ } else if (command === 'ata-provenance') {
506
+ const provenance = program ? ataProvenance(program) : null;
507
+ if (program && !provenance) {
508
+ throw Error(`${program} is not a program sememe pins with the ATA table — try decode-native, decode-token, decode-raydium or decode`);
509
+ }
510
+ console.log(JSON.stringify(provenance ?? ataPrograms().map((id) => ataProvenance(id)), null, 2));
511
+ } else if (command === 'account') {
512
+ if (!program) throw Error('account needs a program id');
513
+ const data = args.shift();
514
+ if (!data) throw Error('account needs the account data (base64 or hex)');
515
+ const store = option('--store', path.join(os.homedir(), '.sememe'));
516
+ const name = option('--name', undefined);
517
+ const trailing = option('--trailing', undefined);
518
+ if (trailing !== undefined && trailing !== 'report' && trailing !== 'error') {
519
+ throw Error('--trailing must be "report" or "error"');
520
+ }
521
+ // The union path reads BOTH verified store sources for the program (the
522
+ // on-chain pull and the adopted published copy) and arbitrates by byte
523
+ // evidence, exactly like `sememe decode --union`. Without it the account
524
+ // path read ONE receipt, so a store holding two copies of a program had
525
+ // two opinions about an account's layout and only one was consulted.
526
+ const decoded = unionRequested
527
+ ? await decodeUnionStoredAccount(store, program, data,
528
+ name === undefined ? { trailing } : { name, trailing })
529
+ : await decodeStoredAccount(store, program, data,
530
+ name === undefined ? { trailing } : { name, trailing });
531
+ console.log(JSON.stringify(decoded, null, 2));
532
+ if (decoded.trailing.count > 0 && trailing !== 'error') {
533
+ console.error(JSON.stringify({
534
+ note: `${decoded.trailing.count} byte(s) after the described struct are not described by the stored IDL`,
535
+ consumed: decoded.consumed,
536
+ bytes: decoded.bytes,
537
+ trailing: decoded.trailing,
538
+ }));
539
+ }
540
+ } else {
541
+ console.error(usage);
542
+ process.exitCode = 2;
543
+ }
544
+ } catch (error) {
545
+ if (error instanceof UsageError) {
546
+ // A malformed command line is refused BEFORE any work, and it is a usage
547
+ // problem, not a failure of the work: plain text on stderr — the same
548
+ // channel the unknown-command usage dump uses — and exit 2. The machine
549
+ // readable JSON error channel stays for errors the work itself produced.
550
+ console.error(error.message);
551
+ process.exitCode = 2;
552
+ } else {
553
+ console.error(JSON.stringify({ error: String(error?.message ?? error) }));
554
+ process.exitCode = 1;
555
+ }
556
+ }
package/index.mjs ADDED
@@ -0,0 +1,48 @@
1
+ // sememe's package entry: the offline decode surface, and nothing else.
2
+ //
3
+ // The split is deliberate and enforced (test/package.test.mjs,
4
+ // test/deps.test.mjs):
5
+ //
6
+ // the entry ("sememe")
7
+ // - decodes instructions, accounts and event self-CPIs from a local,
8
+ // fingerprint-verified store; re-checks the receipt on every read;
9
+ // - imports only node: builtins and files inside this package, so a
10
+ // consumer installs ZERO runtime dependencies;
11
+ // - never touches the network, the CLI, or the qualification harness.
12
+ //
13
+ // the acquisition subpath ("sememe/pull")
14
+ // - acquisition: finds a program's IDL account on chain and writes the
15
+ // store. Needs @solana/web3.js (declared as an optional peer) and a
16
+ // network. A decoder-only consumer never loads it.
17
+ //
18
+ // A package that exports its harness is a harness. This file exists so the
19
+ // consumer's surface is a list you can read in one screen.
20
+
21
+ export {
22
+ decodePayload,
23
+ decodeUnionPayload,
24
+ decodeStoredAccount,
25
+ decodeUnionStoredAccount,
26
+ findStoredIdl,
27
+ findStoredIdls,
28
+ adopt,
29
+ parsePayload,
30
+ decodeHex,
31
+ decodeBase58,
32
+ unionDecode,
33
+ unionAccountDecode,
34
+ UnionRefusal,
35
+ verifiedShortForm,
36
+ VERIFIED_SHORT_FORMS,
37
+ } from './src/decode.mjs';
38
+
39
+ export { encodeBase58 } from './src/base58.mjs';
40
+ export { canonicalJson } from './src/canonical.mjs';
41
+ // The native programs (System, ComputeBudget) are NOT re-exported here on
42
+ // purpose: src/native.mjs derives its wire table from the pinned upstream
43
+ // sources AT IMPORT TIME and refuses to load when the committed artifact
44
+ // disagrees. That loud failure is right for the native decoder, but it must
45
+ // not take the whole entry down for a consumer who only decodes an Anchor
46
+ // instruction. Use `sememe/native` for those two programs. The same applies
47
+ // to the two token programs (SPL Token, Token-2022) — `sememe/token` derives
48
+ // their wire tables from the pinned upstream Rust crates the same way.
@@ -0,0 +1,62 @@
1
+ {
2
+ "program": "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL",
3
+ "label": "SPL Associated Token Account",
4
+ "scheme": "native-ata",
5
+ "source": {
6
+ "repo": "solana-program/associated-token-account",
7
+ "pin": "fbf5379e7398ac8b231c73ee8a9ff677b43dd0ec",
8
+ "license": "Apache-2.0 (LICENSE vendored beside the sources)",
9
+ "enum": "AssociatedTokenAccountInstruction (borsh unit enum: the variant index is the one-byte payload)",
10
+ "programSource": "vendored interface/src/lib.rs declare_id",
11
+ "files": [
12
+ {
13
+ "file": "interface/src/instruction.rs",
14
+ "role": "AssociatedTokenAccountInstruction: borsh unit enum, serialise arms carry the wire byte",
15
+ "sha256": "sha256:033191cb1316fe2370fb9f694b0a0f9f140f647ca44661f07cb661ed4aa8c56d"
16
+ },
17
+ {
18
+ "file": "program/src/processor.rs",
19
+ "role": "runtime verdict: empty input falls back to Create, borsh try_from_slice judges the rest, InvalidInstructionData refuses",
20
+ "sha256": "sha256:e8bf0dd2d9ac87ebc488faf73dee2530df6d6dc83bc19907def973c5209953ca"
21
+ },
22
+ {
23
+ "file": "interface/src/lib.rs",
24
+ "role": "the program id (declare_id), read from upstream, not re-typed",
25
+ "sha256": "sha256:a1bf5de71e01902c9a5b42604ba4eb696c7ef4c100bc6c72a0b59a926f4efad0"
26
+ }
27
+ ],
28
+ "vendoredAt": "vendor/spl-associated-token-account/fbf5379e7398ac8b231c73ee8a9ff677b43dd0ec",
29
+ "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)"
30
+ },
31
+ "runtime": {
32
+ "judge": "borsh AssociatedTokenAccountInstruction::try_from_slice(input)",
33
+ "emptyInputFallback": "Create",
34
+ "unknownInputRefusal": "ProgramError::InvalidInstructionData",
35
+ "source": "program/src/processor.rs at the pin"
36
+ },
37
+ "selector": {
38
+ "width": 1,
39
+ "encoding": "borsh enum index, u8; empty input is the processor's own Create fallback, not an unknown selector"
40
+ },
41
+ "argEncoding": "unit enum: payload is exactly the one index byte; no argument bytes",
42
+ "instructions": [
43
+ {
44
+ "tag": 0,
45
+ "name": "Create",
46
+ "wire": "borsh-unit-enum",
47
+ "args": []
48
+ },
49
+ {
50
+ "tag": 1,
51
+ "name": "CreateIdempotent",
52
+ "wire": "borsh-unit-enum",
53
+ "args": []
54
+ },
55
+ {
56
+ "tag": 2,
57
+ "name": "RecoverNested",
58
+ "wire": "borsh-unit-enum",
59
+ "args": []
60
+ }
61
+ ]
62
+ }