@veilo/sdk-core 0.1.17 → 0.3.3
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/README.md +1257 -272
- package/dist/cjs/client.d.ts +407 -0
- package/dist/cjs/client.js +914 -0
- package/dist/cjs/config.d.ts +82 -0
- package/dist/cjs/config.js +57 -0
- package/dist/cjs/events.d.ts +77 -0
- package/dist/cjs/events.js +167 -0
- package/dist/cjs/idl/privacy_pool.json +10313 -0
- package/dist/cjs/index.d.ts +13 -0
- package/dist/cjs/index.js +57 -0
- package/dist/cjs/merkle.d.ts +64 -0
- package/dist/cjs/merkle.js +133 -0
- package/dist/cjs/poseidon.d.ts +29 -0
- package/dist/cjs/poseidon.js +100 -0
- package/dist/cjs/program.d.ts +26 -0
- package/dist/cjs/program.js +38 -0
- package/dist/cjs/proof.d.ts +183 -0
- package/dist/cjs/proof.js +292 -0
- package/dist/cjs/prover.d.ts +54 -0
- package/dist/cjs/prover.js +112 -0
- package/dist/cjs/relayer.d.ts +295 -0
- package/dist/cjs/relayer.js +246 -0
- package/dist/cjs/retry.d.ts +32 -0
- package/dist/cjs/retry.js +75 -0
- package/dist/cjs/utxo.d.ts +215 -0
- package/dist/cjs/utxo.js +394 -0
- package/dist/esm/client.js +887 -0
- package/dist/esm/config.js +51 -0
- package/dist/esm/events.js +129 -0
- package/dist/esm/idl/privacy_pool.json +10313 -0
- package/dist/esm/index.js +22 -0
- package/dist/esm/merkle.js +129 -0
- package/dist/esm/poseidon.js +87 -0
- package/dist/esm/program.js +31 -0
- package/dist/esm/proof.js +281 -0
- package/dist/esm/prover.js +75 -0
- package/dist/esm/relayer.js +238 -0
- package/dist/esm/retry.js +71 -0
- package/dist/esm/utxo.js +372 -0
- package/package.json +47 -11
- package/src/client.ts +0 -352
- package/src/config.ts +0 -13
- package/src/index.ts +0 -6
- package/src/merkle.ts +0 -178
- package/src/note.ts +0 -193
- package/src/poseidon.ts +0 -62
- package/src/proof.ts +0 -170
- package/test/script.js +0 -0
- package/test-tsconfig.json +0 -19
- package/tests/note.test.ts +0 -50
- package/tests/sdk.integration.test.ts +0 -210
- package/tsconfig.json +0 -18
package/package.json
CHANGED
|
@@ -1,26 +1,54 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@veilo/sdk-core",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
5
|
-
"homepage": "https://github.com/
|
|
3
|
+
"version": "0.3.3",
|
|
4
|
+
"description": "TypeScript SDK for the Veilo Privacy Pool - UTXO-based privacy protocol on Solana with ZK-SNARKs",
|
|
5
|
+
"homepage": "https://github.com/VeiloSolana/veilo-sdk#readme",
|
|
6
6
|
"bugs": {
|
|
7
|
-
"url": "https://github.com/
|
|
7
|
+
"url": "https://github.com/VeiloSolana/veilo-sdk/issues"
|
|
8
8
|
},
|
|
9
9
|
"repository": {
|
|
10
10
|
"type": "git",
|
|
11
|
-
"url": "git+https://github.com/
|
|
11
|
+
"url": "git+https://github.com/VeiloSolana/veilo-sdk.git"
|
|
12
12
|
},
|
|
13
13
|
"license": "ISC",
|
|
14
|
-
"author": "",
|
|
14
|
+
"author": "Veilo <https://github.com/VeiloSolana>",
|
|
15
15
|
"type": "commonjs",
|
|
16
|
-
"main": "dist/index.js",
|
|
17
|
-
"
|
|
16
|
+
"main": "dist/cjs/index.js",
|
|
17
|
+
"module": "dist/esm/index.js",
|
|
18
|
+
"types": "dist/cjs/index.d.ts",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"import": "./dist/esm/index.js",
|
|
22
|
+
"require": "./dist/cjs/index.js",
|
|
23
|
+
"types": "./dist/cjs/index.d.ts"
|
|
24
|
+
},
|
|
25
|
+
"./config": {
|
|
26
|
+
"import": "./dist/esm/config.js",
|
|
27
|
+
"require": "./dist/cjs/config.js",
|
|
28
|
+
"types": "./dist/cjs/config.d.ts"
|
|
29
|
+
},
|
|
30
|
+
"./poseidon": {
|
|
31
|
+
"import": "./dist/esm/poseidon.js",
|
|
32
|
+
"require": "./dist/cjs/poseidon.js",
|
|
33
|
+
"types": "./dist/cjs/poseidon.d.ts"
|
|
34
|
+
},
|
|
35
|
+
"./package.json": "./package.json"
|
|
36
|
+
},
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"access": "public"
|
|
39
|
+
},
|
|
40
|
+
"sideEffects": false,
|
|
41
|
+
"files": [
|
|
42
|
+
"dist/",
|
|
43
|
+
"README.md",
|
|
44
|
+
"LICENSE"
|
|
45
|
+
],
|
|
18
46
|
"directories": {
|
|
19
47
|
"test": "tests"
|
|
20
48
|
},
|
|
21
49
|
"scripts": {
|
|
22
|
-
"build": "rm -rf dist/ && tsc -p tsconfig.json",
|
|
23
|
-
"test": "ANCHOR_PROVIDER_URL=
|
|
50
|
+
"build": "rm -rf dist/ && tsc -p tsconfig.json && tsc -p tsconfig.esm.json",
|
|
51
|
+
"test": "rm -rf dist/ && export ANCHOR_PROVIDER_URL=https://api.devnet.solana.com && export ANCHOR_WALLET=$HOME/.config/solana/id.json && tsc -p test-tsconfig.json && mocha -t 1000000 \"dist/tests/**/*.js\""
|
|
24
52
|
},
|
|
25
53
|
"dependencies": {
|
|
26
54
|
"@coral-xyz/anchor": "^0.30.0",
|
|
@@ -28,12 +56,20 @@
|
|
|
28
56
|
"@solana/spl-token": "^0.4.6",
|
|
29
57
|
"@solana/web3.js": "^1.95.0",
|
|
30
58
|
"circomlibjs": "^0.1.7",
|
|
31
|
-
"mocha": "^11.7.5",
|
|
32
59
|
"tweetnacl": "^1.0.3"
|
|
33
60
|
},
|
|
61
|
+
"peerDependencies": {
|
|
62
|
+
"snarkjs": ">=0.7.0"
|
|
63
|
+
},
|
|
64
|
+
"peerDependenciesMeta": {
|
|
65
|
+
"snarkjs": {
|
|
66
|
+
"optional": true
|
|
67
|
+
}
|
|
68
|
+
},
|
|
34
69
|
"devDependencies": {
|
|
35
70
|
"@types/circomlibjs": "^0.1.6",
|
|
36
71
|
"@types/mocha": "^10.0.10",
|
|
72
|
+
"mocha": "^11.7.5",
|
|
37
73
|
"@types/node": "^24.10.1",
|
|
38
74
|
"chai": "^6.2.1",
|
|
39
75
|
"ts-node": "^10.9.2",
|
package/src/client.ts
DELETED
|
@@ -1,352 +0,0 @@
|
|
|
1
|
-
import * as anchor from "@coral-xyz/anchor";
|
|
2
|
-
import { Program, BN, Idl } from "@coral-xyz/anchor";
|
|
3
|
-
import { PublicKey, SystemProgram, Keypair } from "@solana/web3.js";
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
ProofBuilder,
|
|
7
|
-
WithdrawCircuitInputs,
|
|
8
|
-
WithdrawProofStruct,
|
|
9
|
-
deriveNullifier,
|
|
10
|
-
encodeSnarkjsProofToWithdrawProof,
|
|
11
|
-
} from "./proof";
|
|
12
|
-
|
|
13
|
-
import {
|
|
14
|
-
createNoteWithCommitment,
|
|
15
|
-
EncryptedNotePlaintext,
|
|
16
|
-
encryptNote,
|
|
17
|
-
SerializedNote,
|
|
18
|
-
} from "./note";
|
|
19
|
-
|
|
20
|
-
import { MerklePath, MerkleTree } from "./merkle";
|
|
21
|
-
import { randomBytes } from "crypto";
|
|
22
|
-
|
|
23
|
-
// -----------------------------------------------------------------------------
|
|
24
|
-
// Types
|
|
25
|
-
// -----------------------------------------------------------------------------
|
|
26
|
-
|
|
27
|
-
export type NoteAndMerkleData = {
|
|
28
|
-
note: SerializedNote;
|
|
29
|
-
leafIndex: number;
|
|
30
|
-
root: Uint8Array;
|
|
31
|
-
merklePath: MerklePath;
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
// -----------------------------------------------------------------------------
|
|
35
|
-
// High-level deposit helper (off-chain Merkle is just for zk / demos)
|
|
36
|
-
// -----------------------------------------------------------------------------
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* High-level helper:
|
|
40
|
-
* - Creates a random note + commitment
|
|
41
|
-
* - Inserts the commitment into an *off-chain* Merkle tree (your zk tree)
|
|
42
|
-
* - Calls on-chain `depositFixed` with the commitment
|
|
43
|
-
* - Returns note + index + Merkle path (+ off-chain root)
|
|
44
|
-
*
|
|
45
|
-
* The on-chain program *also* keeps its own rolling 32-byte root, but that is
|
|
46
|
-
* intentionally minimal and independent from this off-chain tree. For a real
|
|
47
|
-
* zk circuit you’d want both to share the same hash function.
|
|
48
|
-
*/
|
|
49
|
-
export async function createNoteDepositWithMerkle<T extends Idl>(params: {
|
|
50
|
-
program: Program<T>;
|
|
51
|
-
depositor: anchor.Wallet;
|
|
52
|
-
denomIndex: number;
|
|
53
|
-
valueLamports: bigint;
|
|
54
|
-
tree: MerkleTree;
|
|
55
|
-
}): Promise<NoteAndMerkleData> {
|
|
56
|
-
const { program, depositor, denomIndex, valueLamports, tree } = params;
|
|
57
|
-
|
|
58
|
-
const note = createNoteWithCommitment({
|
|
59
|
-
value: valueLamports,
|
|
60
|
-
owner: depositor.publicKey,
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
// Maintain your off-chain mirror (for proofs)
|
|
64
|
-
const { index, root } = tree.insert(note.commitment);
|
|
65
|
-
const merklePath = tree.getPath(index);
|
|
66
|
-
|
|
67
|
-
// Call low-level deposit with just the commitment
|
|
68
|
-
await depositFixedSol({
|
|
69
|
-
program,
|
|
70
|
-
depositor,
|
|
71
|
-
denomIndex,
|
|
72
|
-
commitment: note.commitment,
|
|
73
|
-
note,
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
return {
|
|
77
|
-
note,
|
|
78
|
-
leafIndex: index,
|
|
79
|
-
root,
|
|
80
|
-
merklePath,
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// -----------------------------------------------------------------------------
|
|
85
|
-
// PDA helpers (v3 seeds)
|
|
86
|
-
// -----------------------------------------------------------------------------
|
|
87
|
-
|
|
88
|
-
export function getPoolPdas(programId: PublicKey, commitmentSeed?: Uint8Array) {
|
|
89
|
-
const [config] = PublicKey.findProgramAddressSync(
|
|
90
|
-
[Buffer.from("privacy_config_v3")],
|
|
91
|
-
programId
|
|
92
|
-
);
|
|
93
|
-
const [vault] = PublicKey.findProgramAddressSync(
|
|
94
|
-
[Buffer.from("privacy_vault_v3")],
|
|
95
|
-
programId
|
|
96
|
-
);
|
|
97
|
-
const [noteTree] = PublicKey.findProgramAddressSync(
|
|
98
|
-
[Buffer.from("privacy_note_tree_v3")],
|
|
99
|
-
programId
|
|
100
|
-
);
|
|
101
|
-
const [nullifiers] = PublicKey.findProgramAddressSync(
|
|
102
|
-
[Buffer.from("privacy_nullifiers_v3")],
|
|
103
|
-
programId
|
|
104
|
-
);
|
|
105
|
-
|
|
106
|
-
const [noteHint] = PublicKey.findProgramAddressSync(
|
|
107
|
-
[Buffer.from("privacy_note_hint_v3"), commitmentSeed ?? Buffer.alloc(0)],
|
|
108
|
-
programId
|
|
109
|
-
);
|
|
110
|
-
|
|
111
|
-
return { config, vault, noteTree, nullifiers, noteHint };
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// -----------------------------------------------------------------------------
|
|
115
|
-
// Pool initialization
|
|
116
|
-
// -----------------------------------------------------------------------------
|
|
117
|
-
|
|
118
|
-
export async function initializePool<T extends Idl>(params: {
|
|
119
|
-
program: Program<T>;
|
|
120
|
-
admin: anchor.Wallet;
|
|
121
|
-
denomsLamports: (bigint | number)[];
|
|
122
|
-
feeBps: number; // 0–10000
|
|
123
|
-
}) {
|
|
124
|
-
const { program, admin, denomsLamports, feeBps } = params;
|
|
125
|
-
const { config, vault, noteTree, nullifiers } = getPoolPdas(
|
|
126
|
-
program.programId
|
|
127
|
-
);
|
|
128
|
-
|
|
129
|
-
await (program.methods as any)
|
|
130
|
-
.initialize(
|
|
131
|
-
denomsLamports.map((d) => new BN(d.toString())),
|
|
132
|
-
feeBps
|
|
133
|
-
)
|
|
134
|
-
.accounts({
|
|
135
|
-
config,
|
|
136
|
-
vault,
|
|
137
|
-
noteTree,
|
|
138
|
-
nullifiers,
|
|
139
|
-
admin: admin.publicKey,
|
|
140
|
-
systemProgram: SystemProgram.programId,
|
|
141
|
-
} as any)
|
|
142
|
-
.rpc();
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// -----------------------------------------------------------------------------
|
|
146
|
-
// Deposit helpers
|
|
147
|
-
// -----------------------------------------------------------------------------
|
|
148
|
-
|
|
149
|
-
export type DepositNoteResult = {
|
|
150
|
-
commitment: Uint8Array; // 32 bytes
|
|
151
|
-
};
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Low-level deposit helper.
|
|
155
|
-
* Expects a 32-byte commitment; on-chain NoteTree keeps a rolling 32-byte root.
|
|
156
|
-
*/
|
|
157
|
-
export async function depositFixedSol<T extends Idl>(params: {
|
|
158
|
-
program: Program<T>;
|
|
159
|
-
depositor: anchor.Wallet;
|
|
160
|
-
denomIndex: number;
|
|
161
|
-
commitment: Uint8Array; // 32
|
|
162
|
-
note: SerializedNote;
|
|
163
|
-
}) {
|
|
164
|
-
const { program, depositor, denomIndex, commitment, note } = params;
|
|
165
|
-
const { config, vault, noteTree, noteHint } = getPoolPdas(
|
|
166
|
-
program.programId,
|
|
167
|
-
commitment
|
|
168
|
-
);
|
|
169
|
-
|
|
170
|
-
if (commitment.length !== 32) {
|
|
171
|
-
throw new Error("commitment must be 32 bytes");
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const notePlain: EncryptedNotePlaintext = {
|
|
175
|
-
secret: randomBytes(32),
|
|
176
|
-
nullifier: deriveNullifier(note),
|
|
177
|
-
denomIndex: 0,
|
|
178
|
-
};
|
|
179
|
-
|
|
180
|
-
const sharedKey = randomBytes(32); // derive from recipient pubkey in real wallet
|
|
181
|
-
const encryptedNote = encryptNote(notePlain, sharedKey);
|
|
182
|
-
|
|
183
|
-
await (program.methods as any)
|
|
184
|
-
.depositFixed(
|
|
185
|
-
denomIndex,
|
|
186
|
-
Array.from(commitment) // [u8;32]
|
|
187
|
-
)
|
|
188
|
-
.accounts({
|
|
189
|
-
config,
|
|
190
|
-
vault,
|
|
191
|
-
noteTree,
|
|
192
|
-
noteHint,
|
|
193
|
-
depositor: depositor.publicKey,
|
|
194
|
-
systemProgram: SystemProgram.programId,
|
|
195
|
-
} as any)
|
|
196
|
-
.rpc();
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* Convenience: build a random Note off-chain, commit it, and call deposit.
|
|
201
|
-
*/
|
|
202
|
-
export async function createNoteAndDeposit<T extends Idl>(params: {
|
|
203
|
-
program: Program<T>;
|
|
204
|
-
depositor: anchor.Wallet;
|
|
205
|
-
denomIndex: number;
|
|
206
|
-
valueLamports: bigint;
|
|
207
|
-
}) {
|
|
208
|
-
const { program, depositor, denomIndex, valueLamports } = params;
|
|
209
|
-
|
|
210
|
-
const note = createNoteWithCommitment({
|
|
211
|
-
value: valueLamports,
|
|
212
|
-
owner: depositor.publicKey,
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
await depositFixedSol({
|
|
216
|
-
program,
|
|
217
|
-
depositor,
|
|
218
|
-
denomIndex,
|
|
219
|
-
commitment: note.commitment,
|
|
220
|
-
note,
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
return note;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// -----------------------------------------------------------------------------
|
|
227
|
-
// Relayers & Withdrawals
|
|
228
|
-
// -----------------------------------------------------------------------------
|
|
229
|
-
|
|
230
|
-
export async function addRelayer<T extends Idl>(params: {
|
|
231
|
-
program: Program<T>;
|
|
232
|
-
admin: anchor.Wallet;
|
|
233
|
-
newRelayer: PublicKey;
|
|
234
|
-
}) {
|
|
235
|
-
const { program, admin, newRelayer } = params;
|
|
236
|
-
const { config } = getPoolPdas(program.programId);
|
|
237
|
-
|
|
238
|
-
await (program.methods as any)
|
|
239
|
-
.addRelayer(newRelayer)
|
|
240
|
-
.accounts({
|
|
241
|
-
config,
|
|
242
|
-
admin: admin.publicKey,
|
|
243
|
-
} as any)
|
|
244
|
-
.rpc();
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
/**
|
|
248
|
-
* Low-level withdraw:
|
|
249
|
-
* - Expects the **already packed** WithdrawProofStruct
|
|
250
|
-
* (matching Rust `WithdrawProof` fields).
|
|
251
|
-
*/
|
|
252
|
-
export async function withdrawViaRelayer<T extends Idl>(params: {
|
|
253
|
-
program: Program<T>;
|
|
254
|
-
relayer: Keypair;
|
|
255
|
-
recipient: PublicKey;
|
|
256
|
-
denomIndex: number;
|
|
257
|
-
root: Uint8Array; // 32 bytes
|
|
258
|
-
nullifier: Uint8Array; // 32 bytes
|
|
259
|
-
proof: WithdrawProofStruct;
|
|
260
|
-
}) {
|
|
261
|
-
const { program, relayer, recipient, denomIndex, root, nullifier, proof } =
|
|
262
|
-
params;
|
|
263
|
-
const { config, vault, noteTree, nullifiers } = getPoolPdas(
|
|
264
|
-
program.programId
|
|
265
|
-
);
|
|
266
|
-
|
|
267
|
-
if (root.length !== 32) throw new Error("root must be 32 bytes");
|
|
268
|
-
if (nullifier.length !== 32) throw new Error("nullifier must be 32 bytes");
|
|
269
|
-
|
|
270
|
-
await (program.methods as any)
|
|
271
|
-
.withdraw(
|
|
272
|
-
Array.from(root),
|
|
273
|
-
Array.from(nullifier),
|
|
274
|
-
denomIndex,
|
|
275
|
-
recipient,
|
|
276
|
-
proof // <- Anchor / IDL maps this to WithdrawProof { proof_a, proof_b, proof_c }
|
|
277
|
-
)
|
|
278
|
-
.accounts({
|
|
279
|
-
config,
|
|
280
|
-
vault,
|
|
281
|
-
noteTree,
|
|
282
|
-
nullifiers,
|
|
283
|
-
relayer: relayer.publicKey,
|
|
284
|
-
recipient,
|
|
285
|
-
systemProgram: SystemProgram.programId,
|
|
286
|
-
} as any)
|
|
287
|
-
.signers([relayer])
|
|
288
|
-
.rpc();
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
/**
|
|
292
|
-
* High-level withdraw:
|
|
293
|
-
* - Takes note + Merkle path + denom + fee info
|
|
294
|
-
* - Uses a pluggable ProofBuilder (snarkjs.groth16) to create a RawProof
|
|
295
|
-
* - Encodes RawProof into WithdrawProofStruct
|
|
296
|
-
* - Calls the low-level withdrawViaRelayer
|
|
297
|
-
*
|
|
298
|
-
* In production, you'd typically call a relayer HTTP API instead, and let it
|
|
299
|
-
* build/verify the proof server-side. But this is handy for tools / tests.
|
|
300
|
-
*/
|
|
301
|
-
export async function withdrawViaRelayerWithProof<T extends Idl>(params: {
|
|
302
|
-
program: Program<T>;
|
|
303
|
-
relayer: Keypair;
|
|
304
|
-
recipient: PublicKey;
|
|
305
|
-
denomIndex: number;
|
|
306
|
-
feeBps: number;
|
|
307
|
-
root: Uint8Array;
|
|
308
|
-
nullifier: Uint8Array;
|
|
309
|
-
noteData: SerializedNote;
|
|
310
|
-
merklePath: MerklePath;
|
|
311
|
-
builder: ProofBuilder; // must produce a valid snarkjs Groth16 proof
|
|
312
|
-
}) {
|
|
313
|
-
const {
|
|
314
|
-
program,
|
|
315
|
-
relayer,
|
|
316
|
-
recipient,
|
|
317
|
-
denomIndex,
|
|
318
|
-
feeBps,
|
|
319
|
-
root,
|
|
320
|
-
nullifier,
|
|
321
|
-
noteData,
|
|
322
|
-
merklePath,
|
|
323
|
-
builder,
|
|
324
|
-
} = params;
|
|
325
|
-
|
|
326
|
-
const inputs: WithdrawCircuitInputs = {
|
|
327
|
-
root,
|
|
328
|
-
note: noteData,
|
|
329
|
-
merklePath,
|
|
330
|
-
nullifier,
|
|
331
|
-
denomIndex,
|
|
332
|
-
recipient,
|
|
333
|
-
relayer: relayer.publicKey,
|
|
334
|
-
feeBps,
|
|
335
|
-
};
|
|
336
|
-
|
|
337
|
-
// Build snarkjs proof off-chain
|
|
338
|
-
const rawProof = await builder(inputs);
|
|
339
|
-
|
|
340
|
-
// Pack into the struct expected by the on-chain Rust program
|
|
341
|
-
const withdrawProof = encodeSnarkjsProofToWithdrawProof(rawProof);
|
|
342
|
-
|
|
343
|
-
await withdrawViaRelayer({
|
|
344
|
-
program,
|
|
345
|
-
relayer,
|
|
346
|
-
recipient,
|
|
347
|
-
denomIndex,
|
|
348
|
-
root,
|
|
349
|
-
nullifier,
|
|
350
|
-
proof: withdrawProof,
|
|
351
|
-
});
|
|
352
|
-
}
|
package/src/config.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
// config.ts
|
|
2
|
-
import { LAMPORTS_PER_SOL } from "@solana/web3.js";
|
|
3
|
-
|
|
4
|
-
export const DEFAULT_FEE_BPS = 50; // 0.5%
|
|
5
|
-
|
|
6
|
-
export function sol(n: number): bigint {
|
|
7
|
-
return BigInt(n) * BigInt(LAMPORTS_PER_SOL);
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export type PoolInitConfig = {
|
|
11
|
-
denomsSol: number[]; // [1, 5] etc
|
|
12
|
-
feeBps?: number;
|
|
13
|
-
};
|
package/src/index.ts
DELETED
package/src/merkle.ts
DELETED
|
@@ -1,178 +0,0 @@
|
|
|
1
|
-
// sdk-core/src/merkle.ts
|
|
2
|
-
import {
|
|
3
|
-
BN254_FR_MODULUS,
|
|
4
|
-
bytesToBigIntBE,
|
|
5
|
-
bigIntToBytesBE,
|
|
6
|
-
} from "./note";
|
|
7
|
-
import {
|
|
8
|
-
getPoseidon,
|
|
9
|
-
frToBigInt,
|
|
10
|
-
} from "./poseidon";
|
|
11
|
-
|
|
12
|
-
export type MerklePath = {
|
|
13
|
-
/** Index of the leaf in the tree (0-based) */
|
|
14
|
-
leafIndex: number;
|
|
15
|
-
/** Sibling hashes from leaf level up to (but not including) the root */
|
|
16
|
-
path: Uint8Array[];
|
|
17
|
-
/**
|
|
18
|
-
* 0 = current node is left child, sibling is right
|
|
19
|
-
* 1 = current node is right child, sibling is left
|
|
20
|
-
* (matches your Circom `pathIndices` semantics)
|
|
21
|
-
*/
|
|
22
|
-
indices: number[];
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
export type Bytes32 = Uint8Array;
|
|
26
|
-
|
|
27
|
-
// Poseidon-based hash of two 32-byte field elements.
|
|
28
|
-
function hashPair(left: Bytes32, right: Bytes32): Bytes32 {
|
|
29
|
-
if (left.length !== 32 || right.length !== 32) {
|
|
30
|
-
throw new Error("hashPair expects 32-byte inputs");
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const poseidon = getPoseidon();
|
|
34
|
-
|
|
35
|
-
const lf = bytesToBigIntBE(left) % BN254_FR_MODULUS;
|
|
36
|
-
const rf = bytesToBigIntBE(right) % BN254_FR_MODULUS;
|
|
37
|
-
|
|
38
|
-
const fe = poseidon([lf, rf]);
|
|
39
|
-
const outBig = frToBigInt(fe) % BN254_FR_MODULUS;
|
|
40
|
-
|
|
41
|
-
return bigIntToBytesBE(outBig);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Fixed-depth binary Merkle tree over BN254 Fr:
|
|
46
|
-
* - Leaves and internal nodes are 32-byte big-endian Fr elements.
|
|
47
|
-
* - Hash is Poseidon(2)(left, right).
|
|
48
|
-
* - Unfilled leaves use the Poseidon zero chain:
|
|
49
|
-
* zero[0] = 0
|
|
50
|
-
* zero[i+1] = Poseidon(zero[i], zero[i])
|
|
51
|
-
*
|
|
52
|
-
* Depth must match:
|
|
53
|
-
* - Circom WithdrawCircuit(depth)
|
|
54
|
-
* - Rust MERKLE_TREE_HEIGHT
|
|
55
|
-
*/
|
|
56
|
-
export class MerkleTree {
|
|
57
|
-
readonly depth: number;
|
|
58
|
-
readonly zeroes: Uint8Array[];
|
|
59
|
-
readonly layers: Uint8Array[][];
|
|
60
|
-
nextIndex: number;
|
|
61
|
-
|
|
62
|
-
constructor(depth = 16) {
|
|
63
|
-
this.depth = depth;
|
|
64
|
-
this.zeroes = [];
|
|
65
|
-
this.layers = [];
|
|
66
|
-
|
|
67
|
-
// zero[0] = 0 (Fr), then zero[i+1] = Poseidon(zero[i], zero[i])
|
|
68
|
-
let zero = bigIntToBytesBE(0n);
|
|
69
|
-
this.zeroes.push(zero);
|
|
70
|
-
|
|
71
|
-
for (let level = 1; level <= depth; level++) {
|
|
72
|
-
zero = hashPair(zero, zero);
|
|
73
|
-
this.zeroes.push(zero);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Allocate layers:
|
|
77
|
-
// level 0: 2^depth leaves
|
|
78
|
-
// level 1: 2^(depth-1) parents
|
|
79
|
-
// ...
|
|
80
|
-
// level depth: 1 root
|
|
81
|
-
for (let level = 0; level <= depth; level++) {
|
|
82
|
-
const size = 1 << (depth - level);
|
|
83
|
-
|
|
84
|
-
if (!Number.isSafeInteger(size) || size <= 0) {
|
|
85
|
-
throw new Error(
|
|
86
|
-
`Invalid MerkleTree depth=${depth}, level=${level}, size=${size}`,
|
|
87
|
-
);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const arr: Uint8Array[] = new Array(size);
|
|
91
|
-
for (let i = 0; i < size; i++) {
|
|
92
|
-
arr[i] = this.zeroes[level];
|
|
93
|
-
}
|
|
94
|
-
this.layers.push(arr);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
this.nextIndex = 0;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
get capacity(): number {
|
|
101
|
-
return 1 << this.depth;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
get root(): Uint8Array {
|
|
105
|
-
return this.layers[this.depth][0];
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* Insert a leaf and recompute the path up to the root.
|
|
110
|
-
* Returns the leaf index and the new root.
|
|
111
|
-
*/
|
|
112
|
-
insert(leaf: Uint8Array): { index: number; root: Uint8Array } {
|
|
113
|
-
if (leaf.length !== 32) {
|
|
114
|
-
throw new Error("leaf must be 32 bytes");
|
|
115
|
-
}
|
|
116
|
-
const index = this.nextIndex;
|
|
117
|
-
if (index >= this.capacity) {
|
|
118
|
-
throw new Error("Merkle tree is full");
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// Set leaf
|
|
122
|
-
this.layers[0][index] = leaf;
|
|
123
|
-
|
|
124
|
-
// Bubble up
|
|
125
|
-
let idx = index;
|
|
126
|
-
for (let level = 1; level <= this.depth; level++) {
|
|
127
|
-
const parentIndex = Math.floor(idx / 2);
|
|
128
|
-
const leftIndex = parentIndex * 2;
|
|
129
|
-
const rightIndex = leftIndex + 1;
|
|
130
|
-
|
|
131
|
-
const left = this.layers[level - 1][leftIndex];
|
|
132
|
-
const right =
|
|
133
|
-
rightIndex < this.layers[level - 1].length
|
|
134
|
-
? this.layers[level - 1][rightIndex]
|
|
135
|
-
: this.zeroes[level - 1];
|
|
136
|
-
|
|
137
|
-
this.layers[level][parentIndex] = hashPair(left, right);
|
|
138
|
-
idx = parentIndex;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
this.nextIndex++;
|
|
142
|
-
return { index, root: this.root };
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Return Merkle proof (path + indices) for a given leaf index.
|
|
147
|
-
* This feeds directly into the Circom `MerklePathVerifier(depth)`:
|
|
148
|
-
* - `pathElements[i]` = sibling
|
|
149
|
-
* - `pathIndices[i]` = 0/1 as defined above
|
|
150
|
-
*/
|
|
151
|
-
getPath(index: number): MerklePath {
|
|
152
|
-
if (index < 0 || index >= this.capacity) {
|
|
153
|
-
throw new Error("index out of range");
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
const path: Uint8Array[] = [];
|
|
157
|
-
const indices: number[] = [];
|
|
158
|
-
|
|
159
|
-
let idx = index;
|
|
160
|
-
for (let level = 0; level < this.depth; level++) {
|
|
161
|
-
const isRight = idx % 2 === 1;
|
|
162
|
-
const siblingIndex = isRight ? idx - 1 : idx + 1;
|
|
163
|
-
|
|
164
|
-
const sibling =
|
|
165
|
-
siblingIndex < this.layers[level].length
|
|
166
|
-
? this.layers[level][siblingIndex]
|
|
167
|
-
: this.zeroes[level];
|
|
168
|
-
|
|
169
|
-
path.push(sibling);
|
|
170
|
-
// 0 = current is left, 1 = current is right
|
|
171
|
-
indices.push(isRight ? 1 : 0);
|
|
172
|
-
|
|
173
|
-
idx = Math.floor(idx / 2);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
return { leafIndex: index, path, indices };
|
|
177
|
-
}
|
|
178
|
-
}
|