@cascade-fyi/compression-kit 0.1.0
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 +201 -0
- package/README.md +230 -0
- package/dist/index.cjs +1653 -0
- package/dist/index.d.cts +1033 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.mts +1033 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +1501 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +75 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1501 @@
|
|
|
1
|
+
import bs58 from "bs58";
|
|
2
|
+
import { AccountRole, address, getAddressEncoder, getProgramDerivedAddress } from "@solana/kit";
|
|
3
|
+
import { keccak_256 } from "@noble/hashes/sha3.js";
|
|
4
|
+
|
|
5
|
+
//#region src/state/types.ts
|
|
6
|
+
/**
|
|
7
|
+
* Tree type enum matching the on-chain representation.
|
|
8
|
+
*/
|
|
9
|
+
let TreeType = /* @__PURE__ */ function(TreeType$1) {
|
|
10
|
+
/** v1 state merkle tree */
|
|
11
|
+
TreeType$1[TreeType$1["StateV1"] = 1] = "StateV1";
|
|
12
|
+
/** v1 address merkle tree */
|
|
13
|
+
TreeType$1[TreeType$1["AddressV1"] = 2] = "AddressV1";
|
|
14
|
+
/** v2 state merkle tree */
|
|
15
|
+
TreeType$1[TreeType$1["StateV2"] = 3] = "StateV2";
|
|
16
|
+
/** v2 address merkle tree */
|
|
17
|
+
TreeType$1[TreeType$1["AddressV2"] = 4] = "AddressV2";
|
|
18
|
+
return TreeType$1;
|
|
19
|
+
}({});
|
|
20
|
+
|
|
21
|
+
//#endregion
|
|
22
|
+
//#region src/constants.ts
|
|
23
|
+
/**
|
|
24
|
+
* Light Protocol constants using Solana Kit patterns.
|
|
25
|
+
*
|
|
26
|
+
* Uses:
|
|
27
|
+
* - Address type (branded string) instead of PublicKey
|
|
28
|
+
* - Native bigint instead of BN.js
|
|
29
|
+
*/
|
|
30
|
+
let VERSION = /* @__PURE__ */ function(VERSION$1) {
|
|
31
|
+
VERSION$1["V1"] = "V1";
|
|
32
|
+
VERSION$1["V2"] = "V2";
|
|
33
|
+
return VERSION$1;
|
|
34
|
+
}({});
|
|
35
|
+
/**
|
|
36
|
+
* Feature flags for protocol versioning.
|
|
37
|
+
* @internal
|
|
38
|
+
*/
|
|
39
|
+
const featureFlags = {
|
|
40
|
+
version: VERSION.V1,
|
|
41
|
+
isV2: () => featureFlags.version === VERSION.V2
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Returns versioned endpoint name.
|
|
45
|
+
* @example versionedEndpoint('getCompressedAccount') -> 'getCompressedAccountV2' (if V2)
|
|
46
|
+
*/
|
|
47
|
+
const versionedEndpoint = (base) => featureFlags.isV2() ? `${base}V2` : base;
|
|
48
|
+
/**
|
|
49
|
+
* BN254 prime field size.
|
|
50
|
+
* All hashes must be less than this value for ZK circuit compatibility.
|
|
51
|
+
*/
|
|
52
|
+
const FIELD_SIZE = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;
|
|
53
|
+
/**
|
|
54
|
+
* Highest address plus one (used for address validation).
|
|
55
|
+
*/
|
|
56
|
+
const HIGHEST_ADDRESS_PLUS_ONE = 452312848583266388373324160190187140051835877600158453279131187530910662655n;
|
|
57
|
+
/** Light System Program ID */
|
|
58
|
+
const LIGHT_SYSTEM_PROGRAM = address("SySTEM1eSU2p4BGQfQpimFEWWSC1XDFeun3Nqzz3rT7");
|
|
59
|
+
/** Account Compression Program ID */
|
|
60
|
+
const ACCOUNT_COMPRESSION_PROGRAM = address("compr6CUsB5m2jS4Y3831ztGSTnDpnKJTKS95d64XVq");
|
|
61
|
+
/** Noop Program ID (for logging) */
|
|
62
|
+
const NOOP_PROGRAM = address("noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV");
|
|
63
|
+
/** Compressed Token Program ID */
|
|
64
|
+
const COMPRESSED_TOKEN_PROGRAM = address("cTokenmWW8bLPjZEBAUgYy3zKxQZW6VKi7bqNFEVv3m");
|
|
65
|
+
/** Registered Program PDA (constant) */
|
|
66
|
+
const REGISTERED_PROGRAM_PDA = address("35hkDgaAKwMCaxRz2ocSZ6NaUrtKkyNqU6c4RV3tYJRh");
|
|
67
|
+
const INVOKE_DISCRIMINATOR = new Uint8Array([
|
|
68
|
+
26,
|
|
69
|
+
16,
|
|
70
|
+
169,
|
|
71
|
+
7,
|
|
72
|
+
21,
|
|
73
|
+
202,
|
|
74
|
+
242,
|
|
75
|
+
25
|
|
76
|
+
]);
|
|
77
|
+
const INVOKE_CPI_DISCRIMINATOR = new Uint8Array([
|
|
78
|
+
49,
|
|
79
|
+
212,
|
|
80
|
+
191,
|
|
81
|
+
129,
|
|
82
|
+
39,
|
|
83
|
+
194,
|
|
84
|
+
43,
|
|
85
|
+
196
|
|
86
|
+
]);
|
|
87
|
+
const INVOKE_CPI_WITH_READ_ONLY_DISCRIMINATOR = new Uint8Array([
|
|
88
|
+
86,
|
|
89
|
+
47,
|
|
90
|
+
163,
|
|
91
|
+
166,
|
|
92
|
+
21,
|
|
93
|
+
223,
|
|
94
|
+
92,
|
|
95
|
+
8
|
|
96
|
+
]);
|
|
97
|
+
const INVOKE_CPI_WITH_ACCOUNT_INFO_DISCRIMINATOR = new Uint8Array([
|
|
98
|
+
228,
|
|
99
|
+
34,
|
|
100
|
+
128,
|
|
101
|
+
84,
|
|
102
|
+
47,
|
|
103
|
+
139,
|
|
104
|
+
86,
|
|
105
|
+
240
|
|
106
|
+
]);
|
|
107
|
+
const INSERT_INTO_QUEUES_DISCRIMINATOR = new Uint8Array([
|
|
108
|
+
180,
|
|
109
|
+
143,
|
|
110
|
+
159,
|
|
111
|
+
153,
|
|
112
|
+
35,
|
|
113
|
+
46,
|
|
114
|
+
248,
|
|
115
|
+
163
|
|
116
|
+
]);
|
|
117
|
+
const COMPUTE_BUDGET_PATTERN = new Uint8Array([
|
|
118
|
+
2,
|
|
119
|
+
64,
|
|
120
|
+
66,
|
|
121
|
+
15,
|
|
122
|
+
0
|
|
123
|
+
]);
|
|
124
|
+
/** Mainnet state tree lookup table */
|
|
125
|
+
const STATE_TREE_LOOKUP_TABLE_MAINNET = address("7i86eQs3GSqHjN47WdWLTCGMW6gde1q96G2EVnUyK2st");
|
|
126
|
+
/** Mainnet nullified state tree lookup table */
|
|
127
|
+
const NULLIFIED_STATE_TREE_LOOKUP_TABLE_MAINNET = address("H9QD4u1fG7KmkAzn2tDXhheushxFe1EcrjGGyEFXeMqT");
|
|
128
|
+
/** Devnet state tree lookup table */
|
|
129
|
+
const STATE_TREE_LOOKUP_TABLE_DEVNET = address("Dk9mNkbiZXJZ4By8DfSP6HEE4ojZzRvucwpawLeuwq8q");
|
|
130
|
+
/** Devnet nullified state tree lookup table */
|
|
131
|
+
const NULLIFIED_STATE_TREE_LOOKUP_TABLE_DEVNET = address("AXbHzp1NgjLvpfnD6JRTTovXZ7APUCdtWZFCRr5tCxse");
|
|
132
|
+
/**
|
|
133
|
+
* Returns default state tree lookup tables for each network.
|
|
134
|
+
*/
|
|
135
|
+
function defaultStateTreeLookupTables() {
|
|
136
|
+
return {
|
|
137
|
+
mainnet: [{
|
|
138
|
+
stateTreeLookupTable: STATE_TREE_LOOKUP_TABLE_MAINNET,
|
|
139
|
+
nullifyLookupTable: NULLIFIED_STATE_TREE_LOOKUP_TABLE_MAINNET
|
|
140
|
+
}],
|
|
141
|
+
devnet: [{
|
|
142
|
+
stateTreeLookupTable: STATE_TREE_LOOKUP_TABLE_DEVNET,
|
|
143
|
+
nullifyLookupTable: NULLIFIED_STATE_TREE_LOOKUP_TABLE_DEVNET
|
|
144
|
+
}]
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
const MERKLE_TREE_PUBKEY = address("smt1NamzXdq4AMqS2fS2F1i5KTYPZRhoHgWx38d8WsT");
|
|
148
|
+
const NULLIFIER_QUEUE_PUBKEY = address("nfq1NvQDJ2GEgnS8zt9prAe8rjjpAW1zFkrvZoBR148");
|
|
149
|
+
const CPI_CONTEXT_PUBKEY = address("cpi1uHzrEhBG733DoEJNgHCyRS3XmmyVNZx5fonubE4");
|
|
150
|
+
const MERKLE_TREE_2_PUBKEY = address("smt2rJAFdyJJupwMKAqTNAJwvjhmiZ4JYGZmbVRw1Ho");
|
|
151
|
+
const NULLIFIER_QUEUE_2_PUBKEY = address("nfq2hgS7NYemXsFaFUCe3EMXSDSfnZnAe27jC6aPP1X");
|
|
152
|
+
const CPI_CONTEXT_2_PUBKEY = address("cpi2cdhkH5roePvcudTgUL8ppEBfTay1desGh8G8QxK");
|
|
153
|
+
const ADDRESS_TREE = address("amt1Ayt45jfbdw5YSo7iz6WZxUmnZsQTYXy82hVwyC2");
|
|
154
|
+
const ADDRESS_QUEUE = address("aq1S9z4reTSQAdgWHGD2zDaS39sjGrAxbR31vxJ2F4F");
|
|
155
|
+
const BATCH_MERKLE_TREE_1 = address("bmt1LryLZUMmF7ZtqESaw7wifBXLfXHQYoE4GAmrahU");
|
|
156
|
+
const BATCH_QUEUE_1 = address("oq1na8gojfdUhsfCpyjNt6h4JaDWtHf1yQj4koBWfto");
|
|
157
|
+
const BATCH_CPI_CONTEXT_1 = address("cpi15BoVPKgEPw5o8wc2T816GE7b378nMXnhH3Xbq4y");
|
|
158
|
+
const BATCH_MERKLE_TREE_2 = address("bmt2UxoBxB9xWev4BkLvkGdapsz6sZGkzViPNph7VFi");
|
|
159
|
+
const BATCH_QUEUE_2 = address("oq2UkeMsJLfXt2QHzim242SUi3nvjJs8Pn7Eac9H9vg");
|
|
160
|
+
const BATCH_CPI_CONTEXT_2 = address("cpi2yGapXUR3As5SjnHBAVvmApNiLsbeZpF3euWnW6B");
|
|
161
|
+
const BATCH_ADDRESS_TREE = address("amt2kaJA14v3urZbZvnc5v2np8jqvc4Z8zDep5wbtzx");
|
|
162
|
+
const TEST_BATCH_ADDRESS_TREE = address("EzKE84aVTkCUhDHLELqyJaq1Y7UVVmqxXqZjVHwHY3rK");
|
|
163
|
+
const DEFAULT_MERKLE_TREE_HEIGHT = 26;
|
|
164
|
+
const DEFAULT_MERKLE_TREE_ROOTS = 2800;
|
|
165
|
+
/** Threshold for UTXO merging (per asset) */
|
|
166
|
+
const UTXO_MERGE_THRESHOLD = 20;
|
|
167
|
+
const UTXO_MERGE_MAXIMUM = 10;
|
|
168
|
+
/** Tree rollover threshold (95% capacity) */
|
|
169
|
+
const TRANSACTION_MERKLE_TREE_ROLLOVER_THRESHOLD = BigInt(Math.floor(2 ** DEFAULT_MERKLE_TREE_HEIGHT * .95));
|
|
170
|
+
/** Fee per output compressed account (for tree rollover) */
|
|
171
|
+
const STATE_MERKLE_TREE_ROLLOVER_FEE = featureFlags.isV2() ? 1n : 300n;
|
|
172
|
+
/** Fee per new address (for address tree rollover) */
|
|
173
|
+
const ADDRESS_QUEUE_ROLLOVER_FEE = 392n;
|
|
174
|
+
/** Network fee for nullifying compressed accounts */
|
|
175
|
+
const STATE_MERKLE_TREE_NETWORK_FEE = 5000n;
|
|
176
|
+
/** V1 network fee per new address */
|
|
177
|
+
const ADDRESS_TREE_NETWORK_FEE_V1 = 5000n;
|
|
178
|
+
/** V2 network fee per new address */
|
|
179
|
+
const ADDRESS_TREE_NETWORK_FEE_V2 = 10000n;
|
|
180
|
+
/**
|
|
181
|
+
* Derives the account compression authority PDA.
|
|
182
|
+
*/
|
|
183
|
+
async function getAccountCompressionAuthority() {
|
|
184
|
+
const [pda] = await getProgramDerivedAddress({
|
|
185
|
+
programAddress: LIGHT_SYSTEM_PROGRAM,
|
|
186
|
+
seeds: [new TextEncoder().encode("cpi_authority")]
|
|
187
|
+
});
|
|
188
|
+
return pda;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Returns static accounts needed for Light System Program calls.
|
|
192
|
+
*/
|
|
193
|
+
async function defaultStaticAccounts() {
|
|
194
|
+
return [
|
|
195
|
+
REGISTERED_PROGRAM_PDA,
|
|
196
|
+
NOOP_PROGRAM,
|
|
197
|
+
ACCOUNT_COMPRESSION_PROGRAM,
|
|
198
|
+
await getAccountCompressionAuthority()
|
|
199
|
+
];
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Check if URL is localhost/localnet.
|
|
203
|
+
* @internal
|
|
204
|
+
*/
|
|
205
|
+
function isLocalTest(url) {
|
|
206
|
+
return url.includes("localhost") || url.includes("127.0.0.1");
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Returns default test state tree accounts for localnet.
|
|
210
|
+
*/
|
|
211
|
+
function defaultTestStateTreeAccounts() {
|
|
212
|
+
return {
|
|
213
|
+
nullifierQueue: NULLIFIER_QUEUE_PUBKEY,
|
|
214
|
+
merkleTree: MERKLE_TREE_PUBKEY,
|
|
215
|
+
merkleTreeHeight: DEFAULT_MERKLE_TREE_HEIGHT,
|
|
216
|
+
addressTree: ADDRESS_TREE,
|
|
217
|
+
addressQueue: ADDRESS_QUEUE
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Returns active state tree infos for localnet testing.
|
|
222
|
+
* @internal
|
|
223
|
+
*/
|
|
224
|
+
function localTestActiveStateTreeInfos() {
|
|
225
|
+
const v1Trees = [{
|
|
226
|
+
tree: MERKLE_TREE_PUBKEY,
|
|
227
|
+
queue: NULLIFIER_QUEUE_PUBKEY,
|
|
228
|
+
cpiContext: CPI_CONTEXT_PUBKEY,
|
|
229
|
+
treeType: TreeType.StateV1,
|
|
230
|
+
nextTreeInfo: null
|
|
231
|
+
}, {
|
|
232
|
+
tree: MERKLE_TREE_2_PUBKEY,
|
|
233
|
+
queue: NULLIFIER_QUEUE_2_PUBKEY,
|
|
234
|
+
cpiContext: CPI_CONTEXT_2_PUBKEY,
|
|
235
|
+
treeType: TreeType.StateV1,
|
|
236
|
+
nextTreeInfo: null
|
|
237
|
+
}];
|
|
238
|
+
if (!featureFlags.isV2()) return v1Trees;
|
|
239
|
+
return [
|
|
240
|
+
...v1Trees,
|
|
241
|
+
{
|
|
242
|
+
tree: BATCH_MERKLE_TREE_1,
|
|
243
|
+
queue: BATCH_QUEUE_1,
|
|
244
|
+
cpiContext: BATCH_CPI_CONTEXT_1,
|
|
245
|
+
treeType: TreeType.StateV2,
|
|
246
|
+
nextTreeInfo: null
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
tree: BATCH_MERKLE_TREE_2,
|
|
250
|
+
queue: BATCH_QUEUE_2,
|
|
251
|
+
cpiContext: BATCH_CPI_CONTEXT_2,
|
|
252
|
+
treeType: TreeType.StateV2,
|
|
253
|
+
nextTreeInfo: null
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
tree: BATCH_ADDRESS_TREE,
|
|
257
|
+
queue: BATCH_ADDRESS_TREE,
|
|
258
|
+
treeType: TreeType.AddressV2,
|
|
259
|
+
nextTreeInfo: null
|
|
260
|
+
}
|
|
261
|
+
];
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Returns default address tree info.
|
|
265
|
+
*/
|
|
266
|
+
function getDefaultAddressTreeInfo() {
|
|
267
|
+
if (featureFlags.isV2()) return {
|
|
268
|
+
tree: BATCH_ADDRESS_TREE,
|
|
269
|
+
queue: BATCH_ADDRESS_TREE,
|
|
270
|
+
treeType: TreeType.AddressV2,
|
|
271
|
+
nextTreeInfo: null
|
|
272
|
+
};
|
|
273
|
+
return {
|
|
274
|
+
tree: ADDRESS_TREE,
|
|
275
|
+
queue: ADDRESS_QUEUE,
|
|
276
|
+
treeType: TreeType.AddressV1,
|
|
277
|
+
nextTreeInfo: null
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
//#endregion
|
|
282
|
+
//#region src/errors.ts
|
|
283
|
+
/**
|
|
284
|
+
* Error types for Light Protocol stateless operations.
|
|
285
|
+
*/
|
|
286
|
+
let UtxoErrorCode = /* @__PURE__ */ function(UtxoErrorCode$1) {
|
|
287
|
+
UtxoErrorCode$1["NEGATIVE_LAMPORTS"] = "NEGATIVE_LAMPORTS";
|
|
288
|
+
UtxoErrorCode$1["NOT_U64"] = "NOT_U64";
|
|
289
|
+
UtxoErrorCode$1["BLINDING_EXCEEDS_FIELD_SIZE"] = "BLINDING_EXCEEDS_FIELD_SIZE";
|
|
290
|
+
return UtxoErrorCode$1;
|
|
291
|
+
}({});
|
|
292
|
+
let SelectInUtxosErrorCode = /* @__PURE__ */ function(SelectInUtxosErrorCode$1) {
|
|
293
|
+
SelectInUtxosErrorCode$1["FAILED_TO_FIND_UTXO_COMBINATION"] = "FAILED_TO_FIND_UTXO_COMBINATION";
|
|
294
|
+
SelectInUtxosErrorCode$1["INVALID_NUMBER_OF_IN_UTXOS"] = "INVALID_NUMBER_OF_IN_UTXOS";
|
|
295
|
+
return SelectInUtxosErrorCode$1;
|
|
296
|
+
}({});
|
|
297
|
+
let CreateUtxoErrorCode = /* @__PURE__ */ function(CreateUtxoErrorCode$1) {
|
|
298
|
+
CreateUtxoErrorCode$1["OWNER_UNDEFINED"] = "OWNER_UNDEFINED";
|
|
299
|
+
CreateUtxoErrorCode$1["INVALID_OUTPUT_UTXO_LENGTH"] = "INVALID_OUTPUT_UTXO_LENGTH";
|
|
300
|
+
CreateUtxoErrorCode$1["UTXO_DATA_UNDEFINED"] = "UTXO_DATA_UNDEFINED";
|
|
301
|
+
return CreateUtxoErrorCode$1;
|
|
302
|
+
}({});
|
|
303
|
+
let RpcErrorCode = /* @__PURE__ */ function(RpcErrorCode$1) {
|
|
304
|
+
RpcErrorCode$1["CONNECTION_UNDEFINED"] = "CONNECTION_UNDEFINED";
|
|
305
|
+
RpcErrorCode$1["RPC_PUBKEY_UNDEFINED"] = "RPC_PUBKEY_UNDEFINED";
|
|
306
|
+
RpcErrorCode$1["RPC_METHOD_NOT_IMPLEMENTED"] = "RPC_METHOD_NOT_IMPLEMENTED";
|
|
307
|
+
RpcErrorCode$1["RPC_INVALID"] = "RPC_INVALID";
|
|
308
|
+
return RpcErrorCode$1;
|
|
309
|
+
}({});
|
|
310
|
+
let LookupTableErrorCode = /* @__PURE__ */ function(LookupTableErrorCode$1) {
|
|
311
|
+
LookupTableErrorCode$1["LOOK_UP_TABLE_UNDEFINED"] = "LOOK_UP_TABLE_UNDEFINED";
|
|
312
|
+
LookupTableErrorCode$1["LOOK_UP_TABLE_NOT_INITIALIZED"] = "LOOK_UP_TABLE_NOT_INITIALIZED";
|
|
313
|
+
return LookupTableErrorCode$1;
|
|
314
|
+
}({});
|
|
315
|
+
let HashErrorCode = /* @__PURE__ */ function(HashErrorCode$1) {
|
|
316
|
+
HashErrorCode$1["NO_POSEIDON_HASHER_PROVIDED"] = "NO_POSEIDON_HASHER_PROVIDED";
|
|
317
|
+
return HashErrorCode$1;
|
|
318
|
+
}({});
|
|
319
|
+
let ProofErrorCode = /* @__PURE__ */ function(ProofErrorCode$1) {
|
|
320
|
+
ProofErrorCode$1["INVALID_PROOF"] = "INVALID_PROOF";
|
|
321
|
+
ProofErrorCode$1["PROOF_INPUT_UNDEFINED"] = "PROOF_INPUT_UNDEFINED";
|
|
322
|
+
ProofErrorCode$1["PROOF_GENERATION_FAILED"] = "PROOF_GENERATION_FAILED";
|
|
323
|
+
return ProofErrorCode$1;
|
|
324
|
+
}({});
|
|
325
|
+
let MerkleTreeErrorCode = /* @__PURE__ */ function(MerkleTreeErrorCode$1) {
|
|
326
|
+
MerkleTreeErrorCode$1["MERKLE_TREE_NOT_INITIALIZED"] = "MERKLE_TREE_NOT_INITIALIZED";
|
|
327
|
+
MerkleTreeErrorCode$1["SOL_MERKLE_TREE_UNDEFINED"] = "SOL_MERKLE_TREE_UNDEFINED";
|
|
328
|
+
MerkleTreeErrorCode$1["MERKLE_TREE_UNDEFINED"] = "MERKLE_TREE_UNDEFINED";
|
|
329
|
+
MerkleTreeErrorCode$1["INPUT_UTXO_NOT_INSERTED_IN_MERKLE_TREE"] = "INPUT_UTXO_NOT_INSERTED_IN_MERKLE_TREE";
|
|
330
|
+
MerkleTreeErrorCode$1["MERKLE_TREE_INDEX_UNDEFINED"] = "MERKLE_TREE_INDEX_UNDEFINED";
|
|
331
|
+
MerkleTreeErrorCode$1["MERKLE_TREE_SET_SPACE_UNDEFINED"] = "MERKLE_TREE_SET_SPACE_UNDEFINED";
|
|
332
|
+
return MerkleTreeErrorCode$1;
|
|
333
|
+
}({});
|
|
334
|
+
let UtilsErrorCode = /* @__PURE__ */ function(UtilsErrorCode$1) {
|
|
335
|
+
UtilsErrorCode$1["ACCOUNT_NAME_UNDEFINED_IN_IDL"] = "ACCOUNT_NAME_UNDEFINED_IN_IDL";
|
|
336
|
+
UtilsErrorCode$1["PROPERTY_UNDEFINED"] = "PROPERTY_UNDEFINED";
|
|
337
|
+
UtilsErrorCode$1["LOOK_UP_TABLE_CREATION_FAILED"] = "LOOK_UP_TABLE_CREATION_FAILED";
|
|
338
|
+
UtilsErrorCode$1["UNSUPPORTED_ARCHITECTURE"] = "UNSUPPORTED_ARCHITECTURE";
|
|
339
|
+
UtilsErrorCode$1["UNSUPPORTED_PLATFORM"] = "UNSUPPORTED_PLATFORM";
|
|
340
|
+
UtilsErrorCode$1["ACCOUNTS_UNDEFINED"] = "ACCOUNTS_UNDEFINED";
|
|
341
|
+
UtilsErrorCode$1["INVALID_NUMBER"] = "INVALID_NUMBER";
|
|
342
|
+
return UtilsErrorCode$1;
|
|
343
|
+
}({});
|
|
344
|
+
let BN254ErrorCode = /* @__PURE__ */ function(BN254ErrorCode$1) {
|
|
345
|
+
BN254ErrorCode$1["VALUE_TOO_LARGE"] = "VALUE_TOO_LARGE";
|
|
346
|
+
BN254ErrorCode$1["INVALID_BASE58"] = "INVALID_BASE58";
|
|
347
|
+
return BN254ErrorCode$1;
|
|
348
|
+
}({});
|
|
349
|
+
/**
|
|
350
|
+
* Base error class for Light Protocol errors.
|
|
351
|
+
*/
|
|
352
|
+
var LightError = class extends Error {
|
|
353
|
+
constructor(code, functionName, codeMessage) {
|
|
354
|
+
super(`${code}: ${codeMessage ?? ""}`);
|
|
355
|
+
this.name = this.constructor.name;
|
|
356
|
+
this.code = code;
|
|
357
|
+
this.functionName = functionName;
|
|
358
|
+
this.codeMessage = codeMessage;
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
var UtxoError = class extends LightError {};
|
|
362
|
+
var SelectInUtxosError = class extends LightError {};
|
|
363
|
+
var CreateUtxoError = class extends LightError {};
|
|
364
|
+
var RpcError = class extends LightError {};
|
|
365
|
+
var LookupTableError = class extends LightError {};
|
|
366
|
+
var HashError = class extends LightError {};
|
|
367
|
+
var ProofError = class extends LightError {};
|
|
368
|
+
var MerkleTreeError = class extends LightError {};
|
|
369
|
+
var UtilsError = class extends LightError {};
|
|
370
|
+
var BN254Error = class extends LightError {};
|
|
371
|
+
function createUtxoError(code, functionName, message) {
|
|
372
|
+
return new UtxoError(code, functionName, message);
|
|
373
|
+
}
|
|
374
|
+
function createRpcError(code, functionName, message) {
|
|
375
|
+
return new RpcError(code, functionName, message);
|
|
376
|
+
}
|
|
377
|
+
function createProofError(code, functionName, message) {
|
|
378
|
+
return new ProofError(code, functionName, message);
|
|
379
|
+
}
|
|
380
|
+
function createBN254Error(code, functionName, message) {
|
|
381
|
+
return new BN254Error(code, functionName, message);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
//#endregion
|
|
385
|
+
//#region src/state/bn254.ts
|
|
386
|
+
/**
|
|
387
|
+
* BN254 field element utilities using native bigint.
|
|
388
|
+
*
|
|
389
|
+
* BN254 is the elliptic curve used by Light Protocol's ZK proofs.
|
|
390
|
+
* All hashes must be less than the field modulus (~2^254) for circuit compatibility.
|
|
391
|
+
*
|
|
392
|
+
* This module replaces BN.js with native bigint for:
|
|
393
|
+
* - Better performance
|
|
394
|
+
* - No Node.js Buffer dependency
|
|
395
|
+
* - Edge/browser compatibility
|
|
396
|
+
*/
|
|
397
|
+
/**
|
|
398
|
+
* Check if a bigint is within the BN254 field.
|
|
399
|
+
*/
|
|
400
|
+
function isBN254(value) {
|
|
401
|
+
return value >= 0n && value < FIELD_SIZE;
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Assert that a bigint is within the BN254 field.
|
|
405
|
+
* @throws BN254Error if value is out of range
|
|
406
|
+
*/
|
|
407
|
+
function assertIsBN254(value) {
|
|
408
|
+
if (!isBN254(value)) throw createBN254Error(BN254ErrorCode.VALUE_TOO_LARGE, "assertIsBN254", `Value ${value} exceeds BN254 field size`);
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Enforce BN254 field size constraint.
|
|
412
|
+
* @throws BN254Error if value is out of range
|
|
413
|
+
*/
|
|
414
|
+
function enforceFieldSize(value) {
|
|
415
|
+
assertIsBN254(value);
|
|
416
|
+
return value;
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Create a BN254 field element from various input types.
|
|
420
|
+
*
|
|
421
|
+
* @param input - Number, string, bigint, or byte array
|
|
422
|
+
* @param base - Optional base for string parsing: 10, 16, 'hex', or 'base58'
|
|
423
|
+
* @returns BN254 field element
|
|
424
|
+
* @throws BN254Error if value exceeds field size
|
|
425
|
+
*/
|
|
426
|
+
function createBN254(input, base) {
|
|
427
|
+
let value;
|
|
428
|
+
if (base === "base58") {
|
|
429
|
+
if (typeof input !== "string") throw createBN254Error(BN254ErrorCode.INVALID_BASE58, "createBN254", "Base58 input must be a string");
|
|
430
|
+
value = bytesToBigIntBE(bs58.decode(input));
|
|
431
|
+
} else if (typeof input === "bigint") value = input;
|
|
432
|
+
else if (typeof input === "number") value = BigInt(input);
|
|
433
|
+
else if (typeof input === "string") if (base === "hex" || base === 16) {
|
|
434
|
+
const cleanHex = input.startsWith("0x") ? input.slice(2) : input;
|
|
435
|
+
value = BigInt(`0x${cleanHex}`);
|
|
436
|
+
} else value = BigInt(input);
|
|
437
|
+
else if (input instanceof Uint8Array || Array.isArray(input)) value = bytesToBigIntBE(input instanceof Uint8Array ? input : new Uint8Array(input));
|
|
438
|
+
else throw createBN254Error(BN254ErrorCode.VALUE_TOO_LARGE, "createBN254", `Unsupported input type: ${typeof input}`);
|
|
439
|
+
return enforceFieldSize(value);
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* Create a BN254 from a 32-byte array (big-endian).
|
|
443
|
+
*/
|
|
444
|
+
function bn254FromBytes(bytes) {
|
|
445
|
+
if (bytes.length !== 32) throw createBN254Error(BN254ErrorCode.VALUE_TOO_LARGE, "bn254FromBytes", `Expected 32 bytes, got ${bytes.length}`);
|
|
446
|
+
return createBN254(bytes);
|
|
447
|
+
}
|
|
448
|
+
/**
|
|
449
|
+
* Convert a BN254 field element to a base58 string.
|
|
450
|
+
*/
|
|
451
|
+
function encodeBN254toBase58(value) {
|
|
452
|
+
const bytes = bigIntToBytesBE(value, 32);
|
|
453
|
+
return bs58.encode(bytes);
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Convert a BN254 field element to a hex string (with 0x prefix).
|
|
457
|
+
*/
|
|
458
|
+
function encodeBN254toHex(value) {
|
|
459
|
+
return `0x${value.toString(16).padStart(64, "0")}`;
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Convert a BN254 field element to a 32-byte array (big-endian).
|
|
463
|
+
*/
|
|
464
|
+
function bn254ToBytes(value) {
|
|
465
|
+
return bigIntToBytesBE(value, 32);
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Convert a BN254 field element to a decimal string.
|
|
469
|
+
*/
|
|
470
|
+
function bn254ToDecimalString(value) {
|
|
471
|
+
return value.toString(10);
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Convert bytes to bigint (big-endian).
|
|
475
|
+
*/
|
|
476
|
+
function bytesToBigIntBE(bytes) {
|
|
477
|
+
let result = 0n;
|
|
478
|
+
for (const byte of bytes) result = result << 8n | BigInt(byte);
|
|
479
|
+
return result;
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Convert bytes to bigint (little-endian).
|
|
483
|
+
*/
|
|
484
|
+
function bytesToBigIntLE(bytes) {
|
|
485
|
+
let result = 0n;
|
|
486
|
+
for (let i = bytes.length - 1; i >= 0; i--) result = result << 8n | BigInt(bytes[i]);
|
|
487
|
+
return result;
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* Convert bigint to bytes (big-endian).
|
|
491
|
+
*/
|
|
492
|
+
function bigIntToBytesBE(value, length) {
|
|
493
|
+
const bytes = new Uint8Array(length);
|
|
494
|
+
let remaining = value;
|
|
495
|
+
for (let i = length - 1; i >= 0; i--) {
|
|
496
|
+
bytes[i] = Number(remaining & 255n);
|
|
497
|
+
remaining >>= 8n;
|
|
498
|
+
}
|
|
499
|
+
return bytes;
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Convert bigint to bytes (little-endian).
|
|
503
|
+
*/
|
|
504
|
+
function bigIntToBytesLE(value, length) {
|
|
505
|
+
const bytes = new Uint8Array(length);
|
|
506
|
+
let remaining = value;
|
|
507
|
+
for (let i = 0; i < length; i++) {
|
|
508
|
+
bytes[i] = Number(remaining & 255n);
|
|
509
|
+
remaining >>= 8n;
|
|
510
|
+
}
|
|
511
|
+
return bytes;
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Add two BN254 values with modular reduction.
|
|
515
|
+
*/
|
|
516
|
+
function bn254Add(a, b) {
|
|
517
|
+
return (a + b) % FIELD_SIZE;
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Subtract two BN254 values with modular reduction.
|
|
521
|
+
*/
|
|
522
|
+
function bn254Sub(a, b) {
|
|
523
|
+
const result = (a - b) % FIELD_SIZE;
|
|
524
|
+
return result < 0n ? result + FIELD_SIZE : result;
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* Multiply two BN254 values with modular reduction.
|
|
528
|
+
*/
|
|
529
|
+
function bn254Mul(a, b) {
|
|
530
|
+
return a * b % FIELD_SIZE;
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Check if a value is smaller than the BN254 field size (big-endian bytes).
|
|
534
|
+
*/
|
|
535
|
+
function isSmallerThanFieldSize(bytes) {
|
|
536
|
+
return bytesToBigIntBE(bytes) < FIELD_SIZE;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
//#endregion
|
|
540
|
+
//#region src/utils/conversion.ts
|
|
541
|
+
/**
|
|
542
|
+
* Conversion and hashing utilities for Light Protocol.
|
|
543
|
+
*
|
|
544
|
+
* Uses @noble/hashes for Keccak256 - pure JS, works in all environments.
|
|
545
|
+
*/
|
|
546
|
+
/**
|
|
547
|
+
* Hash multiple byte arrays with Keccak256 and truncate to BN254 field size.
|
|
548
|
+
*
|
|
549
|
+
* This is the primary hash function used by Light Protocol. It:
|
|
550
|
+
* 1. Concatenates all input arrays
|
|
551
|
+
* 2. Hashes with Keccak256
|
|
552
|
+
* 3. Sets the first byte to 0 to ensure the result fits in BN254 field
|
|
553
|
+
*
|
|
554
|
+
* @param inputs - Array of byte arrays to hash
|
|
555
|
+
* @returns 32-byte hash that fits in BN254 field
|
|
556
|
+
*/
|
|
557
|
+
function hashvToBn254FieldSizeBe(inputs) {
|
|
558
|
+
const hasher = keccak_256.create();
|
|
559
|
+
for (const input of inputs) hasher.update(input);
|
|
560
|
+
const hash = hasher.digest();
|
|
561
|
+
hash[0] = 0;
|
|
562
|
+
return hash;
|
|
563
|
+
}
|
|
564
|
+
/**
|
|
565
|
+
* Hash multiple byte arrays with Keccak256, appending 0xFF bump seed.
|
|
566
|
+
*
|
|
567
|
+
* This variant appends a 255 bump seed before hashing, matching the
|
|
568
|
+
* on-chain behavior for certain hash derivations.
|
|
569
|
+
*
|
|
570
|
+
* @param inputs - Array of byte arrays to hash
|
|
571
|
+
* @returns 32-byte hash that fits in BN254 field
|
|
572
|
+
*/
|
|
573
|
+
function hashvToBn254FieldSizeBeWithBump(inputs) {
|
|
574
|
+
const hasher = keccak_256.create();
|
|
575
|
+
for (const input of inputs) hasher.update(input);
|
|
576
|
+
hasher.update(new Uint8Array([255]));
|
|
577
|
+
const hash = hasher.digest();
|
|
578
|
+
hash[0] = 0;
|
|
579
|
+
return hash;
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* Hash bytes with Keccak256 and find a valid bump seed.
|
|
583
|
+
*
|
|
584
|
+
* @deprecated Use hashvToBn254FieldSizeBe instead.
|
|
585
|
+
*
|
|
586
|
+
* This function iterates through bump seeds (255 down to 0) to find one
|
|
587
|
+
* that produces a hash smaller than the BN254 field size. This is the
|
|
588
|
+
* legacy approach - the simpler truncation method is now preferred.
|
|
589
|
+
*
|
|
590
|
+
* @param bytes - Bytes to hash
|
|
591
|
+
* @returns Tuple of [hash, bumpSeed] or null if no valid bump found
|
|
592
|
+
*/
|
|
593
|
+
function hashToBn254FieldSizeBe(bytes) {
|
|
594
|
+
let bumpSeed = 255;
|
|
595
|
+
while (bumpSeed >= 0) {
|
|
596
|
+
const inputWithBump = new Uint8Array(bytes.length + 1);
|
|
597
|
+
inputWithBump.set(bytes);
|
|
598
|
+
inputWithBump[bytes.length] = bumpSeed;
|
|
599
|
+
const hash = keccak_256(inputWithBump);
|
|
600
|
+
if (hash.length !== 32) throw new Error("Invalid hash length");
|
|
601
|
+
hash[0] = 0;
|
|
602
|
+
if (isSmallerThanFieldSize(hash)) return [hash, bumpSeed];
|
|
603
|
+
bumpSeed--;
|
|
604
|
+
}
|
|
605
|
+
return null;
|
|
606
|
+
}
|
|
607
|
+
/**
|
|
608
|
+
* Convert hex string to bytes.
|
|
609
|
+
*/
|
|
610
|
+
function hexToBytes(hex) {
|
|
611
|
+
const cleanHex = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
612
|
+
if (cleanHex.length % 2 !== 0) throw new Error("Invalid hex string length");
|
|
613
|
+
const bytes = new Uint8Array(cleanHex.length / 2);
|
|
614
|
+
for (let i = 0; i < bytes.length; i++) bytes[i] = parseInt(cleanHex.slice(i * 2, i * 2 + 2), 16);
|
|
615
|
+
return bytes;
|
|
616
|
+
}
|
|
617
|
+
/**
|
|
618
|
+
* Convert bytes to hex string (no 0x prefix).
|
|
619
|
+
*/
|
|
620
|
+
function bytesToHex(bytes) {
|
|
621
|
+
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
622
|
+
}
|
|
623
|
+
/**
|
|
624
|
+
* Convert bigint to hex string with 0x prefix.
|
|
625
|
+
*/
|
|
626
|
+
function toHex(value) {
|
|
627
|
+
return `0x${value.toString(16)}`;
|
|
628
|
+
}
|
|
629
|
+
/**
|
|
630
|
+
* Ensure value is an array.
|
|
631
|
+
*/
|
|
632
|
+
function toArray(value) {
|
|
633
|
+
return Array.isArray(value) ? value : [value];
|
|
634
|
+
}
|
|
635
|
+
/**
|
|
636
|
+
* Merge bytes arrays into one.
|
|
637
|
+
*/
|
|
638
|
+
function mergeBytes(arrays) {
|
|
639
|
+
const totalLength = arrays.reduce((acc, arr) => acc + arr.length, 0);
|
|
640
|
+
const result = new Uint8Array(totalLength);
|
|
641
|
+
let offset = 0;
|
|
642
|
+
for (const arr of arrays) {
|
|
643
|
+
result.set(arr, offset);
|
|
644
|
+
offset += arr.length;
|
|
645
|
+
}
|
|
646
|
+
return result;
|
|
647
|
+
}
|
|
648
|
+
/**
|
|
649
|
+
* Compare two byte arrays for equality.
|
|
650
|
+
*/
|
|
651
|
+
function bytesEqual(a, b) {
|
|
652
|
+
if (a.length !== b.length) return false;
|
|
653
|
+
for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
|
|
654
|
+
return true;
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* Pad bytes to a fixed length (right-pad with zeros).
|
|
658
|
+
*/
|
|
659
|
+
function padBytes(bytes, length) {
|
|
660
|
+
if (bytes.length >= length) return bytes;
|
|
661
|
+
const result = new Uint8Array(length);
|
|
662
|
+
result.set(bytes);
|
|
663
|
+
return result;
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* Push unique items to an array.
|
|
667
|
+
* Mutates the array in place.
|
|
668
|
+
*/
|
|
669
|
+
function pushUniqueItems(items, target) {
|
|
670
|
+
for (const item of items) if (!target.includes(item)) target.push(item);
|
|
671
|
+
}
|
|
672
|
+
/**
|
|
673
|
+
* Convert bytes to decimal string (for ZK circuit compatibility).
|
|
674
|
+
*/
|
|
675
|
+
function bytesToDecimalString(bytes) {
|
|
676
|
+
return bytesToBigIntBE(bytes).toString(10);
|
|
677
|
+
}
|
|
678
|
+
/**
|
|
679
|
+
* Validate that a hash value is within BN254 field size.
|
|
680
|
+
*/
|
|
681
|
+
function validateBN254Hash(hash) {
|
|
682
|
+
if (hash.length !== 32) return false;
|
|
683
|
+
return isSmallerThanFieldSize(hash);
|
|
684
|
+
}
|
|
685
|
+
/**
|
|
686
|
+
* Assert hash is valid BN254 field element.
|
|
687
|
+
*/
|
|
688
|
+
function assertValidBN254Hash(hash) {
|
|
689
|
+
if (!validateBN254Hash(hash)) throw new Error(`Invalid BN254 hash: must be 32 bytes and less than field size`);
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
//#endregion
|
|
693
|
+
//#region src/utils/address.ts
|
|
694
|
+
/**
|
|
695
|
+
* Address derivation utilities for Light Protocol.
|
|
696
|
+
*
|
|
697
|
+
* Light Protocol uses a custom address derivation scheme that produces
|
|
698
|
+
* addresses within the BN254 field for ZK circuit compatibility.
|
|
699
|
+
*/
|
|
700
|
+
/**
|
|
701
|
+
* Derive an address seed from seeds and program ID.
|
|
702
|
+
*
|
|
703
|
+
* This combines the program ID with user-provided seeds and hashes
|
|
704
|
+
* them to produce a 32-byte seed suitable for address derivation.
|
|
705
|
+
*
|
|
706
|
+
* @param seeds - User-provided seed bytes
|
|
707
|
+
* @param programId - The program ID that "owns" this address
|
|
708
|
+
* @returns 32-byte address seed
|
|
709
|
+
*/
|
|
710
|
+
function deriveAddressSeed(seeds, programId) {
|
|
711
|
+
const encoder = getAddressEncoder();
|
|
712
|
+
return hashvToBn254FieldSizeBe([Uint8Array.from(encoder.encode(programId)), ...seeds]);
|
|
713
|
+
}
|
|
714
|
+
/**
|
|
715
|
+
* Derive a compressed account address from a seed and address tree.
|
|
716
|
+
*
|
|
717
|
+
* @param seed - 32-byte seed (typically from deriveAddressSeed)
|
|
718
|
+
* @param addressMerkleTreePubkey - The address tree to derive from
|
|
719
|
+
* @returns Derived address as a Solana Address
|
|
720
|
+
*
|
|
721
|
+
* @example
|
|
722
|
+
* ```typescript
|
|
723
|
+
* import { deriveAddressSeed, deriveAddress } from '@cascade-fyi/compression-kit';
|
|
724
|
+
* import { address } from '@solana/kit';
|
|
725
|
+
*
|
|
726
|
+
* const programId = address('YourProgramId...');
|
|
727
|
+
* const seed = deriveAddressSeed([new TextEncoder().encode('my-seed')], programId);
|
|
728
|
+
* const compressedAddress = deriveAddress(seed);
|
|
729
|
+
* ```
|
|
730
|
+
*/
|
|
731
|
+
function deriveAddress(seed, addressMerkleTreePubkey) {
|
|
732
|
+
if (seed.length !== 32) throw new Error("Seed length must be 32 bytes");
|
|
733
|
+
const treePubkey = addressMerkleTreePubkey ?? defaultTestStateTreeAccounts().addressTree;
|
|
734
|
+
const encoder = getAddressEncoder();
|
|
735
|
+
const hashResult = hashToBn254FieldSizeBe(mergeBytes([Uint8Array.from(encoder.encode(treePubkey)), seed]));
|
|
736
|
+
if (hashResult === null) throw new Error("DeriveAddressError: Failed to find valid bump seed");
|
|
737
|
+
const [hash] = hashResult;
|
|
738
|
+
return address(bs58.encode(hash));
|
|
739
|
+
}
|
|
740
|
+
/**
|
|
741
|
+
* Derive address seed using V2 method (no program ID in combined seeds).
|
|
742
|
+
*
|
|
743
|
+
* @param seeds - Seeds to hash together
|
|
744
|
+
* @returns 32-byte address seed
|
|
745
|
+
*/
|
|
746
|
+
function deriveAddressSeedV2(seeds) {
|
|
747
|
+
return hashvToBn254FieldSizeBeWithBump(seeds);
|
|
748
|
+
}
|
|
749
|
+
/**
|
|
750
|
+
* Derive address using V2 method (matches Rust derive_address_from_seed).
|
|
751
|
+
*
|
|
752
|
+
* @param addressSeed - 32-byte address seed
|
|
753
|
+
* @param addressMerkleTreePubkey - Address tree pubkey
|
|
754
|
+
* @param programId - Program ID
|
|
755
|
+
* @returns Derived address
|
|
756
|
+
*/
|
|
757
|
+
function deriveAddressV2(addressSeed, addressMerkleTreePubkey, programId) {
|
|
758
|
+
if (addressSeed.length !== 32) throw new Error("Address seed length must be 32 bytes");
|
|
759
|
+
const encoder = getAddressEncoder();
|
|
760
|
+
const hash = hashvToBn254FieldSizeBeWithBump([
|
|
761
|
+
addressSeed,
|
|
762
|
+
Uint8Array.from(encoder.encode(addressMerkleTreePubkey)),
|
|
763
|
+
Uint8Array.from(encoder.encode(programId))
|
|
764
|
+
]);
|
|
765
|
+
return address(bs58.encode(hash));
|
|
766
|
+
}
|
|
767
|
+
/**
|
|
768
|
+
* Get the index of an address in an array, adding it if not present.
|
|
769
|
+
*/
|
|
770
|
+
function getIndexOrAdd(accounts, pubkey) {
|
|
771
|
+
const existingIndex = accounts.indexOf(pubkey);
|
|
772
|
+
if (existingIndex !== -1) return existingIndex;
|
|
773
|
+
accounts.push(pubkey);
|
|
774
|
+
return accounts.length - 1;
|
|
775
|
+
}
|
|
776
|
+
/**
|
|
777
|
+
* Pack new address params for instruction data.
|
|
778
|
+
*
|
|
779
|
+
* Converts NewAddressParams to NewAddressParamsPacked by replacing
|
|
780
|
+
* pubkeys with their indices in the remaining accounts array.
|
|
781
|
+
*
|
|
782
|
+
* @param newAddressParams - Array of new address parameters
|
|
783
|
+
* @param remainingAccounts - Existing remaining accounts (will be modified)
|
|
784
|
+
* @returns Packed params and updated remaining accounts
|
|
785
|
+
*/
|
|
786
|
+
function packNewAddressParams(newAddressParams, remainingAccounts) {
|
|
787
|
+
const accounts = [...remainingAccounts];
|
|
788
|
+
const newAddressParamsPacked = newAddressParams.map((params) => ({
|
|
789
|
+
seed: params.seed,
|
|
790
|
+
addressMerkleTreeRootIndex: params.addressMerkleTreeRootIndex,
|
|
791
|
+
addressMerkleTreeAccountIndex: 0,
|
|
792
|
+
addressQueueAccountIndex: 0
|
|
793
|
+
}));
|
|
794
|
+
for (let i = 0; i < newAddressParams.length; i++) newAddressParamsPacked[i].addressMerkleTreeAccountIndex = getIndexOrAdd(accounts, newAddressParams[i].addressMerkleTreePubkey);
|
|
795
|
+
for (let i = 0; i < newAddressParams.length; i++) newAddressParamsPacked[i].addressQueueAccountIndex = getIndexOrAdd(accounts, newAddressParams[i].addressQueuePubkey);
|
|
796
|
+
return {
|
|
797
|
+
newAddressParamsPacked,
|
|
798
|
+
remainingAccounts: accounts
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
/**
|
|
802
|
+
* Convert an address to bytes.
|
|
803
|
+
*/
|
|
804
|
+
function addressToBytes(addr) {
|
|
805
|
+
const encoder = getAddressEncoder();
|
|
806
|
+
return Uint8Array.from(encoder.encode(addr));
|
|
807
|
+
}
|
|
808
|
+
/**
|
|
809
|
+
* Convert bytes to an address.
|
|
810
|
+
*
|
|
811
|
+
* Note: This performs base58 encoding on the bytes. The bytes should
|
|
812
|
+
* be a valid 32-byte representation of an address.
|
|
813
|
+
*/
|
|
814
|
+
function bytesToAddress(bytes) {
|
|
815
|
+
if (bytes.length !== 32) throw new Error("Address must be 32 bytes");
|
|
816
|
+
return address(bs58.encode(bytes));
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
//#endregion
|
|
820
|
+
//#region src/utils/instruction.ts
|
|
821
|
+
/**
|
|
822
|
+
* Instruction building utilities for Light Protocol.
|
|
823
|
+
*
|
|
824
|
+
* Uses Solana Kit patterns:
|
|
825
|
+
* - AccountMeta with Address and AccountRole
|
|
826
|
+
* - Pure data objects instead of classes where possible
|
|
827
|
+
*/
|
|
828
|
+
/**
|
|
829
|
+
* Create a basic system account config.
|
|
830
|
+
*/
|
|
831
|
+
function createSystemAccountConfig(selfProgram) {
|
|
832
|
+
return { selfProgram };
|
|
833
|
+
}
|
|
834
|
+
/**
|
|
835
|
+
* Create a system account config with CPI context.
|
|
836
|
+
*/
|
|
837
|
+
function createSystemAccountConfigWithCpi(selfProgram, cpiContext) {
|
|
838
|
+
return {
|
|
839
|
+
selfProgram,
|
|
840
|
+
cpiContext
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
/**
|
|
844
|
+
* Derive the CPI signer PDA for a program.
|
|
845
|
+
*/
|
|
846
|
+
async function getCpiSignerPda(selfProgram) {
|
|
847
|
+
const [pda] = await getProgramDerivedAddress({
|
|
848
|
+
programAddress: selfProgram,
|
|
849
|
+
seeds: [new TextEncoder().encode("cpi_authority")]
|
|
850
|
+
});
|
|
851
|
+
return pda;
|
|
852
|
+
}
|
|
853
|
+
/**
|
|
854
|
+
* Derive the account compression authority PDA.
|
|
855
|
+
*/
|
|
856
|
+
async function getAccountCompressionAuthority$1() {
|
|
857
|
+
const [pda] = await getProgramDerivedAddress({
|
|
858
|
+
programAddress: LIGHT_SYSTEM_PROGRAM,
|
|
859
|
+
seeds: [new TextEncoder().encode("cpi_authority")]
|
|
860
|
+
});
|
|
861
|
+
return pda;
|
|
862
|
+
}
|
|
863
|
+
/**
|
|
864
|
+
* Get the system program address.
|
|
865
|
+
*/
|
|
866
|
+
function getSystemProgram() {
|
|
867
|
+
return "11111111111111111111111111111111";
|
|
868
|
+
}
|
|
869
|
+
/**
|
|
870
|
+
* Build Light System account metas (V1 layout).
|
|
871
|
+
*
|
|
872
|
+
* @param config - System account configuration
|
|
873
|
+
* @returns Array of account metas for Light System Program
|
|
874
|
+
*
|
|
875
|
+
* @example
|
|
876
|
+
* ```typescript
|
|
877
|
+
* import { getLightSystemAccountMetas, createSystemAccountConfig } from '@cascade-fyi/compression-kit';
|
|
878
|
+
* import { address } from '@solana/kit';
|
|
879
|
+
*
|
|
880
|
+
* const programId = address('YourProgramId...');
|
|
881
|
+
* const config = createSystemAccountConfig(programId);
|
|
882
|
+
* const metas = await getLightSystemAccountMetas(config);
|
|
883
|
+
* ```
|
|
884
|
+
*/
|
|
885
|
+
async function getLightSystemAccountMetas(config) {
|
|
886
|
+
const cpiSigner = await getCpiSignerPda(config.selfProgram);
|
|
887
|
+
const compressionAuthority = await getAccountCompressionAuthority$1();
|
|
888
|
+
const metas = [
|
|
889
|
+
{
|
|
890
|
+
address: LIGHT_SYSTEM_PROGRAM,
|
|
891
|
+
role: AccountRole.READONLY
|
|
892
|
+
},
|
|
893
|
+
{
|
|
894
|
+
address: cpiSigner,
|
|
895
|
+
role: AccountRole.READONLY
|
|
896
|
+
},
|
|
897
|
+
{
|
|
898
|
+
address: REGISTERED_PROGRAM_PDA,
|
|
899
|
+
role: AccountRole.READONLY
|
|
900
|
+
},
|
|
901
|
+
{
|
|
902
|
+
address: NOOP_PROGRAM,
|
|
903
|
+
role: AccountRole.READONLY
|
|
904
|
+
},
|
|
905
|
+
{
|
|
906
|
+
address: compressionAuthority,
|
|
907
|
+
role: AccountRole.READONLY
|
|
908
|
+
},
|
|
909
|
+
{
|
|
910
|
+
address: ACCOUNT_COMPRESSION_PROGRAM,
|
|
911
|
+
role: AccountRole.READONLY
|
|
912
|
+
},
|
|
913
|
+
{
|
|
914
|
+
address: config.selfProgram,
|
|
915
|
+
role: AccountRole.READONLY
|
|
916
|
+
}
|
|
917
|
+
];
|
|
918
|
+
if (config.solPoolPda) metas.push({
|
|
919
|
+
address: config.solPoolPda,
|
|
920
|
+
role: AccountRole.WRITABLE
|
|
921
|
+
});
|
|
922
|
+
if (config.solCompressionRecipient) metas.push({
|
|
923
|
+
address: config.solCompressionRecipient,
|
|
924
|
+
role: AccountRole.WRITABLE
|
|
925
|
+
});
|
|
926
|
+
metas.push({
|
|
927
|
+
address: getSystemProgram(),
|
|
928
|
+
role: AccountRole.READONLY
|
|
929
|
+
});
|
|
930
|
+
if (config.cpiContext) metas.push({
|
|
931
|
+
address: config.cpiContext,
|
|
932
|
+
role: AccountRole.WRITABLE
|
|
933
|
+
});
|
|
934
|
+
return metas;
|
|
935
|
+
}
|
|
936
|
+
/**
|
|
937
|
+
* Build Light System account metas (V2 layout - no noop program).
|
|
938
|
+
*/
|
|
939
|
+
async function getLightSystemAccountMetasV2(config) {
|
|
940
|
+
const cpiSigner = await getCpiSignerPda(config.selfProgram);
|
|
941
|
+
const compressionAuthority = await getAccountCompressionAuthority$1();
|
|
942
|
+
const metas = [
|
|
943
|
+
{
|
|
944
|
+
address: LIGHT_SYSTEM_PROGRAM,
|
|
945
|
+
role: AccountRole.READONLY
|
|
946
|
+
},
|
|
947
|
+
{
|
|
948
|
+
address: cpiSigner,
|
|
949
|
+
role: AccountRole.READONLY
|
|
950
|
+
},
|
|
951
|
+
{
|
|
952
|
+
address: REGISTERED_PROGRAM_PDA,
|
|
953
|
+
role: AccountRole.READONLY
|
|
954
|
+
},
|
|
955
|
+
{
|
|
956
|
+
address: compressionAuthority,
|
|
957
|
+
role: AccountRole.READONLY
|
|
958
|
+
},
|
|
959
|
+
{
|
|
960
|
+
address: ACCOUNT_COMPRESSION_PROGRAM,
|
|
961
|
+
role: AccountRole.READONLY
|
|
962
|
+
},
|
|
963
|
+
{
|
|
964
|
+
address: getSystemProgram(),
|
|
965
|
+
role: AccountRole.READONLY
|
|
966
|
+
}
|
|
967
|
+
];
|
|
968
|
+
if (config.solPoolPda) metas.push({
|
|
969
|
+
address: config.solPoolPda,
|
|
970
|
+
role: AccountRole.WRITABLE
|
|
971
|
+
});
|
|
972
|
+
if (config.solCompressionRecipient) metas.push({
|
|
973
|
+
address: config.solCompressionRecipient,
|
|
974
|
+
role: AccountRole.WRITABLE
|
|
975
|
+
});
|
|
976
|
+
if (config.cpiContext) metas.push({
|
|
977
|
+
address: config.cpiContext,
|
|
978
|
+
role: AccountRole.WRITABLE
|
|
979
|
+
});
|
|
980
|
+
return metas;
|
|
981
|
+
}
|
|
982
|
+
/**
|
|
983
|
+
* Helper class for building remaining accounts for Light Protocol instructions.
|
|
984
|
+
*
|
|
985
|
+
* Manages three categories of accounts:
|
|
986
|
+
* 1. Pre-accounts: Signers and other accounts that come first
|
|
987
|
+
* 2. System accounts: Light System Program static accounts
|
|
988
|
+
* 3. Packed accounts: Dynamic accounts indexed by pubkey
|
|
989
|
+
*/
|
|
990
|
+
var PackedAccounts = class PackedAccounts {
|
|
991
|
+
constructor() {
|
|
992
|
+
this.preAccounts = [];
|
|
993
|
+
this.systemAccounts = [];
|
|
994
|
+
this.nextIndex = 0;
|
|
995
|
+
this.accountMap = /* @__PURE__ */ new Map();
|
|
996
|
+
}
|
|
997
|
+
/**
|
|
998
|
+
* Create a new PackedAccounts with system accounts (V1 layout).
|
|
999
|
+
*/
|
|
1000
|
+
static async newWithSystemAccounts(config) {
|
|
1001
|
+
const instance = new PackedAccounts();
|
|
1002
|
+
await instance.addSystemAccounts(config);
|
|
1003
|
+
return instance;
|
|
1004
|
+
}
|
|
1005
|
+
/**
|
|
1006
|
+
* Create a new PackedAccounts with system accounts (V2 layout).
|
|
1007
|
+
*/
|
|
1008
|
+
static async newWithSystemAccountsV2(config) {
|
|
1009
|
+
const instance = new PackedAccounts();
|
|
1010
|
+
await instance.addSystemAccountsV2(config);
|
|
1011
|
+
return instance;
|
|
1012
|
+
}
|
|
1013
|
+
/**
|
|
1014
|
+
* Add a signer to pre-accounts (readonly).
|
|
1015
|
+
*/
|
|
1016
|
+
addPreAccountsSigner(pubkey) {
|
|
1017
|
+
this.preAccounts.push({
|
|
1018
|
+
address: pubkey,
|
|
1019
|
+
role: AccountRole.READONLY_SIGNER
|
|
1020
|
+
});
|
|
1021
|
+
}
|
|
1022
|
+
/**
|
|
1023
|
+
* Add a writable signer to pre-accounts.
|
|
1024
|
+
*/
|
|
1025
|
+
addPreAccountsSignerMut(pubkey) {
|
|
1026
|
+
this.preAccounts.push({
|
|
1027
|
+
address: pubkey,
|
|
1028
|
+
role: AccountRole.WRITABLE_SIGNER
|
|
1029
|
+
});
|
|
1030
|
+
}
|
|
1031
|
+
/**
|
|
1032
|
+
* Add an account meta to pre-accounts.
|
|
1033
|
+
*/
|
|
1034
|
+
addPreAccountsMeta(accountMeta) {
|
|
1035
|
+
this.preAccounts.push(accountMeta);
|
|
1036
|
+
}
|
|
1037
|
+
/**
|
|
1038
|
+
* Add system accounts (V1 layout).
|
|
1039
|
+
*/
|
|
1040
|
+
async addSystemAccounts(config) {
|
|
1041
|
+
const metas = await getLightSystemAccountMetas(config);
|
|
1042
|
+
this.systemAccounts.push(...metas);
|
|
1043
|
+
}
|
|
1044
|
+
/**
|
|
1045
|
+
* Add system accounts (V2 layout).
|
|
1046
|
+
*/
|
|
1047
|
+
async addSystemAccountsV2(config) {
|
|
1048
|
+
const metas = await getLightSystemAccountMetasV2(config);
|
|
1049
|
+
this.systemAccounts.push(...metas);
|
|
1050
|
+
}
|
|
1051
|
+
/**
|
|
1052
|
+
* Insert or get index for a writable account.
|
|
1053
|
+
*/
|
|
1054
|
+
insertOrGet(pubkey) {
|
|
1055
|
+
return this.insertOrGetConfig(pubkey, false, true);
|
|
1056
|
+
}
|
|
1057
|
+
/**
|
|
1058
|
+
* Insert or get index for a readonly account.
|
|
1059
|
+
*/
|
|
1060
|
+
insertOrGetReadOnly(pubkey) {
|
|
1061
|
+
return this.insertOrGetConfig(pubkey, false, false);
|
|
1062
|
+
}
|
|
1063
|
+
/**
|
|
1064
|
+
* Insert or get index with full configuration.
|
|
1065
|
+
*/
|
|
1066
|
+
insertOrGetConfig(pubkey, isSigner, isWritable) {
|
|
1067
|
+
const key = pubkey;
|
|
1068
|
+
const existing = this.accountMap.get(key);
|
|
1069
|
+
if (existing) return existing[0];
|
|
1070
|
+
const index = this.nextIndex++;
|
|
1071
|
+
let role;
|
|
1072
|
+
if (isSigner && isWritable) role = AccountRole.WRITABLE_SIGNER;
|
|
1073
|
+
else if (isSigner) role = AccountRole.READONLY_SIGNER;
|
|
1074
|
+
else if (isWritable) role = AccountRole.WRITABLE;
|
|
1075
|
+
else role = AccountRole.READONLY;
|
|
1076
|
+
const meta = {
|
|
1077
|
+
address: pubkey,
|
|
1078
|
+
role
|
|
1079
|
+
};
|
|
1080
|
+
this.accountMap.set(key, [index, meta]);
|
|
1081
|
+
return index;
|
|
1082
|
+
}
|
|
1083
|
+
/**
|
|
1084
|
+
* Get packed accounts sorted by insertion order.
|
|
1085
|
+
*/
|
|
1086
|
+
getPackedAccountMetas() {
|
|
1087
|
+
const entries = Array.from(this.accountMap.entries());
|
|
1088
|
+
entries.sort((a, b) => a[1][0] - b[1][0]);
|
|
1089
|
+
return entries.map(([, [, meta]]) => meta);
|
|
1090
|
+
}
|
|
1091
|
+
/**
|
|
1092
|
+
* Get offsets for system and packed accounts.
|
|
1093
|
+
*/
|
|
1094
|
+
getOffsets() {
|
|
1095
|
+
const systemStart = this.preAccounts.length;
|
|
1096
|
+
return [systemStart, systemStart + this.systemAccounts.length];
|
|
1097
|
+
}
|
|
1098
|
+
/**
|
|
1099
|
+
* Build final remaining accounts array with offsets.
|
|
1100
|
+
*/
|
|
1101
|
+
toAccountMetas() {
|
|
1102
|
+
const packed = this.getPackedAccountMetas();
|
|
1103
|
+
const [systemStart, packedStart] = this.getOffsets();
|
|
1104
|
+
return {
|
|
1105
|
+
remainingAccounts: [
|
|
1106
|
+
...this.preAccounts,
|
|
1107
|
+
...this.systemAccounts,
|
|
1108
|
+
...packed
|
|
1109
|
+
],
|
|
1110
|
+
systemStart,
|
|
1111
|
+
packedStart
|
|
1112
|
+
};
|
|
1113
|
+
}
|
|
1114
|
+
};
|
|
1115
|
+
|
|
1116
|
+
//#endregion
|
|
1117
|
+
//#region src/rpc.ts
|
|
1118
|
+
let requestId = 0n;
|
|
1119
|
+
/**
|
|
1120
|
+
* Light Protocol Photon RPC client.
|
|
1121
|
+
*
|
|
1122
|
+
* Provides methods for querying compressed accounts, requesting validity proofs,
|
|
1123
|
+
* and other indexer operations.
|
|
1124
|
+
*/
|
|
1125
|
+
var PhotonRpc = class {
|
|
1126
|
+
constructor(endpoint, headers) {
|
|
1127
|
+
this.endpoint = endpoint;
|
|
1128
|
+
this.headers = {
|
|
1129
|
+
"Content-Type": "application/json",
|
|
1130
|
+
Accept: "application/json",
|
|
1131
|
+
...headers
|
|
1132
|
+
};
|
|
1133
|
+
}
|
|
1134
|
+
/**
|
|
1135
|
+
* Make a JSON-RPC request to the Photon indexer.
|
|
1136
|
+
*/
|
|
1137
|
+
async request(method, params) {
|
|
1138
|
+
const body = {
|
|
1139
|
+
jsonrpc: "2.0",
|
|
1140
|
+
id: (++requestId).toString(),
|
|
1141
|
+
method,
|
|
1142
|
+
params
|
|
1143
|
+
};
|
|
1144
|
+
const response = await fetch(this.endpoint, {
|
|
1145
|
+
method: "POST",
|
|
1146
|
+
headers: this.headers,
|
|
1147
|
+
body: JSON.stringify(body)
|
|
1148
|
+
});
|
|
1149
|
+
if (!response.ok) throw createRpcError(RpcErrorCode.RPC_INVALID, "request", `HTTP ${response.status}: ${response.statusText}`);
|
|
1150
|
+
const json = await response.json();
|
|
1151
|
+
if (json.error) throw createRpcError(RpcErrorCode.RPC_INVALID, method, `${json.error.code}: ${json.error.message}`);
|
|
1152
|
+
return json.result;
|
|
1153
|
+
}
|
|
1154
|
+
/**
|
|
1155
|
+
* Request with context wrapper.
|
|
1156
|
+
*/
|
|
1157
|
+
async requestWithContext(method, params) {
|
|
1158
|
+
return this.request(method, params);
|
|
1159
|
+
}
|
|
1160
|
+
/**
|
|
1161
|
+
* Get indexer health status.
|
|
1162
|
+
*/
|
|
1163
|
+
async getIndexerHealth() {
|
|
1164
|
+
return this.request("getIndexerHealth", {});
|
|
1165
|
+
}
|
|
1166
|
+
/**
|
|
1167
|
+
* Get current indexer slot.
|
|
1168
|
+
*/
|
|
1169
|
+
async getIndexerSlot() {
|
|
1170
|
+
return this.request("getIndexerSlot", {});
|
|
1171
|
+
}
|
|
1172
|
+
/**
|
|
1173
|
+
* Get a single compressed account by address or hash.
|
|
1174
|
+
*/
|
|
1175
|
+
async getCompressedAccount(addressOrHash) {
|
|
1176
|
+
const method = versionedEndpoint("getCompressedAccount");
|
|
1177
|
+
const params = {};
|
|
1178
|
+
if (addressOrHash.address) params.address = addressOrHash.address;
|
|
1179
|
+
if (addressOrHash.hash !== void 0) params.hash = encodeBN254(addressOrHash.hash);
|
|
1180
|
+
const result = await this.requestWithContext(method, params);
|
|
1181
|
+
if (!result.value) return null;
|
|
1182
|
+
return parseCompressedAccount(result.value);
|
|
1183
|
+
}
|
|
1184
|
+
/**
|
|
1185
|
+
* Get multiple compressed accounts by hashes.
|
|
1186
|
+
*/
|
|
1187
|
+
async getMultipleCompressedAccounts(hashes) {
|
|
1188
|
+
const method = versionedEndpoint("getMultipleCompressedAccounts");
|
|
1189
|
+
const params = { hashes: hashes.map(encodeBN254) };
|
|
1190
|
+
return (await this.requestWithContext(method, params)).value.items.map(parseCompressedAccount);
|
|
1191
|
+
}
|
|
1192
|
+
/**
|
|
1193
|
+
* Get compressed accounts by owner.
|
|
1194
|
+
*/
|
|
1195
|
+
async getCompressedAccountsByOwner(owner, config) {
|
|
1196
|
+
const method = versionedEndpoint("getCompressedAccountsByOwner");
|
|
1197
|
+
const params = {
|
|
1198
|
+
owner,
|
|
1199
|
+
cursor: config?.cursor,
|
|
1200
|
+
limit: config?.limit
|
|
1201
|
+
};
|
|
1202
|
+
const result = await this.requestWithContext(method, params);
|
|
1203
|
+
return {
|
|
1204
|
+
items: result.value.items.map(parseCompressedAccount),
|
|
1205
|
+
cursor: result.value.cursor
|
|
1206
|
+
};
|
|
1207
|
+
}
|
|
1208
|
+
/**
|
|
1209
|
+
* Get compressed SOL balance by owner.
|
|
1210
|
+
*/
|
|
1211
|
+
async getCompressedBalanceByOwner(owner) {
|
|
1212
|
+
const method = versionedEndpoint("getCompressedBalanceByOwner");
|
|
1213
|
+
const result = await this.requestWithContext(method, { owner });
|
|
1214
|
+
return BigInt(result.value);
|
|
1215
|
+
}
|
|
1216
|
+
/**
|
|
1217
|
+
* Get merkle proof for a compressed account.
|
|
1218
|
+
*/
|
|
1219
|
+
async getCompressedAccountProof(hash) {
|
|
1220
|
+
const method = versionedEndpoint("getCompressedAccountProof");
|
|
1221
|
+
const params = { hash: encodeBN254(hash) };
|
|
1222
|
+
return parseMerkleProof((await this.requestWithContext(method, params)).value);
|
|
1223
|
+
}
|
|
1224
|
+
/**
|
|
1225
|
+
* Get merkle proofs for multiple compressed accounts.
|
|
1226
|
+
*/
|
|
1227
|
+
async getMultipleCompressedAccountProofs(hashes) {
|
|
1228
|
+
const method = versionedEndpoint("getMultipleCompressedAccountProofs");
|
|
1229
|
+
const params = { hashes: hashes.map(encodeBN254) };
|
|
1230
|
+
return (await this.requestWithContext(method, params)).value.map(parseMerkleProof);
|
|
1231
|
+
}
|
|
1232
|
+
/**
|
|
1233
|
+
* Get validity proof for compressed accounts and/or new addresses.
|
|
1234
|
+
*
|
|
1235
|
+
* This is the main method for obtaining ZK proofs needed to use
|
|
1236
|
+
* compressed accounts in transactions.
|
|
1237
|
+
*/
|
|
1238
|
+
async getValidityProof(hashes, newAddresses) {
|
|
1239
|
+
const method = versionedEndpoint("getValidityProof");
|
|
1240
|
+
const params = {
|
|
1241
|
+
hashes: hashes.map((h) => ({
|
|
1242
|
+
hash: encodeBN254(h.hash),
|
|
1243
|
+
tree: h.stateTreeInfo.tree,
|
|
1244
|
+
queue: h.stateTreeInfo.queue
|
|
1245
|
+
})),
|
|
1246
|
+
newAddresses: newAddresses.map((a) => ({
|
|
1247
|
+
address: encodeBN254(a.address),
|
|
1248
|
+
tree: a.addressTreeInfo.tree,
|
|
1249
|
+
queue: a.addressTreeInfo.queue
|
|
1250
|
+
}))
|
|
1251
|
+
};
|
|
1252
|
+
return parseValidityProof((await this.requestWithContext(method, params)).value);
|
|
1253
|
+
}
|
|
1254
|
+
/**
|
|
1255
|
+
* Get compressed token accounts by owner.
|
|
1256
|
+
*/
|
|
1257
|
+
async getCompressedTokenAccountsByOwner(owner, config) {
|
|
1258
|
+
const method = versionedEndpoint("getCompressedTokenAccountsByOwner");
|
|
1259
|
+
const params = {
|
|
1260
|
+
owner,
|
|
1261
|
+
mint: config?.mint,
|
|
1262
|
+
cursor: config?.cursor,
|
|
1263
|
+
limit: config?.limit
|
|
1264
|
+
};
|
|
1265
|
+
const result = await this.requestWithContext(method, params);
|
|
1266
|
+
return {
|
|
1267
|
+
items: result.value.items.map(parseTokenAccount),
|
|
1268
|
+
cursor: result.value.cursor
|
|
1269
|
+
};
|
|
1270
|
+
}
|
|
1271
|
+
/**
|
|
1272
|
+
* Get compressed token balances by owner.
|
|
1273
|
+
*/
|
|
1274
|
+
async getCompressedTokenBalancesByOwner(owner, config) {
|
|
1275
|
+
const method = versionedEndpoint("getCompressedTokenBalancesByOwner");
|
|
1276
|
+
const params = {
|
|
1277
|
+
owner,
|
|
1278
|
+
mint: config?.mint,
|
|
1279
|
+
cursor: config?.cursor,
|
|
1280
|
+
limit: config?.limit
|
|
1281
|
+
};
|
|
1282
|
+
const result = await this.requestWithContext(method, params);
|
|
1283
|
+
return {
|
|
1284
|
+
items: result.value.items.map((item) => ({
|
|
1285
|
+
balance: BigInt(item.balance),
|
|
1286
|
+
mint: address(item.mint)
|
|
1287
|
+
})),
|
|
1288
|
+
cursor: result.value.cursor
|
|
1289
|
+
};
|
|
1290
|
+
}
|
|
1291
|
+
/**
|
|
1292
|
+
* Get compression signatures for an account hash.
|
|
1293
|
+
*/
|
|
1294
|
+
async getCompressionSignaturesForAccount(hash) {
|
|
1295
|
+
const method = "getCompressionSignaturesForAccount";
|
|
1296
|
+
const params = { hash: encodeBN254(hash) };
|
|
1297
|
+
return (await this.requestWithContext(method, params)).value.items;
|
|
1298
|
+
}
|
|
1299
|
+
/**
|
|
1300
|
+
* Get compression signatures for an address.
|
|
1301
|
+
*/
|
|
1302
|
+
async getCompressionSignaturesForAddress(addr, options) {
|
|
1303
|
+
const method = "getCompressionSignaturesForAddress";
|
|
1304
|
+
const params = {
|
|
1305
|
+
address: addr,
|
|
1306
|
+
cursor: options?.cursor,
|
|
1307
|
+
limit: options?.limit
|
|
1308
|
+
};
|
|
1309
|
+
const result = await this.requestWithContext(method, params);
|
|
1310
|
+
return {
|
|
1311
|
+
items: result.value.items,
|
|
1312
|
+
cursor: result.value.cursor
|
|
1313
|
+
};
|
|
1314
|
+
}
|
|
1315
|
+
/**
|
|
1316
|
+
* Get compression signatures for an owner.
|
|
1317
|
+
*/
|
|
1318
|
+
async getCompressionSignaturesForOwner(owner, options) {
|
|
1319
|
+
const method = "getCompressionSignaturesForOwner";
|
|
1320
|
+
const params = {
|
|
1321
|
+
owner,
|
|
1322
|
+
cursor: options?.cursor,
|
|
1323
|
+
limit: options?.limit
|
|
1324
|
+
};
|
|
1325
|
+
const result = await this.requestWithContext(method, params);
|
|
1326
|
+
return {
|
|
1327
|
+
items: result.value.items,
|
|
1328
|
+
cursor: result.value.cursor
|
|
1329
|
+
};
|
|
1330
|
+
}
|
|
1331
|
+
/**
|
|
1332
|
+
* Get latest non-voting signatures (compression transactions).
|
|
1333
|
+
*/
|
|
1334
|
+
async getLatestCompressionSignatures(cursor, limit) {
|
|
1335
|
+
const method = "getLatestCompressionSignatures";
|
|
1336
|
+
const params = {
|
|
1337
|
+
cursor,
|
|
1338
|
+
limit
|
|
1339
|
+
};
|
|
1340
|
+
const result = await this.requestWithContext(method, params);
|
|
1341
|
+
return {
|
|
1342
|
+
items: result.value.items,
|
|
1343
|
+
cursor: result.value.cursor
|
|
1344
|
+
};
|
|
1345
|
+
}
|
|
1346
|
+
};
|
|
1347
|
+
/**
|
|
1348
|
+
* Encode BN254 to base58 string for RPC params.
|
|
1349
|
+
*/
|
|
1350
|
+
function encodeBN254(value) {
|
|
1351
|
+
const bytes = new Uint8Array(32);
|
|
1352
|
+
let remaining = value;
|
|
1353
|
+
for (let i = 31; i >= 0; i--) {
|
|
1354
|
+
bytes[i] = Number(remaining & 255n);
|
|
1355
|
+
remaining >>= 8n;
|
|
1356
|
+
}
|
|
1357
|
+
return bs58.encode(bytes);
|
|
1358
|
+
}
|
|
1359
|
+
function parseCompressedAccount(raw) {
|
|
1360
|
+
const treeInfo = parseTreeInfo(raw);
|
|
1361
|
+
return {
|
|
1362
|
+
owner: address(raw.owner),
|
|
1363
|
+
lamports: BigInt(raw.lamports),
|
|
1364
|
+
address: raw.address ? base58ToBytes(raw.address) : null,
|
|
1365
|
+
data: raw.data ? {
|
|
1366
|
+
discriminator: hexToBytes$1(raw.data.discriminator),
|
|
1367
|
+
data: base64ToBytes(raw.data.data),
|
|
1368
|
+
dataHash: base58ToBytes(raw.data.dataHash)
|
|
1369
|
+
} : null,
|
|
1370
|
+
treeInfo,
|
|
1371
|
+
hash: createBN254(raw.hash, "base58"),
|
|
1372
|
+
leafIndex: raw.leafIndex,
|
|
1373
|
+
proveByIndex: raw.proveByIndex ?? false,
|
|
1374
|
+
readOnly: false
|
|
1375
|
+
};
|
|
1376
|
+
}
|
|
1377
|
+
function parseTreeInfo(raw) {
|
|
1378
|
+
if (featureFlags.isV2() && "merkleContext" in raw && raw.merkleContext) {
|
|
1379
|
+
const ctx = raw.merkleContext;
|
|
1380
|
+
return {
|
|
1381
|
+
tree: address(ctx.tree),
|
|
1382
|
+
queue: address(ctx.queue),
|
|
1383
|
+
treeType: ctx.treeType,
|
|
1384
|
+
cpiContext: ctx.cpiContext ? address(ctx.cpiContext) : void 0,
|
|
1385
|
+
nextTreeInfo: null
|
|
1386
|
+
};
|
|
1387
|
+
}
|
|
1388
|
+
if ("treeContext" in raw && raw.treeContext) {
|
|
1389
|
+
const ctx = raw.treeContext;
|
|
1390
|
+
return {
|
|
1391
|
+
tree: address(ctx.tree),
|
|
1392
|
+
queue: address(ctx.queue),
|
|
1393
|
+
treeType: ctx.treeType,
|
|
1394
|
+
cpiContext: ctx.cpiContext ? address(ctx.cpiContext) : void 0,
|
|
1395
|
+
nextTreeInfo: null
|
|
1396
|
+
};
|
|
1397
|
+
}
|
|
1398
|
+
const tree = "tree" in raw && raw.tree ? raw.tree : "merkleTree" in raw ? raw.merkleTree ?? "" : "";
|
|
1399
|
+
return {
|
|
1400
|
+
tree: address(tree),
|
|
1401
|
+
queue: address(tree),
|
|
1402
|
+
treeType: 1,
|
|
1403
|
+
nextTreeInfo: null
|
|
1404
|
+
};
|
|
1405
|
+
}
|
|
1406
|
+
function parseMerkleProof(raw) {
|
|
1407
|
+
return {
|
|
1408
|
+
treeInfo: parseTreeInfo(raw),
|
|
1409
|
+
hash: createBN254(raw.hash, "base58"),
|
|
1410
|
+
leafIndex: raw.leafIndex,
|
|
1411
|
+
proveByIndex: raw.proveByIndex ?? false,
|
|
1412
|
+
merkleProof: raw.proof.map((p) => createBN254(p, "base58")),
|
|
1413
|
+
rootIndex: raw.rootSeq,
|
|
1414
|
+
root: createBN254(raw.root, "base58")
|
|
1415
|
+
};
|
|
1416
|
+
}
|
|
1417
|
+
function parseValidityProof(raw) {
|
|
1418
|
+
const proof = raw.compressedProof ? {
|
|
1419
|
+
a: new Uint8Array(raw.compressedProof.a),
|
|
1420
|
+
b: new Uint8Array(raw.compressedProof.b),
|
|
1421
|
+
c: new Uint8Array(raw.compressedProof.c)
|
|
1422
|
+
} : null;
|
|
1423
|
+
if (raw.accounts) return {
|
|
1424
|
+
compressedProof: proof,
|
|
1425
|
+
roots: raw.accounts.map((a) => createBN254(a.root, "base58")),
|
|
1426
|
+
rootIndices: raw.accounts.map((a) => a.rootIndex.rootIndex),
|
|
1427
|
+
leafIndices: raw.accounts.map((a) => a.leafIndex),
|
|
1428
|
+
leaves: raw.accounts.map((a) => createBN254(a.hash, "base58")),
|
|
1429
|
+
treeInfos: raw.accounts.map((a) => ({
|
|
1430
|
+
tree: address(a.merkleContext.tree),
|
|
1431
|
+
queue: address(a.merkleContext.queue),
|
|
1432
|
+
treeType: a.merkleContext.treeType,
|
|
1433
|
+
nextTreeInfo: null
|
|
1434
|
+
})),
|
|
1435
|
+
proveByIndices: raw.accounts.map((a) => a.rootIndex.proveByIndex)
|
|
1436
|
+
};
|
|
1437
|
+
return {
|
|
1438
|
+
compressedProof: proof,
|
|
1439
|
+
roots: raw.roots.map((r) => createBN254(r, "base58")),
|
|
1440
|
+
rootIndices: raw.rootIndices,
|
|
1441
|
+
leafIndices: raw.leafIndices,
|
|
1442
|
+
leaves: raw.leaves.map((l) => createBN254(l, "base58")),
|
|
1443
|
+
treeInfos: (raw.merkleTrees ?? []).map((t) => ({
|
|
1444
|
+
tree: address(t),
|
|
1445
|
+
queue: address(t),
|
|
1446
|
+
treeType: 1,
|
|
1447
|
+
nextTreeInfo: null
|
|
1448
|
+
})),
|
|
1449
|
+
proveByIndices: raw.leafIndices.map(() => false)
|
|
1450
|
+
};
|
|
1451
|
+
}
|
|
1452
|
+
function parseTokenAccount(raw) {
|
|
1453
|
+
return {
|
|
1454
|
+
compressedAccount: parseCompressedAccount(raw.account),
|
|
1455
|
+
parsed: {
|
|
1456
|
+
mint: address(raw.tokenData.mint),
|
|
1457
|
+
owner: address(raw.tokenData.owner),
|
|
1458
|
+
amount: BigInt(raw.tokenData.amount),
|
|
1459
|
+
delegate: raw.tokenData.delegate ? address(raw.tokenData.delegate) : null,
|
|
1460
|
+
state: raw.tokenData.state === "initialized" ? 1 : 0,
|
|
1461
|
+
tlv: null
|
|
1462
|
+
}
|
|
1463
|
+
};
|
|
1464
|
+
}
|
|
1465
|
+
function base58ToBytes(value) {
|
|
1466
|
+
return bs58.decode(value);
|
|
1467
|
+
}
|
|
1468
|
+
function base64ToBytes(value) {
|
|
1469
|
+
const binary = atob(value);
|
|
1470
|
+
const bytes = new Uint8Array(binary.length);
|
|
1471
|
+
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
1472
|
+
return bytes;
|
|
1473
|
+
}
|
|
1474
|
+
function hexToBytes$1(hex) {
|
|
1475
|
+
const cleanHex = hex.startsWith("0x") ? hex.slice(2) : hex;
|
|
1476
|
+
const bytes = new Uint8Array(cleanHex.length / 2);
|
|
1477
|
+
for (let i = 0; i < bytes.length; i++) bytes[i] = parseInt(cleanHex.slice(i * 2, i * 2 + 2), 16);
|
|
1478
|
+
return bytes;
|
|
1479
|
+
}
|
|
1480
|
+
/**
|
|
1481
|
+
* Create a Photon RPC client.
|
|
1482
|
+
*
|
|
1483
|
+
* @param endpoint - Photon indexer URL (e.g., "https://zk-testnet.helius.dev:8784")
|
|
1484
|
+
* @param headers - Optional additional headers
|
|
1485
|
+
* @returns PhotonRpc instance
|
|
1486
|
+
*
|
|
1487
|
+
* @example
|
|
1488
|
+
* ```typescript
|
|
1489
|
+
* import { createPhotonRpc } from '@cascade-fyi/compression-kit';
|
|
1490
|
+
*
|
|
1491
|
+
* const rpc = createPhotonRpc('https://mainnet.helius-rpc.com/?api-key=YOUR_KEY');
|
|
1492
|
+
* const accounts = await rpc.getCompressedAccountsByOwner(ownerAddress);
|
|
1493
|
+
* ```
|
|
1494
|
+
*/
|
|
1495
|
+
function createPhotonRpc(endpoint, headers) {
|
|
1496
|
+
return new PhotonRpc(endpoint, headers);
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
//#endregion
|
|
1500
|
+
export { ACCOUNT_COMPRESSION_PROGRAM, ADDRESS_QUEUE, ADDRESS_QUEUE_ROLLOVER_FEE, ADDRESS_TREE, ADDRESS_TREE_NETWORK_FEE_V1, ADDRESS_TREE_NETWORK_FEE_V2, BATCH_ADDRESS_TREE, BATCH_CPI_CONTEXT_1, BATCH_CPI_CONTEXT_2, BATCH_MERKLE_TREE_1, BATCH_MERKLE_TREE_2, BATCH_QUEUE_1, BATCH_QUEUE_2, BN254Error, BN254ErrorCode, COMPRESSED_TOKEN_PROGRAM, COMPUTE_BUDGET_PATTERN, CPI_CONTEXT_2_PUBKEY, CPI_CONTEXT_PUBKEY, CreateUtxoError, CreateUtxoErrorCode, DEFAULT_MERKLE_TREE_HEIGHT, DEFAULT_MERKLE_TREE_ROOTS, FIELD_SIZE, HIGHEST_ADDRESS_PLUS_ONE, HashError, HashErrorCode, INSERT_INTO_QUEUES_DISCRIMINATOR, INVOKE_CPI_DISCRIMINATOR, INVOKE_CPI_WITH_ACCOUNT_INFO_DISCRIMINATOR, INVOKE_CPI_WITH_READ_ONLY_DISCRIMINATOR, INVOKE_DISCRIMINATOR, LIGHT_SYSTEM_PROGRAM, LookupTableError, LookupTableErrorCode, MERKLE_TREE_2_PUBKEY, MERKLE_TREE_PUBKEY, MerkleTreeError, MerkleTreeErrorCode, NOOP_PROGRAM, NULLIFIED_STATE_TREE_LOOKUP_TABLE_DEVNET, NULLIFIED_STATE_TREE_LOOKUP_TABLE_MAINNET, NULLIFIER_QUEUE_2_PUBKEY, NULLIFIER_QUEUE_PUBKEY, PackedAccounts, PhotonRpc, ProofError, ProofErrorCode, REGISTERED_PROGRAM_PDA, RpcError, RpcErrorCode, STATE_MERKLE_TREE_NETWORK_FEE, STATE_MERKLE_TREE_ROLLOVER_FEE, STATE_TREE_LOOKUP_TABLE_DEVNET, STATE_TREE_LOOKUP_TABLE_MAINNET, SelectInUtxosError, SelectInUtxosErrorCode, TEST_BATCH_ADDRESS_TREE, TRANSACTION_MERKLE_TREE_ROLLOVER_THRESHOLD, TreeType, UTXO_MERGE_MAXIMUM, UTXO_MERGE_THRESHOLD, UtilsError, UtilsErrorCode, UtxoError, UtxoErrorCode, VERSION, addressToBytes, assertIsBN254, assertValidBN254Hash, bigIntToBytesBE, bigIntToBytesLE, bn254Add, bn254FromBytes, bn254Mul, bn254Sub, bn254ToBytes, bn254ToDecimalString, bytesEqual, bytesToAddress, bytesToBigIntBE, bytesToBigIntLE, bytesToDecimalString, bytesToHex, createBN254, createBN254Error, createPhotonRpc, createProofError, createRpcError, createSystemAccountConfig, createSystemAccountConfigWithCpi, createUtxoError, defaultStateTreeLookupTables, defaultStaticAccounts, defaultTestStateTreeAccounts, deriveAddress, deriveAddressSeed, deriveAddressSeedV2, deriveAddressV2, encodeBN254toBase58, encodeBN254toHex, featureFlags, getAccountCompressionAuthority, getAccountCompressionAuthority$1 as getCompressionAuthority, getCpiSignerPda, getDefaultAddressTreeInfo, getIndexOrAdd, getLightSystemAccountMetas, getLightSystemAccountMetasV2, getSystemProgram, hashToBn254FieldSizeBe, hashvToBn254FieldSizeBe, hashvToBn254FieldSizeBeWithBump, hexToBytes, isBN254, isLocalTest, isSmallerThanFieldSize, localTestActiveStateTreeInfos, mergeBytes, packNewAddressParams, padBytes, pushUniqueItems, toArray, toHex, validateBN254Hash, versionedEndpoint };
|
|
1501
|
+
//# sourceMappingURL=index.mjs.map
|