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.
- package/LICENSE +21 -0
- package/README.md +2667 -2
- package/bin/sememe.mjs +556 -0
- package/index.mjs +48 -0
- package/interface/ata-ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL.json +62 -0
- package/interface/native-11111111111111111111111111111111.json +226 -0
- package/interface/native-ComputeBudget111111111111111111111111111111.json +72 -0
- package/interface/raydium-675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8.json +352 -0
- package/interface/token-TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA.json +334 -0
- package/interface/token-TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb.json +535 -0
- package/interface/token-metadata.json +132 -0
- package/package.json +71 -4
- package/src/account-list.mjs +308 -0
- package/src/account-union.mjs +523 -0
- package/src/account.mjs +199 -0
- package/src/acquire.mjs +145 -0
- package/src/adopt.mjs +205 -0
- package/src/ata-interface.mjs +276 -0
- package/src/ata.mjs +223 -0
- package/src/base58.mjs +96 -0
- package/src/canonical.mjs +24 -0
- package/src/cli-surface.mjs +300 -0
- package/src/codec.mjs +1069 -0
- package/src/conformance.mjs +431 -0
- package/src/corpus/harvest-compute-budget-2026-10-31.json +19122 -0
- package/src/corpus/harvest-system-2026-10-31.json +26090 -0
- package/src/decode.mjs +286 -0
- package/src/dialect.mjs +145 -0
- package/src/discriminators.mjs +177 -0
- package/src/drift.mjs +172 -0
- package/src/gap.mjs +205 -0
- package/src/lighthouse.mjs +168 -0
- package/src/lock.mjs +440 -0
- package/src/native-audit.mjs +56 -0
- package/src/native-corpus.mjs +57 -0
- package/src/native-interface.mjs +252 -0
- package/src/native.mjs +324 -0
- package/src/peer.mjs +95 -0
- package/src/program-id.mjs +74 -0
- package/src/pull.mjs +117 -0
- package/src/rawle.mjs +450 -0
- package/src/raydium-interface.mjs +747 -0
- package/src/raydium.mjs +391 -0
- package/src/receipt-digest.mjs +84 -0
- package/src/receipt-name.mjs +128 -0
- package/src/receipt-order.mjs +56 -0
- package/src/shank.mjs +73 -0
- package/src/short-forms.mjs +209 -0
- package/src/store-read.mjs +250 -0
- package/src/store-write.mjs +46 -0
- package/src/token-interface.mjs +394 -0
- package/src/token-metadata-interface.mjs +371 -0
- package/src/token-metadata.mjs +317 -0
- package/src/token.mjs +443 -0
- package/src/union.mjs +428 -0
- package/vendor/anza-solana-sdk/8db406e2edfafa25939ec782b77b5b6f30de164d/LICENSE.txt +201 -0
- package/vendor/anza-solana-sdk/8db406e2edfafa25939ec782b77b5b6f30de164d/compute-budget-interface/src/lib.rs +93 -0
- package/vendor/anza-solana-sdk/8db406e2edfafa25939ec782b77b5b6f30de164d/system-interface/src/instruction.rs +1899 -0
- package/vendor/lighthouse/4c579479c98635e419b1b167f08be02a71604a71/LICENSE +21 -0
- package/vendor/lighthouse/4c579479c98635e419b1b167f08be02a71604a71/lighthouse.json +2769 -0
- package/vendor/raydium-amm/91739d8d9f5cf347e0ce14ebe4d2bb7069fb31b6/LICENSE +201 -0
- package/vendor/raydium-amm/91739d8d9f5cf347e0ce14ebe4d2bb7069fb31b6/program/src/instruction.rs +1189 -0
- package/vendor/raydium-amm/91739d8d9f5cf347e0ce14ebe4d2bb7069fb31b6/program/src/state.rs +1348 -0
- package/vendor/spl-associated-token-account/fbf5379e7398ac8b231c73ee8a9ff677b43dd0ec/LICENSE +201 -0
- package/vendor/spl-associated-token-account/fbf5379e7398ac8b231c73ee8a9ff677b43dd0ec/interface/src/instruction.rs +169 -0
- package/vendor/spl-associated-token-account/fbf5379e7398ac8b231c73ee8a9ff677b43dd0ec/interface/src/lib.rs +12 -0
- package/vendor/spl-associated-token-account/fbf5379e7398ac8b231c73ee8a9ff677b43dd0ec/program/src/processor.rs +308 -0
- package/vendor/spl-token/737788ff5bcc3f1f44cfb222f25483187650be76/LICENSE.txt +176 -0
- package/vendor/spl-token/737788ff5bcc3f1f44cfb222f25483187650be76/interface/src/instruction.rs +1963 -0
- package/vendor/spl-token-2022/5251dcc07ecbcb721d6c4f0231090defadf00311/LICENSE.txt +176 -0
- package/vendor/spl-token-2022/5251dcc07ecbcb721d6c4f0231090defadf00311/interface/src/instruction.rs +2868 -0
- package/vendor/spl-token-metadata-interface/7038969ec74a89f1afdb9bc97c112d4d26fead9a/LICENSE +201 -0
- package/vendor/spl-token-metadata-interface/7038969ec74a89f1afdb9bc97c112d4d26fead9a/interface/src/error.rs +58 -0
- package/vendor/spl-token-metadata-interface/7038969ec74a89f1afdb9bc97c112d4d26fead9a/interface/src/instruction.rs +596 -0
- package/vendor/spl-token-metadata-interface/7038969ec74a89f1afdb9bc97c112d4d26fead9a/interface/src/lib.rs +21 -0
- package/vendor/spl-token-metadata-interface/7038969ec74a89f1afdb9bc97c112d4d26fead9a/interface/src/state.rs +229 -0
package/src/token.mjs
ADDED
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
// The SPL Token + Token-2022 decoder: the vendored-upstream path for the two
|
|
2
|
+
// programs that publish NO IDL account to pull.
|
|
3
|
+
//
|
|
4
|
+
// Why this file exists: measured 2026-11-02 on four contiguous finalized
|
|
5
|
+
// mainnet blocks, the two token programs carry 5,402 of 12,572 watched
|
|
6
|
+
// payloads (43%) and 99.7% of the "no interface" gap, and neither program
|
|
7
|
+
// answers at either pullable address. The upstream Rust interface crates ARE
|
|
8
|
+
// their interface, so the wire table is derived from the vendored crates by
|
|
9
|
+
// src/token-interface.mjs and committed as a reviewable artifact
|
|
10
|
+
// (interface/token-<program>.json).
|
|
11
|
+
//
|
|
12
|
+
// Ground truth chain, every row derived and then VERIFIED, not typed:
|
|
13
|
+
// 1. INTERFACE solana-program/token@737788ff interface/src/instruction.rs
|
|
14
|
+
// solana-program/token-2022@5251dcc0 interface/src/instruction.rs
|
|
15
|
+
// DERIVED by tools/token-interface.mjs into
|
|
16
|
+
// interface/token-<program>.json. This module reads the
|
|
17
|
+
// artifact at load and re-hashes the vendored Rust against the
|
|
18
|
+
// sha256 the artifact claims; a mismatch REFUSES the load
|
|
19
|
+
// instead of decoding from it — the same two-check contract
|
|
20
|
+
// as src/native.mjs (fresh derivation + artifact equality).
|
|
21
|
+
// 2. WIRE the pack()/unpack() pairs in those crates: tag = the
|
|
22
|
+
// integer upstream writes and matches, byte order = the pack
|
|
23
|
+
// arm's write sequence, COption = upstream's own
|
|
24
|
+
// pack_pubkey_option/pack_u64_option (1 tag byte + payload).
|
|
25
|
+
// 3. RUNTIME the same pin's processor dispatch: an unknown tag is
|
|
26
|
+
// InvalidInstruction, so an unknown selector here is a
|
|
27
|
+
// refusal, not a guess.
|
|
28
|
+
//
|
|
29
|
+
// What is deliberately NOT claimed: extension sub-instructions (Token-2022
|
|
30
|
+
// tags 26/27/28/30/33/34/36/37/39/40/41/42/43/44/46 dispatch into extension
|
|
31
|
+
// processors on input[1..]) are named at the top level only, and their
|
|
32
|
+
// sub-instruction bytes are reported as trailing. Token-2022's tag 255
|
|
33
|
+
// (Batch) unpacks upstream but the processor refuses it.
|
|
34
|
+
//
|
|
35
|
+
// The token-metadata FALLBACK (spl-token-metadata-interface, 8-byte
|
|
36
|
+
// discriminators) is now covered, because Token-2022's own processor falls
|
|
37
|
+
// through to it: `decode_instruction_type(input)` reads ONE byte, and when
|
|
38
|
+
// that byte is not a PodTokenInstruction variant the processor tries
|
|
39
|
+
// `TokenMetadataInstruction::unpack(input)` before giving up. A payload
|
|
40
|
+
// starting with 0xd2/0xdd/0xea/0xd7/0xfa is therefore an instruction this
|
|
41
|
+
// program EXECUTES, and refusing it as "unknown-selector" would have been a
|
|
42
|
+
// true statement about the token table and a false one about the program.
|
|
43
|
+
//
|
|
44
|
+
// The fallback is Token-2022 ONLY (legacy SPL Token's processor has no such
|
|
45
|
+
// branch) and it sits EXACTLY where upstream puts it: the 1-byte token
|
|
46
|
+
// selector is consulted FIRST, and only a payload whose first byte is not a
|
|
47
|
+
// token variant reaches the metadata table. Upstream does not fall through
|
|
48
|
+
// to metadata when a token variant is recognized but its body is bad, so
|
|
49
|
+
// neither does this module.
|
|
50
|
+
//
|
|
51
|
+
// Strictness mirrors src/native.mjs: an unknown selector, a truncated
|
|
52
|
+
// payload, an invalid option tag or an unknown authority byte is a refusal
|
|
53
|
+
// with a named reason — never a confident wrong label.
|
|
54
|
+
|
|
55
|
+
import { readFileSync } from 'node:fs';
|
|
56
|
+
import path from 'node:path';
|
|
57
|
+
import { fileURLToPath } from 'node:url';
|
|
58
|
+
import { decodeBase58, encodeBase58 } from './base58.mjs';
|
|
59
|
+
import {
|
|
60
|
+
deriveTokenInterface, TOKEN_SPECS, vendoredTokenHash,
|
|
61
|
+
SPL_TOKEN_PROGRAM, SPL_TOKEN_2022_PROGRAM,
|
|
62
|
+
} from './token-interface.mjs';
|
|
63
|
+
import {
|
|
64
|
+
decodeTokenMetadataInstruction, encodeTokenMetadataInstruction,
|
|
65
|
+
tokenMetadataProvenance, tokenMetadataNames, TokenMetadataCodecError,
|
|
66
|
+
TOKEN_METADATA_PROGRAM,
|
|
67
|
+
} from './token-metadata.mjs';
|
|
68
|
+
|
|
69
|
+
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
70
|
+
const ROOT = path.resolve(HERE, '..');
|
|
71
|
+
|
|
72
|
+
export { SPL_TOKEN_PROGRAM, SPL_TOKEN_2022_PROGRAM };
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Load one token interface: DERIVE it from the vendored upstream Rust, then
|
|
76
|
+
* require the committed artifact to agree with the derivation byte for byte.
|
|
77
|
+
*
|
|
78
|
+
* Two checks, deliberately (the contract src/native.mjs already proved):
|
|
79
|
+
* - the derivation runs against the vendored bytes themselves, so a swapped
|
|
80
|
+
* or edited source file cannot silently change the table it describes;
|
|
81
|
+
* - the committed artifact must equal that derivation, so a tampered or
|
|
82
|
+
* stale artifact refuses the load instead of decoding from it.
|
|
83
|
+
*/
|
|
84
|
+
function loadInterface(spec) {
|
|
85
|
+
const artifactPath = path.join(ROOT, 'interface', `token-${spec.program}.json`);
|
|
86
|
+
const derived = deriveTokenInterface(spec);
|
|
87
|
+
let current;
|
|
88
|
+
try {
|
|
89
|
+
current = readFileSync(artifactPath, 'utf8');
|
|
90
|
+
} catch (error) {
|
|
91
|
+
throw new Error(`token interface artifact for ${spec.label} is missing: ${error.message}`);
|
|
92
|
+
}
|
|
93
|
+
const expected = `${JSON.stringify(derived, null, 2)}\n`;
|
|
94
|
+
if (current !== expected) {
|
|
95
|
+
throw new Error(
|
|
96
|
+
`token interface for ${spec.label} refuses to load: interface/token-${spec.program}.json does not equal ` +
|
|
97
|
+
`a fresh derivation of ${derived.source.file} (${derived.source.fileSha256}, hashed now: ${vendoredTokenHash(spec)})`,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
if (!derived.instructions.length || !derived.selector?.width) {
|
|
101
|
+
throw new Error(`token interface artifact for ${spec.label} is malformed`);
|
|
102
|
+
}
|
|
103
|
+
return derived;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const ARTIFACTS = new Map(TOKEN_SPECS.map((spec) => [spec.program, loadInterface(spec)]));
|
|
107
|
+
const AUTHORITY = new Map(TOKEN_SPECS.map((spec) => [
|
|
108
|
+
spec.program,
|
|
109
|
+
new Map(ARTIFACTS.get(spec.program).authorityType.map((entry) => [entry.byte, entry.name])),
|
|
110
|
+
]));
|
|
111
|
+
const AUTHORITY_NAME = new Map(TOKEN_SPECS.map((spec) => [
|
|
112
|
+
spec.program,
|
|
113
|
+
new Map(ARTIFACTS.get(spec.program).authorityType.map((entry) => [entry.name, entry.byte])),
|
|
114
|
+
]));
|
|
115
|
+
|
|
116
|
+
export class TokenCodecError extends Error {
|
|
117
|
+
constructor(reason, detail) {
|
|
118
|
+
super(detail ? `${reason}: ${detail}` : reason);
|
|
119
|
+
this.name = 'TokenCodecError';
|
|
120
|
+
this.reason = reason;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Whether `program` is one of the token programs this module owns. */
|
|
125
|
+
export function isTokenProgram(program) {
|
|
126
|
+
return ARTIFACTS.has(program);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Whether `program`'s processor falls through to the token-metadata table.
|
|
131
|
+
*
|
|
132
|
+
* Token-2022's processor does (`decode_instruction_type` fails ->
|
|
133
|
+
* `TokenMetadataInstruction::unpack`); legacy SPL Token has no such branch.
|
|
134
|
+
*/
|
|
135
|
+
export function hasTokenMetadataFallback(program) {
|
|
136
|
+
return program === TOKEN_METADATA_PROGRAM;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Program ids this module can decode, for callers that enumerate. */
|
|
140
|
+
export function tokenPrograms() {
|
|
141
|
+
return [...ARTIFACTS.keys()];
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Provenance for one token program, or null. */
|
|
145
|
+
export function tokenProvenance(program) {
|
|
146
|
+
const artifact = ARTIFACTS.get(program);
|
|
147
|
+
if (!artifact) return null;
|
|
148
|
+
return {
|
|
149
|
+
label: artifact.label,
|
|
150
|
+
interface: `${artifact.source.repo}@${artifact.source.pin.slice(0, 8)} ${artifact.source.file} TokenInstruction (explicit tags)`,
|
|
151
|
+
encoding: artifact.argEncoding,
|
|
152
|
+
runtime: artifact.runtime,
|
|
153
|
+
variants: artifact.instructions.length,
|
|
154
|
+
// Token-2022's processor falls through to the token-metadata crate; the
|
|
155
|
+
// fallback has its OWN vendored source, artifact and wire rules.
|
|
156
|
+
...(hasTokenMetadataFallback(program) ? { fallback: tokenMetadataProvenance() } : {}),
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function readLe(bytes, offset, size) {
|
|
161
|
+
let value = 0n;
|
|
162
|
+
for (let index = size - 1; index >= 0; index--) value = (value << 8n) | BigInt(bytes[offset + index]);
|
|
163
|
+
return value;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function u64ToNumber(value, what) {
|
|
167
|
+
if (value > BigInt(Number.MAX_SAFE_INTEGER)) throw Error(`${what} exceeds Number.MAX_SAFE_INTEGER`);
|
|
168
|
+
return Number(value);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Decode one arg. `trailing` types (utf8_rest, u16_vec, bytes) consume the
|
|
173
|
+
* REST of the payload and must be the last arg — which is exactly how
|
|
174
|
+
* upstream writes them, so the check below is upstream's own shape.
|
|
175
|
+
*/
|
|
176
|
+
function decodeArg(type, bytes, offset, what) {
|
|
177
|
+
if (type === 'u8') {
|
|
178
|
+
if (offset + 1 > bytes.length) throw new TokenCodecError('truncated-u8', `${what}: truncated u8 at ${offset}`);
|
|
179
|
+
return { value: bytes[offset], size: 1 };
|
|
180
|
+
}
|
|
181
|
+
if (type === 'u16') {
|
|
182
|
+
if (offset + 2 > bytes.length) throw new TokenCodecError('truncated-u16', `${what}: truncated u16 at ${offset}`);
|
|
183
|
+
return { value: bytes[offset] | (bytes[offset + 1] << 8), size: 2 };
|
|
184
|
+
}
|
|
185
|
+
if (type === 'u64') {
|
|
186
|
+
if (offset + 8 > bytes.length) throw new TokenCodecError('truncated-u64', `${what}: truncated u64 at ${offset}`);
|
|
187
|
+
return { value: readLe(bytes, offset, 8), size: 8 };
|
|
188
|
+
}
|
|
189
|
+
if (type === 'pubkey') {
|
|
190
|
+
if (offset + 32 > bytes.length) throw new TokenCodecError('truncated-pubkey', `${what}: truncated pubkey at ${offset}`);
|
|
191
|
+
return { value: encodeBase58(bytes.subarray(offset, offset + 32)), size: 32 };
|
|
192
|
+
}
|
|
193
|
+
if (type === 'coption_pubkey' || type === 'coption_u64') {
|
|
194
|
+
const width = type === 'coption_pubkey' ? 32 : 8;
|
|
195
|
+
if (offset >= bytes.length) throw new TokenCodecError('truncated-coption-tag', `${what}: truncated option tag at ${offset}`);
|
|
196
|
+
const tag = bytes[offset];
|
|
197
|
+
if (tag === 0) return { value: null, size: 1 };
|
|
198
|
+
if (tag !== 1) throw new TokenCodecError('invalid-coption-tag', `${what}: option tag ${tag} at ${offset} (upstream writes 0 or 1)`);
|
|
199
|
+
if (offset + 1 + width > bytes.length) throw new TokenCodecError('truncated-coption-payload', `${what}: truncated option payload at ${offset}`);
|
|
200
|
+
if (type === 'coption_pubkey') return { value: encodeBase58(bytes.subarray(offset + 1, offset + 33)), size: 33 };
|
|
201
|
+
return { value: readLe(bytes, offset + 1, 8), size: 9 };
|
|
202
|
+
}
|
|
203
|
+
if (type === 'authority_type') {
|
|
204
|
+
if (offset >= bytes.length) throw new TokenCodecError('truncated-authority-type', `${what}: truncated authority byte at ${offset}`);
|
|
205
|
+
const byte = bytes[offset];
|
|
206
|
+
return { value: byte, size: 1 };
|
|
207
|
+
}
|
|
208
|
+
if (type === 'utf8_rest') {
|
|
209
|
+
const rest = bytes.subarray(offset);
|
|
210
|
+
const text = Buffer.from(rest).toString('utf8');
|
|
211
|
+
if (Buffer.from(text, 'utf8').compare(rest) !== 0) {
|
|
212
|
+
throw new TokenCodecError('invalid-utf8', `${what}: remaining ${rest.length} byte(s) are not valid utf8`);
|
|
213
|
+
}
|
|
214
|
+
return { value: text, size: rest.length };
|
|
215
|
+
}
|
|
216
|
+
if (type === 'u16_vec') {
|
|
217
|
+
const rest = bytes.length - offset;
|
|
218
|
+
if (rest % 2 !== 0) throw new TokenCodecError('truncated-u16', `${what}: ${rest} trailing byte(s), not a whole number of u16`);
|
|
219
|
+
const out = [];
|
|
220
|
+
for (let at = offset; at < bytes.length; at += 2) out.push(bytes[at] | (bytes[at + 1] << 8));
|
|
221
|
+
return { value: out, size: rest };
|
|
222
|
+
}
|
|
223
|
+
if (type === 'bytes') {
|
|
224
|
+
return { value: Buffer.from(bytes.subarray(offset)).toString('hex'), size: bytes.length - offset };
|
|
225
|
+
}
|
|
226
|
+
throw new TokenCodecError('unknown-arg-type', `unknown arg type ${type}`);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function encodeArg(type, value, what, program) {
|
|
230
|
+
if (type === 'u8') {
|
|
231
|
+
const n = typeof value === 'bigint' ? Number(value) : value;
|
|
232
|
+
if (!Number.isSafeInteger(n) || n < 0 || n > 0xff) throw Error(`${what}: not a u8`);
|
|
233
|
+
return Buffer.from([n]);
|
|
234
|
+
}
|
|
235
|
+
if (type === 'u16') {
|
|
236
|
+
const n = typeof value === 'bigint' ? Number(value) : value;
|
|
237
|
+
if (!Number.isSafeInteger(n) || n < 0 || n > 0xffff) throw Error(`${what}: not a u16`);
|
|
238
|
+
const out = Buffer.alloc(2);
|
|
239
|
+
out.writeUInt16LE(n);
|
|
240
|
+
return out;
|
|
241
|
+
}
|
|
242
|
+
if (type === 'u64') {
|
|
243
|
+
const v = typeof value === 'string' && /^-?\d+$/.test(value) ? BigInt(value)
|
|
244
|
+
: typeof value === 'number' ? BigInt(value)
|
|
245
|
+
: typeof value === 'bigint' ? value
|
|
246
|
+
: (() => { throw Error(`${what}: not a u64`); })();
|
|
247
|
+
if (v < 0n || v >= 1n << 64n) throw Error(`${what}: out of u64 range`);
|
|
248
|
+
const out = Buffer.alloc(8);
|
|
249
|
+
for (let index = 0; index < 8; index++) out[index] = Number((v >> BigInt(index * 8)) & 0xffn);
|
|
250
|
+
return out;
|
|
251
|
+
}
|
|
252
|
+
if (type === 'pubkey') {
|
|
253
|
+
const raw = Buffer.from(decodeBase58(String(value)));
|
|
254
|
+
if (raw.length !== 32) throw Error(`${what}: not a 32-byte pubkey`);
|
|
255
|
+
return raw;
|
|
256
|
+
}
|
|
257
|
+
if (type === 'coption_pubkey' || type === 'coption_u64') {
|
|
258
|
+
if (value === null || value === undefined) return Buffer.from([0]);
|
|
259
|
+
if (type === 'coption_pubkey') {
|
|
260
|
+
const raw = Buffer.from(decodeBase58(String(value)));
|
|
261
|
+
if (raw.length !== 32) throw Error(`${what}: not a 32-byte pubkey`);
|
|
262
|
+
return Buffer.concat([Buffer.from([1]), raw]);
|
|
263
|
+
}
|
|
264
|
+
const v = typeof value === 'string' && /^\d+$/.test(value) ? BigInt(value)
|
|
265
|
+
: typeof value === 'number' ? BigInt(value)
|
|
266
|
+
: typeof value === 'bigint' ? value
|
|
267
|
+
: (() => { throw Error(`${what}: not a u64`); })();
|
|
268
|
+
if (v < 0n || v >= 1n << 64n) throw Error(`${what}: out of u64 range`);
|
|
269
|
+
const out = Buffer.alloc(9);
|
|
270
|
+
out[0] = 1;
|
|
271
|
+
for (let index = 0; index < 8; index++) out[index + 1] = Number((v >> BigInt(index * 8)) & 0xffn);
|
|
272
|
+
return out;
|
|
273
|
+
}
|
|
274
|
+
if (type === 'authority_type') {
|
|
275
|
+
// The byte is the wire form; the NAME is a human label. Accept either,
|
|
276
|
+
// so a decoded instruction can be re-encoded without the caller having
|
|
277
|
+
// to look the byte up again.
|
|
278
|
+
let n;
|
|
279
|
+
if (typeof value === 'string') {
|
|
280
|
+
n = AUTHORITY_NAME.get(program)?.get(value);
|
|
281
|
+
if (n === undefined) throw Error(`${what}: unknown AuthorityType name ${value}`);
|
|
282
|
+
} else {
|
|
283
|
+
n = typeof value === 'bigint' ? Number(value) : value;
|
|
284
|
+
}
|
|
285
|
+
if (!Number.isSafeInteger(n) || n < 0 || n > 0xff) throw Error(`${what}: not an authority byte`);
|
|
286
|
+
return Buffer.from([n]);
|
|
287
|
+
}
|
|
288
|
+
if (type === 'utf8_rest') return Buffer.from(String(value), 'utf8');
|
|
289
|
+
if (type === 'u16_vec') {
|
|
290
|
+
const out = Buffer.alloc(value.length * 2);
|
|
291
|
+
value.forEach((entry, index) => out.writeUInt16LE(entry, index * 2));
|
|
292
|
+
return out;
|
|
293
|
+
}
|
|
294
|
+
if (type === 'bytes') return Buffer.from(String(value), 'hex');
|
|
295
|
+
throw Error(`unknown arg type ${type}`);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Decode one token instruction payload.
|
|
300
|
+
*
|
|
301
|
+
* Returns null with a NAMED REASON attached via `tokenRefusal` when the
|
|
302
|
+
* bytes cannot honestly be named — the tri-state matters: `program` is not a
|
|
303
|
+
* token program means the caller was never supposed to ask, `null` means the
|
|
304
|
+
* token program's own table refuses, a result means the bytes are named AND
|
|
305
|
+
* re-encodable.
|
|
306
|
+
*
|
|
307
|
+
* `trailing: 'report'` (default) returns the decoded instruction plus the
|
|
308
|
+
* byte count the table does not explain; 'error' refuses instead.
|
|
309
|
+
*/
|
|
310
|
+
export function decodeTokenInstruction(program, bytes, { trailing = 'report' } = {}) {
|
|
311
|
+
const artifact = ARTIFACTS.get(program);
|
|
312
|
+
if (!artifact) return { refused: 'not-token' };
|
|
313
|
+
if (!(bytes instanceof Uint8Array) || bytes.length === 0) {
|
|
314
|
+
throw new TokenCodecError('empty-payload', `${program}: no instruction data`);
|
|
315
|
+
}
|
|
316
|
+
const tag = bytes[0];
|
|
317
|
+
const entry = artifact.instructions.find((ix) => ix.tag === tag);
|
|
318
|
+
if (!entry) {
|
|
319
|
+
// Upstream's own dispatch order: the ONE-BYTE token selector is tried
|
|
320
|
+
// first, and only when it does not match does the processor fall through
|
|
321
|
+
// to `TokenMetadataInstruction::unpack(input)`. A payload whose first
|
|
322
|
+
// byte is not a token variant is therefore either a metadata instruction
|
|
323
|
+
// (8-byte discriminator, Token-2022 only) or a refusal — and it is never
|
|
324
|
+
// a token instruction this table skipped.
|
|
325
|
+
if (hasTokenMetadataFallback(program)) {
|
|
326
|
+
try {
|
|
327
|
+
return decodeTokenMetadataInstruction(program, bytes);
|
|
328
|
+
} catch (error) {
|
|
329
|
+
// The fallback raises its own error type; the token module's
|
|
330
|
+
// contract is one error type with a named reason, so the reason is
|
|
331
|
+
// preserved and the type is normalized here.
|
|
332
|
+
if (error instanceof TokenMetadataCodecError) {
|
|
333
|
+
throw new TokenCodecError(error.reason, error.message);
|
|
334
|
+
}
|
|
335
|
+
throw error;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
throw new TokenCodecError(
|
|
339
|
+
'unknown-selector',
|
|
340
|
+
`${program}: tag ${tag} is not in the pinned ${artifact.label} interface`,
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
const args = {};
|
|
344
|
+
let offset = 1;
|
|
345
|
+
const order = entry.args.map((spec, index) => ({ spec, index }));
|
|
346
|
+
const restKinds = new Set(['utf8_rest', 'u16_vec', 'bytes']);
|
|
347
|
+
for (let index = 0; index < entry.args.length; index++) {
|
|
348
|
+
const spec = entry.args[index];
|
|
349
|
+
const decoded = decodeArg(spec.type, bytes, offset, entry.name);
|
|
350
|
+
args[spec.name] = decoded.value;
|
|
351
|
+
offset += decoded.size;
|
|
352
|
+
if (restKinds.has(spec.type) && index !== entry.args.length - 1) {
|
|
353
|
+
throw new TokenCodecError('rest-arg-not-last', `${program}: ${entry.name} rest arg ${spec.name} is not last — upstream order violated`);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
void order;
|
|
357
|
+
const trailingBytes = bytes.length - offset;
|
|
358
|
+
if (trailingBytes > 0 && trailing === 'error') {
|
|
359
|
+
throw new TokenCodecError('trailing-bytes', `${program}: ${entry.name} explains ${offset} of ${bytes.length} byte(s)`);
|
|
360
|
+
}
|
|
361
|
+
const authorityByte = args.authority_type;
|
|
362
|
+
const authorityName = typeof authorityByte === 'number'
|
|
363
|
+
? AUTHORITY.get(program).get(authorityByte)
|
|
364
|
+
: null;
|
|
365
|
+
if (typeof authorityByte === 'number' && authorityName === undefined) {
|
|
366
|
+
throw new TokenCodecError('unknown-authority-type', `${program}: authority byte ${authorityByte} is not a declared AuthorityType variant`);
|
|
367
|
+
}
|
|
368
|
+
return {
|
|
369
|
+
name: entry.name,
|
|
370
|
+
tag,
|
|
371
|
+
selectorWidth: 1,
|
|
372
|
+
args,
|
|
373
|
+
authorityType: authorityName,
|
|
374
|
+
consumed: offset,
|
|
375
|
+
bytes: bytes.length,
|
|
376
|
+
trailing: { count: trailingBytes },
|
|
377
|
+
roundTripByteExact: encodeTokenInstruction(program, entry.name, args).equals(Buffer.from(bytes)),
|
|
378
|
+
scheme: 'native-token',
|
|
379
|
+
source: 'pinned-vendored-upstream',
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/** Why the last `decodeTokenInstruction` refused, or null if it did not. */
|
|
384
|
+
export function tokenRefusal(program, bytes) {
|
|
385
|
+
try {
|
|
386
|
+
const result = decodeTokenInstruction(program, bytes);
|
|
387
|
+
return result.refused ? result.refused : null;
|
|
388
|
+
} catch (error) {
|
|
389
|
+
return error instanceof TokenCodecError ? error.reason : `thrown: ${error.message}`;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/** Re-encode one token instruction from a name + args. Throws on mismatch. */
|
|
394
|
+
export function encodeTokenInstruction(program, name, values = {}) {
|
|
395
|
+
const artifact = ARTIFACTS.get(program);
|
|
396
|
+
if (!artifact) throw new TokenCodecError('not-token', program);
|
|
397
|
+
const entry = artifact.instructions.find((ix) => ix.name === name);
|
|
398
|
+
if (!entry) throw new TokenCodecError('unknown-variant', `${program}: ${name}`);
|
|
399
|
+
const chunks = [Buffer.from([entry.tag])];
|
|
400
|
+
for (const spec of entry.args) {
|
|
401
|
+
const value = values[spec.name];
|
|
402
|
+
if (value === undefined && spec.type !== 'coption_pubkey' && spec.type !== 'coption_u64') {
|
|
403
|
+
throw new TokenCodecError('missing-arg', `${name}.${spec.name}`);
|
|
404
|
+
}
|
|
405
|
+
chunks.push(encodeArg(spec.type, value, `${name}.${spec.name}`, program));
|
|
406
|
+
}
|
|
407
|
+
return Buffer.concat(chunks);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/** Names the table can claim for `program` (coverage callers). */
|
|
411
|
+
export function tokenNames(program) {
|
|
412
|
+
const artifact = ARTIFACTS.get(program);
|
|
413
|
+
if (!artifact) return [];
|
|
414
|
+
const base = artifact.instructions.map((entry) => entry.name);
|
|
415
|
+
// The processor's fallback branch is part of what the program accepts, so
|
|
416
|
+
// enumeration must include it — a name list that omits it would make a
|
|
417
|
+
// "known name" check refuse payloads the program executes.
|
|
418
|
+
return hasTokenMetadataFallback(program) ? [...base, ...tokenMetadataNames()] : base;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/** Whether `name` belongs to `program`'s token table (including the fallback). */
|
|
422
|
+
export function isTokenName(program, name) {
|
|
423
|
+
const artifact = ARTIFACTS.get(program);
|
|
424
|
+
if (!artifact) return false;
|
|
425
|
+
if (artifact.instructions.some((entry) => entry.name === name)) return true;
|
|
426
|
+
return hasTokenMetadataFallback(program) && tokenMetadataNames().includes(name);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/** AuthorityType byte -> name for `program` (exposed for callers/tests). */
|
|
430
|
+
export function authorityTypeName(program, byte) {
|
|
431
|
+
return AUTHORITY.get(program)?.get(byte) ?? null;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/** AuthorityType name -> byte for `program` (exposed for callers/tests). */
|
|
435
|
+
export function authorityTypeByte(program, name) {
|
|
436
|
+
return AUTHORITY_NAME.get(program)?.get(name) ?? null;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/** The AuthorityType pairs as the artifact records them. */
|
|
440
|
+
export function authorityTypes(program) {
|
|
441
|
+
const artifact = ARTIFACTS.get(program);
|
|
442
|
+
return artifact ? artifact.authorityType.map((entry) => ({ ...entry })) : [];
|
|
443
|
+
}
|