@thru/abi-utils 0.2.0 → 0.2.1
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/dist/index.d.ts +21 -33
- package/dist/index.js +52 -28
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
- package/src/derivation.ts +68 -73
- package/src/index.ts +8 -1
- package/src/parser.ts +4 -4
- package/src/types.ts +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,39 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
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
|
|
2
|
+
* Builds the ABI meta body for an official ABI (program account).
|
|
12
3
|
*/
|
|
13
|
-
declare function
|
|
4
|
+
declare function abiMetaBodyForProgram(programBytes: Uint8Array): Uint8Array;
|
|
14
5
|
/**
|
|
15
|
-
* Derives
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
*
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
*
|
|
24
|
-
* @param ephemeral - Whether to derive an ephemeral address (affects prefix byte)
|
|
25
|
-
* @returns The derived 32-byte address
|
|
6
|
+
* Derives the ABI meta seed from ABI meta kind + body.
|
|
7
|
+
*/
|
|
8
|
+
declare function deriveAbiMetaSeed(kind: number, body: Uint8Array): Promise<Uint8Array>;
|
|
9
|
+
/**
|
|
10
|
+
* Derives the ABI account seed from ABI meta kind + body.
|
|
11
|
+
*/
|
|
12
|
+
declare function deriveAbiAccountSeed(kind: number, body: Uint8Array): Promise<Uint8Array>;
|
|
13
|
+
/**
|
|
14
|
+
* Derives a program-defined account address (owner || ephemeral || seed).
|
|
26
15
|
*/
|
|
27
16
|
declare function deriveProgramAddress(seed: Uint8Array, programId: Uint8Array, ephemeral?: boolean): Promise<Uint8Array>;
|
|
28
17
|
/**
|
|
29
|
-
* Derives the ABI account address
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
*
|
|
34
|
-
* @returns The derived ABI account address as a ta-prefixed string
|
|
18
|
+
* Derives the ABI meta account address (as a ta-prefixed string).
|
|
19
|
+
*/
|
|
20
|
+
declare function deriveAbiMetaAddress(kind: number, body: Uint8Array, abiManagerProgramId: string | Uint8Array, ephemeral?: boolean): Promise<string>;
|
|
21
|
+
/**
|
|
22
|
+
* Derives the ABI account address (as a ta-prefixed string).
|
|
35
23
|
*/
|
|
36
|
-
declare function deriveAbiAddress(
|
|
24
|
+
declare function deriveAbiAddress(kind: number, body: Uint8Array, abiManagerProgramId: string | Uint8Array, ephemeral?: boolean): Promise<string>;
|
|
37
25
|
|
|
38
26
|
/**
|
|
39
27
|
* ABI account state values
|
|
@@ -47,8 +35,8 @@ type AbiAccountState = (typeof ABI_STATE)[keyof typeof ABI_STATE];
|
|
|
47
35
|
* Parsed header from an ABI account's on-chain data
|
|
48
36
|
*/
|
|
49
37
|
interface AbiAccountHeader {
|
|
50
|
-
/** The
|
|
51
|
-
|
|
38
|
+
/** The ABI meta account this ABI is associated with */
|
|
39
|
+
abiMetaAccount: Uint8Array;
|
|
52
40
|
/** Revision number (incremented on each upgrade) */
|
|
53
41
|
revision: bigint;
|
|
54
42
|
/** Account state: OPEN (0) or FINALIZED (1) */
|
|
@@ -68,7 +56,7 @@ interface AbiAccountData extends AbiAccountHeader {
|
|
|
68
56
|
|
|
69
57
|
/**
|
|
70
58
|
* Size of the ABI account header in bytes:
|
|
71
|
-
* - 32 bytes:
|
|
59
|
+
* - 32 bytes: abi_meta_acc (pubkey)
|
|
72
60
|
* - 8 bytes: revision (u64)
|
|
73
61
|
* - 1 byte: state (u8)
|
|
74
62
|
* - 4 bytes: content_sz (u32)
|
|
@@ -92,4 +80,4 @@ declare function parseAbiAccountHeader(data: Uint8Array): AbiAccountHeader;
|
|
|
92
80
|
*/
|
|
93
81
|
declare function parseAbiAccountData(data: Uint8Array): AbiAccountData;
|
|
94
82
|
|
|
95
|
-
export { ABI_ACCOUNT_HEADER_SIZE, ABI_STATE, type AbiAccountData, type AbiAccountHeader, type AbiAccountState, deriveAbiAddress,
|
|
83
|
+
export { ABI_ACCOUNT_HEADER_SIZE, ABI_STATE, type AbiAccountData, type AbiAccountHeader, type AbiAccountState, abiMetaBodyForProgram, deriveAbiAccountSeed, deriveAbiAddress, deriveAbiMetaAddress, deriveAbiMetaSeed, deriveProgramAddress, parseAbiAccountData, parseAbiAccountHeader };
|
package/dist/index.js
CHANGED
|
@@ -1,17 +1,42 @@
|
|
|
1
1
|
// src/derivation.ts
|
|
2
2
|
import { decodeAddress, encodeAddress } from "@thru/helpers";
|
|
3
|
-
var
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
var ABI_META_BODY_SIZE = 96;
|
|
4
|
+
var ABI_ACCOUNT_SUFFIX = "_abi_account";
|
|
5
|
+
var ABI_ACCOUNT_SUFFIX_BYTES = new TextEncoder().encode(ABI_ACCOUNT_SUFFIX);
|
|
6
|
+
function concatBytes(...chunks) {
|
|
7
|
+
const total = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
8
|
+
const out = new Uint8Array(total);
|
|
9
|
+
let offset = 0;
|
|
10
|
+
for (const chunk of chunks) {
|
|
11
|
+
out.set(chunk, offset);
|
|
12
|
+
offset += chunk.length;
|
|
7
13
|
}
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const hashBuffer = await crypto.subtle.digest("SHA-256", combined);
|
|
14
|
+
return out;
|
|
15
|
+
}
|
|
16
|
+
async function sha256Bytes(data) {
|
|
17
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
13
18
|
return new Uint8Array(hashBuffer);
|
|
14
19
|
}
|
|
20
|
+
function abiMetaBodyForProgram(programBytes) {
|
|
21
|
+
if (programBytes.length !== 32) {
|
|
22
|
+
throw new Error(`Expected 32-byte program account, got ${programBytes.length} bytes`);
|
|
23
|
+
}
|
|
24
|
+
const body = new Uint8Array(ABI_META_BODY_SIZE);
|
|
25
|
+
body.set(programBytes, 0);
|
|
26
|
+
return body;
|
|
27
|
+
}
|
|
28
|
+
async function deriveAbiMetaSeed(kind, body) {
|
|
29
|
+
if (body.length !== ABI_META_BODY_SIZE) {
|
|
30
|
+
throw new Error(`Expected ${ABI_META_BODY_SIZE}-byte ABI meta body, got ${body.length} bytes`);
|
|
31
|
+
}
|
|
32
|
+
return sha256Bytes(concatBytes(new Uint8Array([kind]), body));
|
|
33
|
+
}
|
|
34
|
+
async function deriveAbiAccountSeed(kind, body) {
|
|
35
|
+
if (body.length !== ABI_META_BODY_SIZE) {
|
|
36
|
+
throw new Error(`Expected ${ABI_META_BODY_SIZE}-byte ABI meta body, got ${body.length} bytes`);
|
|
37
|
+
}
|
|
38
|
+
return sha256Bytes(concatBytes(new Uint8Array([kind]), body, ABI_ACCOUNT_SUFFIX_BYTES));
|
|
39
|
+
}
|
|
15
40
|
async function deriveProgramAddress(seed, programId, ephemeral = false) {
|
|
16
41
|
if (seed.length !== 32) {
|
|
17
42
|
throw new Error(`Expected 32-byte seed, got ${seed.length} bytes`);
|
|
@@ -19,24 +44,20 @@ async function deriveProgramAddress(seed, programId, ephemeral = false) {
|
|
|
19
44
|
if (programId.length !== 32) {
|
|
20
45
|
throw new Error(`Expected 32-byte program ID, got ${programId.length} bytes`);
|
|
21
46
|
}
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
result[0] = result[0] & 127;
|
|
31
|
-
}
|
|
32
|
-
return result;
|
|
47
|
+
const flag = new Uint8Array([ephemeral ? 1 : 0]);
|
|
48
|
+
return sha256Bytes(concatBytes(programId, flag, seed));
|
|
49
|
+
}
|
|
50
|
+
async function deriveAbiMetaAddress(kind, body, abiManagerProgramId, ephemeral = false) {
|
|
51
|
+
const abiManagerBytes = typeof abiManagerProgramId === "string" ? decodeAddress(abiManagerProgramId) : abiManagerProgramId;
|
|
52
|
+
const seed = await deriveAbiMetaSeed(kind, body);
|
|
53
|
+
const addressBytes = await deriveProgramAddress(seed, abiManagerBytes, ephemeral);
|
|
54
|
+
return encodeAddress(addressBytes);
|
|
33
55
|
}
|
|
34
|
-
async function deriveAbiAddress(
|
|
35
|
-
const programAccountBytes = typeof programAccount === "string" ? decodeAddress(programAccount) : programAccount;
|
|
56
|
+
async function deriveAbiAddress(kind, body, abiManagerProgramId, ephemeral = false) {
|
|
36
57
|
const abiManagerBytes = typeof abiManagerProgramId === "string" ? decodeAddress(abiManagerProgramId) : abiManagerProgramId;
|
|
37
|
-
const seed = await
|
|
38
|
-
const
|
|
39
|
-
return encodeAddress(
|
|
58
|
+
const seed = await deriveAbiAccountSeed(kind, body);
|
|
59
|
+
const addressBytes = await deriveProgramAddress(seed, abiManagerBytes, ephemeral);
|
|
60
|
+
return encodeAddress(addressBytes);
|
|
40
61
|
}
|
|
41
62
|
|
|
42
63
|
// src/types.ts
|
|
@@ -54,13 +75,13 @@ function parseAbiAccountHeader(data) {
|
|
|
54
75
|
);
|
|
55
76
|
}
|
|
56
77
|
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
57
|
-
const
|
|
78
|
+
const abiMetaAccount = data.slice(0, 32);
|
|
58
79
|
const revision = view.getBigUint64(32, true);
|
|
59
80
|
const stateRaw = data[40];
|
|
60
81
|
const state = stateRaw === ABI_STATE.FINALIZED ? ABI_STATE.FINALIZED : ABI_STATE.OPEN;
|
|
61
82
|
const contentSize = view.getUint32(41, true);
|
|
62
83
|
return {
|
|
63
|
-
|
|
84
|
+
abiMetaAccount,
|
|
64
85
|
revision,
|
|
65
86
|
state,
|
|
66
87
|
contentSize
|
|
@@ -85,8 +106,11 @@ function parseAbiAccountData(data) {
|
|
|
85
106
|
export {
|
|
86
107
|
ABI_ACCOUNT_HEADER_SIZE,
|
|
87
108
|
ABI_STATE,
|
|
109
|
+
abiMetaBodyForProgram,
|
|
110
|
+
deriveAbiAccountSeed,
|
|
88
111
|
deriveAbiAddress,
|
|
89
|
-
|
|
112
|
+
deriveAbiMetaAddress,
|
|
113
|
+
deriveAbiMetaSeed,
|
|
90
114
|
deriveProgramAddress,
|
|
91
115
|
parseAbiAccountData,
|
|
92
116
|
parseAbiAccountHeader
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/derivation.ts","../src/types.ts","../src/parser.ts"],"sourcesContent":["import { decodeAddress, encodeAddress } from \"@thru/helpers\";\n\
|
|
1
|
+
{"version":3,"sources":["../src/derivation.ts","../src/types.ts","../src/parser.ts"],"sourcesContent":["import { decodeAddress, encodeAddress } from \"@thru/helpers\";\n\nconst ABI_META_BODY_SIZE = 96;\nconst ABI_ACCOUNT_SUFFIX = \"_abi_account\";\nconst ABI_ACCOUNT_SUFFIX_BYTES = new TextEncoder().encode(ABI_ACCOUNT_SUFFIX);\n\nfunction concatBytes(...chunks: Uint8Array[]): Uint8Array {\n const total = chunks.reduce((sum, chunk) => sum + chunk.length, 0);\n const out = new Uint8Array(total);\n let offset = 0;\n for (const chunk of chunks) {\n out.set(chunk, offset);\n offset += chunk.length;\n }\n return out;\n}\n\nasync function sha256Bytes(data: Uint8Array): Promise<Uint8Array> {\n const hashBuffer = await crypto.subtle.digest(\"SHA-256\", data as BufferSource);\n return new Uint8Array(hashBuffer);\n}\n\n/**\n * Builds the ABI meta body for an official ABI (program account).\n */\nexport function abiMetaBodyForProgram(programBytes: Uint8Array): Uint8Array {\n if (programBytes.length !== 32) {\n throw new Error(`Expected 32-byte program account, got ${programBytes.length} bytes`);\n }\n const body = new Uint8Array(ABI_META_BODY_SIZE);\n body.set(programBytes, 0);\n return body;\n}\n\n/**\n * Derives the ABI meta seed from ABI meta kind + body.\n */\nexport async function deriveAbiMetaSeed(kind: number, body: Uint8Array): Promise<Uint8Array> {\n if (body.length !== ABI_META_BODY_SIZE) {\n throw new Error(`Expected ${ABI_META_BODY_SIZE}-byte ABI meta body, got ${body.length} bytes`);\n }\n return sha256Bytes(concatBytes(new Uint8Array([kind]), body));\n}\n\n/**\n * Derives the ABI account seed from ABI meta kind + body.\n */\nexport async function deriveAbiAccountSeed(kind: number, body: Uint8Array): Promise<Uint8Array> {\n if (body.length !== ABI_META_BODY_SIZE) {\n throw new Error(`Expected ${ABI_META_BODY_SIZE}-byte ABI meta body, got ${body.length} bytes`);\n }\n return sha256Bytes(concatBytes(new Uint8Array([kind]), body, ABI_ACCOUNT_SUFFIX_BYTES));\n}\n\n/**\n * Derives a program-defined account address (owner || ephemeral || seed).\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 const flag = new Uint8Array([ephemeral ? 1 : 0]);\n return sha256Bytes(concatBytes(programId, flag, seed));\n}\n\n/**\n * Derives the ABI meta account address (as a ta-prefixed string).\n */\nexport async function deriveAbiMetaAddress(\n kind: number,\n body: Uint8Array,\n abiManagerProgramId: string | Uint8Array,\n ephemeral: boolean = false\n): Promise<string> {\n const abiManagerBytes = typeof abiManagerProgramId === \"string\"\n ? decodeAddress(abiManagerProgramId)\n : abiManagerProgramId;\n const seed = await deriveAbiMetaSeed(kind, body);\n const addressBytes = await deriveProgramAddress(seed, abiManagerBytes, ephemeral);\n return encodeAddress(addressBytes);\n}\n\n/**\n * Derives the ABI account address (as a ta-prefixed string).\n */\nexport async function deriveAbiAddress(\n kind: number,\n body: Uint8Array,\n abiManagerProgramId: string | Uint8Array,\n ephemeral: boolean = false\n): Promise<string> {\n const abiManagerBytes = typeof abiManagerProgramId === \"string\"\n ? decodeAddress(abiManagerProgramId)\n : abiManagerProgramId;\n const seed = await deriveAbiAccountSeed(kind, body);\n const addressBytes = await deriveProgramAddress(seed, abiManagerBytes, ephemeral);\n return encodeAddress(addressBytes);\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 ABI meta account this ABI is associated with */\n abiMetaAccount: 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: abi_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 // abi_meta_acc: bytes 0-31 (32 bytes)\n const abiMetaAccount = 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 abiMetaAccount,\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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thru/abi-utils",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
}
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@thru/helpers": "0.2.
|
|
14
|
+
"@thru/helpers": "0.2.1"
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
17
|
"@types/node": "^24.10.1",
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"build": "tsup",
|
|
24
24
|
"dev": "tsup --watch",
|
|
25
25
|
"test": "vitest run",
|
|
26
|
+
"test:run": "vitest run",
|
|
26
27
|
"test:watch": "vitest watch",
|
|
27
28
|
"clean": "rm -rf dist"
|
|
28
29
|
}
|
package/src/derivation.ts
CHANGED
|
@@ -1,50 +1,59 @@
|
|
|
1
1
|
import { decodeAddress, encodeAddress } from "@thru/helpers";
|
|
2
2
|
|
|
3
|
+
const ABI_META_BODY_SIZE = 96;
|
|
4
|
+
const ABI_ACCOUNT_SUFFIX = "_abi_account";
|
|
5
|
+
const ABI_ACCOUNT_SUFFIX_BYTES = new TextEncoder().encode(ABI_ACCOUNT_SUFFIX);
|
|
6
|
+
|
|
7
|
+
function concatBytes(...chunks: Uint8Array[]): Uint8Array {
|
|
8
|
+
const total = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
|
9
|
+
const out = new Uint8Array(total);
|
|
10
|
+
let offset = 0;
|
|
11
|
+
for (const chunk of chunks) {
|
|
12
|
+
out.set(chunk, offset);
|
|
13
|
+
offset += chunk.length;
|
|
14
|
+
}
|
|
15
|
+
return out;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function sha256Bytes(data: Uint8Array): Promise<Uint8Array> {
|
|
19
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", data as BufferSource);
|
|
20
|
+
return new Uint8Array(hashBuffer);
|
|
21
|
+
}
|
|
22
|
+
|
|
3
23
|
/**
|
|
4
|
-
*
|
|
24
|
+
* Builds the ABI meta body for an official ABI (program account).
|
|
5
25
|
*/
|
|
6
|
-
|
|
26
|
+
export function abiMetaBodyForProgram(programBytes: Uint8Array): Uint8Array {
|
|
27
|
+
if (programBytes.length !== 32) {
|
|
28
|
+
throw new Error(`Expected 32-byte program account, got ${programBytes.length} bytes`);
|
|
29
|
+
}
|
|
30
|
+
const body = new Uint8Array(ABI_META_BODY_SIZE);
|
|
31
|
+
body.set(programBytes, 0);
|
|
32
|
+
return body;
|
|
33
|
+
}
|
|
7
34
|
|
|
8
35
|
/**
|
|
9
|
-
* Derives the ABI seed
|
|
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
|
|
36
|
+
* Derives the ABI meta seed from ABI meta kind + body.
|
|
19
37
|
*/
|
|
20
|
-
export async function
|
|
21
|
-
if (
|
|
22
|
-
throw new Error(`Expected
|
|
38
|
+
export async function deriveAbiMetaSeed(kind: number, body: Uint8Array): Promise<Uint8Array> {
|
|
39
|
+
if (body.length !== ABI_META_BODY_SIZE) {
|
|
40
|
+
throw new Error(`Expected ${ABI_META_BODY_SIZE}-byte ABI meta body, got ${body.length} bytes`);
|
|
23
41
|
}
|
|
42
|
+
return sha256Bytes(concatBytes(new Uint8Array([kind]), body));
|
|
43
|
+
}
|
|
24
44
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
return new Uint8Array(hashBuffer);
|
|
45
|
+
/**
|
|
46
|
+
* Derives the ABI account seed from ABI meta kind + body.
|
|
47
|
+
*/
|
|
48
|
+
export async function deriveAbiAccountSeed(kind: number, body: Uint8Array): Promise<Uint8Array> {
|
|
49
|
+
if (body.length !== ABI_META_BODY_SIZE) {
|
|
50
|
+
throw new Error(`Expected ${ABI_META_BODY_SIZE}-byte ABI meta body, got ${body.length} bytes`);
|
|
51
|
+
}
|
|
52
|
+
return sha256Bytes(concatBytes(new Uint8Array([kind]), body, ABI_ACCOUNT_SUFFIX_BYTES));
|
|
34
53
|
}
|
|
35
54
|
|
|
36
55
|
/**
|
|
37
|
-
* Derives a program address (
|
|
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
|
|
56
|
+
* Derives a program-defined account address (owner || ephemeral || seed).
|
|
48
57
|
*/
|
|
49
58
|
export async function deriveProgramAddress(
|
|
50
59
|
seed: Uint8Array,
|
|
@@ -58,54 +67,40 @@ export async function deriveProgramAddress(
|
|
|
58
67
|
throw new Error(`Expected 32-byte program ID, got ${programId.length} bytes`);
|
|
59
68
|
}
|
|
60
69
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
}
|
|
70
|
+
const flag = new Uint8Array([ephemeral ? 1 : 0]);
|
|
71
|
+
return sha256Bytes(concatBytes(programId, flag, seed));
|
|
72
|
+
}
|
|
77
73
|
|
|
78
|
-
|
|
74
|
+
/**
|
|
75
|
+
* Derives the ABI meta account address (as a ta-prefixed string).
|
|
76
|
+
*/
|
|
77
|
+
export async function deriveAbiMetaAddress(
|
|
78
|
+
kind: number,
|
|
79
|
+
body: Uint8Array,
|
|
80
|
+
abiManagerProgramId: string | Uint8Array,
|
|
81
|
+
ephemeral: boolean = false
|
|
82
|
+
): Promise<string> {
|
|
83
|
+
const abiManagerBytes = typeof abiManagerProgramId === "string"
|
|
84
|
+
? decodeAddress(abiManagerProgramId)
|
|
85
|
+
: abiManagerProgramId;
|
|
86
|
+
const seed = await deriveAbiMetaSeed(kind, body);
|
|
87
|
+
const addressBytes = await deriveProgramAddress(seed, abiManagerBytes, ephemeral);
|
|
88
|
+
return encodeAddress(addressBytes);
|
|
79
89
|
}
|
|
80
90
|
|
|
81
91
|
/**
|
|
82
|
-
* Derives the ABI account address
|
|
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
|
|
92
|
+
* Derives the ABI account address (as a ta-prefixed string).
|
|
88
93
|
*/
|
|
89
94
|
export async function deriveAbiAddress(
|
|
90
|
-
|
|
95
|
+
kind: number,
|
|
96
|
+
body: Uint8Array,
|
|
91
97
|
abiManagerProgramId: string | Uint8Array,
|
|
92
98
|
ephemeral: boolean = false
|
|
93
99
|
): Promise<string> {
|
|
94
|
-
// Convert string addresses to bytes
|
|
95
|
-
const programAccountBytes = typeof programAccount === "string"
|
|
96
|
-
? decodeAddress(programAccount)
|
|
97
|
-
: programAccount;
|
|
98
|
-
|
|
99
100
|
const abiManagerBytes = typeof abiManagerProgramId === "string"
|
|
100
101
|
? decodeAddress(abiManagerProgramId)
|
|
101
102
|
: abiManagerProgramId;
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
// Derive the PDA
|
|
107
|
-
const abiAccountBytes = await deriveProgramAddress(seed, abiManagerBytes, ephemeral);
|
|
108
|
-
|
|
109
|
-
// Encode as ta-prefixed address
|
|
110
|
-
return encodeAddress(abiAccountBytes);
|
|
103
|
+
const seed = await deriveAbiAccountSeed(kind, body);
|
|
104
|
+
const addressBytes = await deriveProgramAddress(seed, abiManagerBytes, ephemeral);
|
|
105
|
+
return encodeAddress(addressBytes);
|
|
111
106
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export {
|
|
2
|
+
abiMetaBodyForProgram,
|
|
3
|
+
deriveAbiAddress,
|
|
4
|
+
deriveAbiAccountSeed,
|
|
5
|
+
deriveAbiMetaAddress,
|
|
6
|
+
deriveAbiMetaSeed,
|
|
7
|
+
deriveProgramAddress,
|
|
8
|
+
} from "./derivation";
|
|
2
9
|
export type { AbiAccountHeader, AbiAccountData, AbiAccountState } from "./types";
|
|
3
10
|
export { ABI_STATE } from "./types";
|
|
4
11
|
export { parseAbiAccountHeader, parseAbiAccountData, ABI_ACCOUNT_HEADER_SIZE } from "./parser";
|
package/src/parser.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { ABI_STATE } from "./types";
|
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Size of the ABI account header in bytes:
|
|
6
|
-
* - 32 bytes:
|
|
6
|
+
* - 32 bytes: abi_meta_acc (pubkey)
|
|
7
7
|
* - 8 bytes: revision (u64)
|
|
8
8
|
* - 1 byte: state (u8)
|
|
9
9
|
* - 4 bytes: content_sz (u32)
|
|
@@ -27,8 +27,8 @@ export function parseAbiAccountHeader(data: Uint8Array): AbiAccountHeader {
|
|
|
27
27
|
|
|
28
28
|
const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
|
|
29
29
|
|
|
30
|
-
//
|
|
31
|
-
const
|
|
30
|
+
// abi_meta_acc: bytes 0-31 (32 bytes)
|
|
31
|
+
const abiMetaAccount = data.slice(0, 32);
|
|
32
32
|
|
|
33
33
|
// revision: bytes 32-39 (8 bytes, little-endian u64)
|
|
34
34
|
const revision = view.getBigUint64(32, true);
|
|
@@ -43,7 +43,7 @@ export function parseAbiAccountHeader(data: Uint8Array): AbiAccountHeader {
|
|
|
43
43
|
const contentSize = view.getUint32(41, true);
|
|
44
44
|
|
|
45
45
|
return {
|
|
46
|
-
|
|
46
|
+
abiMetaAccount,
|
|
47
47
|
revision,
|
|
48
48
|
state,
|
|
49
49
|
contentSize,
|
package/src/types.ts
CHANGED
|
@@ -12,8 +12,8 @@ export type AbiAccountState = (typeof ABI_STATE)[keyof typeof ABI_STATE];
|
|
|
12
12
|
* Parsed header from an ABI account's on-chain data
|
|
13
13
|
*/
|
|
14
14
|
export interface AbiAccountHeader {
|
|
15
|
-
/** The
|
|
16
|
-
|
|
15
|
+
/** The ABI meta account this ABI is associated with */
|
|
16
|
+
abiMetaAccount: Uint8Array;
|
|
17
17
|
/** Revision number (incremented on each upgrade) */
|
|
18
18
|
revision: bigint;
|
|
19
19
|
/** Account state: OPEN (0) or FINALIZED (1) */
|