@thru/abi-utils 0.1.36

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.
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Derives the ABI seed bytes from a program account pubkey.
3
+ *
4
+ * Algorithm:
5
+ * 1. Get program account bytes (32 bytes)
6
+ * 2. Append "_abi_account" suffix (12 bytes)
7
+ * 3. SHA256 hash the combined 44 bytes
8
+ * 4. Return the 32-byte hash as the seed
9
+ *
10
+ * @param programAccountBytes - The 32-byte program account public key
11
+ * @returns 32-byte seed for PDA derivation
12
+ */
13
+ declare function deriveAbiSeed(programAccountBytes: Uint8Array): Promise<Uint8Array>;
14
+ /**
15
+ * Derives a program address (PDA) from a seed and program ID.
16
+ *
17
+ * This implements the Thru PDA derivation algorithm:
18
+ * 1. Combine seed + program ID bytes
19
+ * 2. SHA256 hash the combined bytes
20
+ * 3. Return as the derived address
21
+ *
22
+ * @param seed - The 32-byte seed
23
+ * @param programId - The program ID bytes (32 bytes)
24
+ * @param ephemeral - Whether to derive an ephemeral address (affects prefix byte)
25
+ * @returns The derived 32-byte address
26
+ */
27
+ declare function deriveProgramAddress(seed: Uint8Array, programId: Uint8Array, ephemeral?: boolean): Promise<Uint8Array>;
28
+ /**
29
+ * Derives the ABI account address for a given program account.
30
+ *
31
+ * @param programAccount - The program account address (ta-prefixed string or 32-byte array)
32
+ * @param abiManagerProgramId - The ABI manager program ID (ta-prefixed string or 32-byte array)
33
+ * @param ephemeral - Whether to derive an ephemeral address
34
+ * @returns The derived ABI account address as a ta-prefixed string
35
+ */
36
+ declare function deriveAbiAddress(programAccount: string | Uint8Array, abiManagerProgramId: string | Uint8Array, ephemeral?: boolean): Promise<string>;
37
+
38
+ /**
39
+ * ABI account state values
40
+ */
41
+ declare const ABI_STATE: {
42
+ readonly OPEN: 0;
43
+ readonly FINALIZED: 1;
44
+ };
45
+ type AbiAccountState = (typeof ABI_STATE)[keyof typeof ABI_STATE];
46
+ /**
47
+ * Parsed header from an ABI account's on-chain data
48
+ */
49
+ interface AbiAccountHeader {
50
+ /** The program meta account this ABI is associated with */
51
+ programMetaAccount: Uint8Array;
52
+ /** Revision number (incremented on each upgrade) */
53
+ revision: bigint;
54
+ /** Account state: OPEN (0) or FINALIZED (1) */
55
+ state: AbiAccountState;
56
+ /** Size of the ABI content in bytes */
57
+ contentSize: number;
58
+ }
59
+ /**
60
+ * Full ABI account data including parsed YAML content
61
+ */
62
+ interface AbiAccountData extends AbiAccountHeader {
63
+ /** Raw ABI YAML content bytes */
64
+ contentBytes: Uint8Array;
65
+ /** ABI YAML content as string */
66
+ content: string;
67
+ }
68
+
69
+ /**
70
+ * Size of the ABI account header in bytes:
71
+ * - 32 bytes: program_meta_acc (pubkey)
72
+ * - 8 bytes: revision (u64)
73
+ * - 1 byte: state (u8)
74
+ * - 4 bytes: content_sz (u32)
75
+ * Total: 45 bytes
76
+ */
77
+ declare const ABI_ACCOUNT_HEADER_SIZE: number;
78
+ /**
79
+ * Parses the header from ABI account data.
80
+ *
81
+ * @param data - Raw account data bytes
82
+ * @returns Parsed header
83
+ * @throws If data is too small for the header
84
+ */
85
+ declare function parseAbiAccountHeader(data: Uint8Array): AbiAccountHeader;
86
+ /**
87
+ * Parses the full ABI account data including content.
88
+ *
89
+ * @param data - Raw account data bytes
90
+ * @returns Parsed header and content
91
+ * @throws If data is too small or content size exceeds available data
92
+ */
93
+ declare function parseAbiAccountData(data: Uint8Array): AbiAccountData;
94
+
95
+ export { ABI_ACCOUNT_HEADER_SIZE, ABI_STATE, type AbiAccountData, type AbiAccountHeader, type AbiAccountState, deriveAbiAddress, deriveAbiSeed, deriveProgramAddress, parseAbiAccountData, parseAbiAccountHeader };
package/dist/index.js ADDED
@@ -0,0 +1,94 @@
1
+ // src/derivation.ts
2
+ import { decodeAddress, encodeAddress } from "@thru/helpers";
3
+ var ABI_SEED_SUFFIX = "_abi_account";
4
+ async function deriveAbiSeed(programAccountBytes) {
5
+ if (programAccountBytes.length !== 32) {
6
+ throw new Error(`Expected 32-byte program account, got ${programAccountBytes.length} bytes`);
7
+ }
8
+ const suffixBytes = new TextEncoder().encode(ABI_SEED_SUFFIX);
9
+ const combined = new Uint8Array(programAccountBytes.length + suffixBytes.length);
10
+ combined.set(programAccountBytes, 0);
11
+ combined.set(suffixBytes, programAccountBytes.length);
12
+ const hashBuffer = await crypto.subtle.digest("SHA-256", combined);
13
+ return new Uint8Array(hashBuffer);
14
+ }
15
+ async function deriveProgramAddress(seed, programId, ephemeral = false) {
16
+ if (seed.length !== 32) {
17
+ throw new Error(`Expected 32-byte seed, got ${seed.length} bytes`);
18
+ }
19
+ if (programId.length !== 32) {
20
+ throw new Error(`Expected 32-byte program ID, got ${programId.length} bytes`);
21
+ }
22
+ const combined = new Uint8Array(seed.length + programId.length);
23
+ combined.set(seed, 0);
24
+ combined.set(programId, seed.length);
25
+ const hashBuffer = await crypto.subtle.digest("SHA-256", combined);
26
+ const result = new Uint8Array(hashBuffer);
27
+ if (ephemeral) {
28
+ result[0] = result[0] | 128;
29
+ } else {
30
+ result[0] = result[0] & 127;
31
+ }
32
+ return result;
33
+ }
34
+ async function deriveAbiAddress(programAccount, abiManagerProgramId, ephemeral = false) {
35
+ const programAccountBytes = typeof programAccount === "string" ? decodeAddress(programAccount) : programAccount;
36
+ const abiManagerBytes = typeof abiManagerProgramId === "string" ? decodeAddress(abiManagerProgramId) : abiManagerProgramId;
37
+ const seed = await deriveAbiSeed(programAccountBytes);
38
+ const abiAccountBytes = await deriveProgramAddress(seed, abiManagerBytes, ephemeral);
39
+ return encodeAddress(abiAccountBytes);
40
+ }
41
+
42
+ // src/types.ts
43
+ var ABI_STATE = {
44
+ OPEN: 0,
45
+ FINALIZED: 1
46
+ };
47
+
48
+ // src/parser.ts
49
+ var ABI_ACCOUNT_HEADER_SIZE = 32 + 8 + 1 + 4;
50
+ function parseAbiAccountHeader(data) {
51
+ if (data.length < ABI_ACCOUNT_HEADER_SIZE) {
52
+ throw new Error(
53
+ `ABI account data too small: ${data.length} bytes, expected at least ${ABI_ACCOUNT_HEADER_SIZE}`
54
+ );
55
+ }
56
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
57
+ const programMetaAccount = data.slice(0, 32);
58
+ const revision = view.getBigUint64(32, true);
59
+ const stateRaw = data[40];
60
+ const state = stateRaw === ABI_STATE.FINALIZED ? ABI_STATE.FINALIZED : ABI_STATE.OPEN;
61
+ const contentSize = view.getUint32(41, true);
62
+ return {
63
+ programMetaAccount,
64
+ revision,
65
+ state,
66
+ contentSize
67
+ };
68
+ }
69
+ function parseAbiAccountData(data) {
70
+ const header = parseAbiAccountHeader(data);
71
+ const expectedSize = ABI_ACCOUNT_HEADER_SIZE + header.contentSize;
72
+ if (data.length < expectedSize) {
73
+ throw new Error(
74
+ `ABI account data incomplete: ${data.length} bytes, expected ${expectedSize} (header + ${header.contentSize} content bytes)`
75
+ );
76
+ }
77
+ const contentBytes = data.slice(ABI_ACCOUNT_HEADER_SIZE, ABI_ACCOUNT_HEADER_SIZE + header.contentSize);
78
+ const content = new TextDecoder().decode(contentBytes);
79
+ return {
80
+ ...header,
81
+ contentBytes,
82
+ content
83
+ };
84
+ }
85
+ export {
86
+ ABI_ACCOUNT_HEADER_SIZE,
87
+ ABI_STATE,
88
+ deriveAbiAddress,
89
+ deriveAbiSeed,
90
+ deriveProgramAddress,
91
+ parseAbiAccountData,
92
+ parseAbiAccountHeader
93
+ };
94
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/derivation.ts","../src/types.ts","../src/parser.ts"],"sourcesContent":["import { decodeAddress, encodeAddress } from \"@thru/helpers\";\n\n/**\n * Suffix appended to program account bytes for ABI seed derivation\n */\nconst ABI_SEED_SUFFIX = \"_abi_account\";\n\n/**\n * Derives the ABI seed bytes from a program account pubkey.\n *\n * Algorithm:\n * 1. Get program account bytes (32 bytes)\n * 2. Append \"_abi_account\" suffix (12 bytes)\n * 3. SHA256 hash the combined 44 bytes\n * 4. Return the 32-byte hash as the seed\n *\n * @param programAccountBytes - The 32-byte program account public key\n * @returns 32-byte seed for PDA derivation\n */\nexport async function deriveAbiSeed(programAccountBytes: Uint8Array): Promise<Uint8Array> {\n if (programAccountBytes.length !== 32) {\n throw new Error(`Expected 32-byte program account, got ${programAccountBytes.length} bytes`);\n }\n\n // Combine program account bytes with suffix\n const suffixBytes = new TextEncoder().encode(ABI_SEED_SUFFIX);\n const combined = new Uint8Array(programAccountBytes.length + suffixBytes.length);\n combined.set(programAccountBytes, 0);\n combined.set(suffixBytes, programAccountBytes.length);\n\n // SHA256 hash\n const hashBuffer = await crypto.subtle.digest(\"SHA-256\", combined);\n return new Uint8Array(hashBuffer);\n}\n\n/**\n * Derives a program address (PDA) from a seed and program ID.\n *\n * This implements the Thru PDA derivation algorithm:\n * 1. Combine seed + program ID bytes\n * 2. SHA256 hash the combined bytes\n * 3. Return as the derived address\n *\n * @param seed - The 32-byte seed\n * @param programId - The program ID bytes (32 bytes)\n * @param ephemeral - Whether to derive an ephemeral address (affects prefix byte)\n * @returns The derived 32-byte address\n */\nexport async function deriveProgramAddress(\n seed: Uint8Array,\n programId: Uint8Array,\n ephemeral: boolean = false\n): Promise<Uint8Array> {\n if (seed.length !== 32) {\n throw new Error(`Expected 32-byte seed, got ${seed.length} bytes`);\n }\n if (programId.length !== 32) {\n throw new Error(`Expected 32-byte program ID, got ${programId.length} bytes`);\n }\n\n // Combine seed + program ID\n const combined = new Uint8Array(seed.length + programId.length);\n combined.set(seed, 0);\n combined.set(programId, seed.length);\n\n // SHA256 hash\n const hashBuffer = await crypto.subtle.digest(\"SHA-256\", combined);\n const result = new Uint8Array(hashBuffer);\n\n // Set the ephemeral bit in the first byte if needed\n // Ephemeral addresses have bit 0x80 set in the first byte\n if (ephemeral) {\n result[0] = result[0] | 0x80;\n } else {\n result[0] = result[0] & 0x7f;\n }\n\n return result;\n}\n\n/**\n * Derives the ABI account address for a given program account.\n *\n * @param programAccount - The program account address (ta-prefixed string or 32-byte array)\n * @param abiManagerProgramId - The ABI manager program ID (ta-prefixed string or 32-byte array)\n * @param ephemeral - Whether to derive an ephemeral address\n * @returns The derived ABI account address as a ta-prefixed string\n */\nexport async function deriveAbiAddress(\n programAccount: string | Uint8Array,\n abiManagerProgramId: string | Uint8Array,\n ephemeral: boolean = false\n): Promise<string> {\n // Convert string addresses to bytes\n const programAccountBytes = typeof programAccount === \"string\"\n ? decodeAddress(programAccount)\n : programAccount;\n\n const abiManagerBytes = typeof abiManagerProgramId === \"string\"\n ? decodeAddress(abiManagerProgramId)\n : abiManagerProgramId;\n\n // Derive the seed\n const seed = await deriveAbiSeed(programAccountBytes);\n\n // Derive the PDA\n const abiAccountBytes = await deriveProgramAddress(seed, abiManagerBytes, ephemeral);\n\n // Encode as ta-prefixed address\n return encodeAddress(abiAccountBytes);\n}\n","/**\n * ABI account state values\n */\nexport const ABI_STATE = {\n OPEN: 0x00,\n FINALIZED: 0x01,\n} as const;\n\nexport type AbiAccountState = (typeof ABI_STATE)[keyof typeof ABI_STATE];\n\n/**\n * Parsed header from an ABI account's on-chain data\n */\nexport interface AbiAccountHeader {\n /** The program meta account this ABI is associated with */\n programMetaAccount: Uint8Array;\n /** Revision number (incremented on each upgrade) */\n revision: bigint;\n /** Account state: OPEN (0) or FINALIZED (1) */\n state: AbiAccountState;\n /** Size of the ABI content in bytes */\n contentSize: number;\n}\n\n/**\n * Full ABI account data including parsed YAML content\n */\nexport interface AbiAccountData extends AbiAccountHeader {\n /** Raw ABI YAML content bytes */\n contentBytes: Uint8Array;\n /** ABI YAML content as string */\n content: string;\n}\n","import type { AbiAccountHeader, AbiAccountData, AbiAccountState } from \"./types\";\nimport { ABI_STATE } from \"./types\";\n\n/**\n * Size of the ABI account header in bytes:\n * - 32 bytes: program_meta_acc (pubkey)\n * - 8 bytes: revision (u64)\n * - 1 byte: state (u8)\n * - 4 bytes: content_sz (u32)\n * Total: 45 bytes\n */\nexport const ABI_ACCOUNT_HEADER_SIZE = 32 + 8 + 1 + 4;\n\n/**\n * Parses the header from ABI account data.\n *\n * @param data - Raw account data bytes\n * @returns Parsed header\n * @throws If data is too small for the header\n */\nexport function parseAbiAccountHeader(data: Uint8Array): AbiAccountHeader {\n if (data.length < ABI_ACCOUNT_HEADER_SIZE) {\n throw new Error(\n `ABI account data too small: ${data.length} bytes, expected at least ${ABI_ACCOUNT_HEADER_SIZE}`\n );\n }\n\n const view = new DataView(data.buffer, data.byteOffset, data.byteLength);\n\n // program_meta_acc: bytes 0-31 (32 bytes)\n const programMetaAccount = data.slice(0, 32);\n\n // revision: bytes 32-39 (8 bytes, little-endian u64)\n const revision = view.getBigUint64(32, true);\n\n // state: byte 40 (1 byte)\n const stateRaw = data[40];\n const state: AbiAccountState = stateRaw === ABI_STATE.FINALIZED\n ? ABI_STATE.FINALIZED\n : ABI_STATE.OPEN;\n\n // content_sz: bytes 41-44 (4 bytes, little-endian u32)\n const contentSize = view.getUint32(41, true);\n\n return {\n programMetaAccount,\n revision,\n state,\n contentSize,\n };\n}\n\n/**\n * Parses the full ABI account data including content.\n *\n * @param data - Raw account data bytes\n * @returns Parsed header and content\n * @throws If data is too small or content size exceeds available data\n */\nexport function parseAbiAccountData(data: Uint8Array): AbiAccountData {\n const header = parseAbiAccountHeader(data);\n\n const expectedSize = ABI_ACCOUNT_HEADER_SIZE + header.contentSize;\n if (data.length < expectedSize) {\n throw new Error(\n `ABI account data incomplete: ${data.length} bytes, expected ${expectedSize} (header + ${header.contentSize} content bytes)`\n );\n }\n\n const contentBytes = data.slice(ABI_ACCOUNT_HEADER_SIZE, ABI_ACCOUNT_HEADER_SIZE + header.contentSize);\n const content = new TextDecoder().decode(contentBytes);\n\n return {\n ...header,\n contentBytes,\n content,\n };\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@thru/abi-utils",
3
+ "version": "0.1.36",
4
+ "type": "module",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./dist/index.js",
10
+ "types": "./dist/index.d.ts"
11
+ }
12
+ },
13
+ "dependencies": {
14
+ "@thru/helpers": "0.1.36"
15
+ },
16
+ "devDependencies": {
17
+ "@types/node": "^24.10.1",
18
+ "tsup": "^8.5.0",
19
+ "typescript": "^5.9.3",
20
+ "vitest": "^3.2.4"
21
+ },
22
+ "scripts": {
23
+ "build": "tsup",
24
+ "dev": "tsup --watch",
25
+ "test": "vitest run",
26
+ "test:watch": "vitest watch",
27
+ "clean": "rm -rf dist"
28
+ }
29
+ }
@@ -0,0 +1,111 @@
1
+ import { decodeAddress, encodeAddress } from "@thru/helpers";
2
+
3
+ /**
4
+ * Suffix appended to program account bytes for ABI seed derivation
5
+ */
6
+ const ABI_SEED_SUFFIX = "_abi_account";
7
+
8
+ /**
9
+ * Derives the ABI seed bytes from a program account pubkey.
10
+ *
11
+ * Algorithm:
12
+ * 1. Get program account bytes (32 bytes)
13
+ * 2. Append "_abi_account" suffix (12 bytes)
14
+ * 3. SHA256 hash the combined 44 bytes
15
+ * 4. Return the 32-byte hash as the seed
16
+ *
17
+ * @param programAccountBytes - The 32-byte program account public key
18
+ * @returns 32-byte seed for PDA derivation
19
+ */
20
+ export async function deriveAbiSeed(programAccountBytes: Uint8Array): Promise<Uint8Array> {
21
+ if (programAccountBytes.length !== 32) {
22
+ throw new Error(`Expected 32-byte program account, got ${programAccountBytes.length} bytes`);
23
+ }
24
+
25
+ // Combine program account bytes with suffix
26
+ const suffixBytes = new TextEncoder().encode(ABI_SEED_SUFFIX);
27
+ const combined = new Uint8Array(programAccountBytes.length + suffixBytes.length);
28
+ combined.set(programAccountBytes, 0);
29
+ combined.set(suffixBytes, programAccountBytes.length);
30
+
31
+ // SHA256 hash
32
+ const hashBuffer = await crypto.subtle.digest("SHA-256", combined);
33
+ return new Uint8Array(hashBuffer);
34
+ }
35
+
36
+ /**
37
+ * Derives a program address (PDA) from a seed and program ID.
38
+ *
39
+ * This implements the Thru PDA derivation algorithm:
40
+ * 1. Combine seed + program ID bytes
41
+ * 2. SHA256 hash the combined bytes
42
+ * 3. Return as the derived address
43
+ *
44
+ * @param seed - The 32-byte seed
45
+ * @param programId - The program ID bytes (32 bytes)
46
+ * @param ephemeral - Whether to derive an ephemeral address (affects prefix byte)
47
+ * @returns The derived 32-byte address
48
+ */
49
+ export async function deriveProgramAddress(
50
+ seed: Uint8Array,
51
+ programId: Uint8Array,
52
+ ephemeral: boolean = false
53
+ ): Promise<Uint8Array> {
54
+ if (seed.length !== 32) {
55
+ throw new Error(`Expected 32-byte seed, got ${seed.length} bytes`);
56
+ }
57
+ if (programId.length !== 32) {
58
+ throw new Error(`Expected 32-byte program ID, got ${programId.length} bytes`);
59
+ }
60
+
61
+ // Combine seed + program ID
62
+ const combined = new Uint8Array(seed.length + programId.length);
63
+ combined.set(seed, 0);
64
+ combined.set(programId, seed.length);
65
+
66
+ // SHA256 hash
67
+ const hashBuffer = await crypto.subtle.digest("SHA-256", combined);
68
+ const result = new Uint8Array(hashBuffer);
69
+
70
+ // Set the ephemeral bit in the first byte if needed
71
+ // Ephemeral addresses have bit 0x80 set in the first byte
72
+ if (ephemeral) {
73
+ result[0] = result[0] | 0x80;
74
+ } else {
75
+ result[0] = result[0] & 0x7f;
76
+ }
77
+
78
+ return result;
79
+ }
80
+
81
+ /**
82
+ * Derives the ABI account address for a given program account.
83
+ *
84
+ * @param programAccount - The program account address (ta-prefixed string or 32-byte array)
85
+ * @param abiManagerProgramId - The ABI manager program ID (ta-prefixed string or 32-byte array)
86
+ * @param ephemeral - Whether to derive an ephemeral address
87
+ * @returns The derived ABI account address as a ta-prefixed string
88
+ */
89
+ export async function deriveAbiAddress(
90
+ programAccount: string | Uint8Array,
91
+ abiManagerProgramId: string | Uint8Array,
92
+ ephemeral: boolean = false
93
+ ): Promise<string> {
94
+ // Convert string addresses to bytes
95
+ const programAccountBytes = typeof programAccount === "string"
96
+ ? decodeAddress(programAccount)
97
+ : programAccount;
98
+
99
+ const abiManagerBytes = typeof abiManagerProgramId === "string"
100
+ ? decodeAddress(abiManagerProgramId)
101
+ : abiManagerProgramId;
102
+
103
+ // Derive the seed
104
+ const seed = await deriveAbiSeed(programAccountBytes);
105
+
106
+ // Derive the PDA
107
+ const abiAccountBytes = await deriveProgramAddress(seed, abiManagerBytes, ephemeral);
108
+
109
+ // Encode as ta-prefixed address
110
+ return encodeAddress(abiAccountBytes);
111
+ }
package/src/index.ts ADDED
@@ -0,0 +1,4 @@
1
+ export { deriveAbiAddress, deriveAbiSeed, deriveProgramAddress } from "./derivation";
2
+ export type { AbiAccountHeader, AbiAccountData, AbiAccountState } from "./types";
3
+ export { ABI_STATE } from "./types";
4
+ export { parseAbiAccountHeader, parseAbiAccountData, ABI_ACCOUNT_HEADER_SIZE } from "./parser";
package/src/parser.ts ADDED
@@ -0,0 +1,78 @@
1
+ import type { AbiAccountHeader, AbiAccountData, AbiAccountState } from "./types";
2
+ import { ABI_STATE } from "./types";
3
+
4
+ /**
5
+ * Size of the ABI account header in bytes:
6
+ * - 32 bytes: program_meta_acc (pubkey)
7
+ * - 8 bytes: revision (u64)
8
+ * - 1 byte: state (u8)
9
+ * - 4 bytes: content_sz (u32)
10
+ * Total: 45 bytes
11
+ */
12
+ export const ABI_ACCOUNT_HEADER_SIZE = 32 + 8 + 1 + 4;
13
+
14
+ /**
15
+ * Parses the header from ABI account data.
16
+ *
17
+ * @param data - Raw account data bytes
18
+ * @returns Parsed header
19
+ * @throws If data is too small for the header
20
+ */
21
+ export function parseAbiAccountHeader(data: Uint8Array): AbiAccountHeader {
22
+ if (data.length < ABI_ACCOUNT_HEADER_SIZE) {
23
+ throw new Error(
24
+ `ABI account data too small: ${data.length} bytes, expected at least ${ABI_ACCOUNT_HEADER_SIZE}`
25
+ );
26
+ }
27
+
28
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
29
+
30
+ // program_meta_acc: bytes 0-31 (32 bytes)
31
+ const programMetaAccount = data.slice(0, 32);
32
+
33
+ // revision: bytes 32-39 (8 bytes, little-endian u64)
34
+ const revision = view.getBigUint64(32, true);
35
+
36
+ // state: byte 40 (1 byte)
37
+ const stateRaw = data[40];
38
+ const state: AbiAccountState = stateRaw === ABI_STATE.FINALIZED
39
+ ? ABI_STATE.FINALIZED
40
+ : ABI_STATE.OPEN;
41
+
42
+ // content_sz: bytes 41-44 (4 bytes, little-endian u32)
43
+ const contentSize = view.getUint32(41, true);
44
+
45
+ return {
46
+ programMetaAccount,
47
+ revision,
48
+ state,
49
+ contentSize,
50
+ };
51
+ }
52
+
53
+ /**
54
+ * Parses the full ABI account data including content.
55
+ *
56
+ * @param data - Raw account data bytes
57
+ * @returns Parsed header and content
58
+ * @throws If data is too small or content size exceeds available data
59
+ */
60
+ export function parseAbiAccountData(data: Uint8Array): AbiAccountData {
61
+ const header = parseAbiAccountHeader(data);
62
+
63
+ const expectedSize = ABI_ACCOUNT_HEADER_SIZE + header.contentSize;
64
+ if (data.length < expectedSize) {
65
+ throw new Error(
66
+ `ABI account data incomplete: ${data.length} bytes, expected ${expectedSize} (header + ${header.contentSize} content bytes)`
67
+ );
68
+ }
69
+
70
+ const contentBytes = data.slice(ABI_ACCOUNT_HEADER_SIZE, ABI_ACCOUNT_HEADER_SIZE + header.contentSize);
71
+ const content = new TextDecoder().decode(contentBytes);
72
+
73
+ return {
74
+ ...header,
75
+ contentBytes,
76
+ content,
77
+ };
78
+ }
package/src/types.ts ADDED
@@ -0,0 +1,33 @@
1
+ /**
2
+ * ABI account state values
3
+ */
4
+ export const ABI_STATE = {
5
+ OPEN: 0x00,
6
+ FINALIZED: 0x01,
7
+ } as const;
8
+
9
+ export type AbiAccountState = (typeof ABI_STATE)[keyof typeof ABI_STATE];
10
+
11
+ /**
12
+ * Parsed header from an ABI account's on-chain data
13
+ */
14
+ export interface AbiAccountHeader {
15
+ /** The program meta account this ABI is associated with */
16
+ programMetaAccount: Uint8Array;
17
+ /** Revision number (incremented on each upgrade) */
18
+ revision: bigint;
19
+ /** Account state: OPEN (0) or FINALIZED (1) */
20
+ state: AbiAccountState;
21
+ /** Size of the ABI content in bytes */
22
+ contentSize: number;
23
+ }
24
+
25
+ /**
26
+ * Full ABI account data including parsed YAML content
27
+ */
28
+ export interface AbiAccountData extends AbiAccountHeader {
29
+ /** Raw ABI YAML content bytes */
30
+ contentBytes: Uint8Array;
31
+ /** ABI YAML content as string */
32
+ content: string;
33
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "strict": true,
7
+ "esModuleInterop": true,
8
+ "skipLibCheck": true,
9
+ "forceConsistentCasingInFileNames": true,
10
+ "declaration": true,
11
+ "declarationMap": true,
12
+ "outDir": "./dist",
13
+ "rootDir": "./src"
14
+ },
15
+ "include": ["src/**/*"],
16
+ "exclude": ["node_modules", "dist"]
17
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,9 @@
1
+ import { defineConfig } from "tsup";
2
+
3
+ export default defineConfig({
4
+ entry: ["src/index.ts"],
5
+ format: ["esm"],
6
+ dts: true,
7
+ clean: true,
8
+ sourcemap: true,
9
+ });
@@ -0,0 +1,7 @@
1
+ import { defineConfig } from "vitest/config";
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ passWithNoTests: true,
6
+ },
7
+ });