@veilo/sdk-core 0.1.17 → 0.4.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/README.md +1296 -272
- package/SHIELD_INTEGRATION.md +143 -0
- package/config.d.ts +2 -0
- package/config.js +4 -0
- package/dist/cjs/client.d.ts +407 -0
- package/dist/cjs/client.js +951 -0
- package/dist/cjs/compactNote.d.ts +107 -0
- package/dist/cjs/compactNote.js +167 -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.d.ts +5 -0
- package/dist/cjs/idl/privacy_pool.js +15218 -0
- package/dist/cjs/idl/privacy_pool.json +10313 -0
- package/dist/cjs/index.d.ts +16 -0
- package/dist/cjs/index.js +67 -0
- package/dist/cjs/merkle.d.ts +77 -0
- package/dist/cjs/merkle.js +156 -0
- package/dist/cjs/poseidon.d.ts +29 -0
- package/dist/cjs/poseidon.js +100 -0
- package/dist/cjs/program.d.ts +37 -0
- package/dist/cjs/program.js +61 -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/random.d.ts +16 -0
- package/dist/cjs/random.js +28 -0
- package/dist/cjs/relayer.d.ts +318 -0
- package/dist/cjs/relayer.js +257 -0
- package/dist/cjs/retry.d.ts +32 -0
- package/dist/cjs/retry.js +75 -0
- package/dist/cjs/shield/alt.d.ts +87 -0
- package/dist/cjs/shield/alt.js +194 -0
- package/dist/cjs/shield/computeBudget.d.ts +61 -0
- package/dist/cjs/shield/computeBudget.js +64 -0
- package/dist/cjs/shield/errors.d.ts +58 -0
- package/dist/cjs/shield/errors.js +121 -0
- package/dist/cjs/shield/finalize.d.ts +45 -0
- package/dist/cjs/shield/finalize.js +119 -0
- package/dist/cjs/shield/index.d.ts +35 -0
- package/dist/cjs/shield/index.js +68 -0
- package/dist/cjs/shield/ix.d.ts +54 -0
- package/dist/cjs/shield/ix.js +119 -0
- package/dist/cjs/shield/owner.d.ts +36 -0
- package/dist/cjs/shield/owner.js +126 -0
- package/dist/cjs/shield/ports.d.ts +43 -0
- package/dist/cjs/shield/ports.js +153 -0
- package/dist/cjs/shield/preflight.d.ts +30 -0
- package/dist/cjs/shield/preflight.js +154 -0
- package/dist/cjs/shield/shield.d.ts +68 -0
- package/dist/cjs/shield/shield.js +499 -0
- package/dist/cjs/shield/types.d.ts +202 -0
- package/dist/cjs/shield/types.js +2 -0
- package/dist/cjs/utxo.d.ts +235 -0
- package/dist/cjs/utxo.js +407 -0
- package/dist/esm/client.d.ts +407 -0
- package/dist/esm/client.js +891 -0
- package/dist/esm/compactNote.d.ts +107 -0
- package/dist/esm/compactNote.js +156 -0
- package/dist/esm/config.d.ts +82 -0
- package/dist/esm/config.js +51 -0
- package/dist/esm/events.d.ts +77 -0
- package/dist/esm/events.js +129 -0
- package/dist/esm/idl/privacy_pool.d.ts +5 -0
- package/dist/esm/idl/privacy_pool.js +15216 -0
- package/dist/esm/idl/privacy_pool.json +10313 -0
- package/dist/esm/index.d.ts +16 -0
- package/dist/esm/index.js +29 -0
- package/dist/esm/merkle.d.ts +77 -0
- package/dist/esm/merkle.js +151 -0
- package/dist/esm/package.json +1 -0
- package/dist/esm/poseidon.d.ts +29 -0
- package/dist/esm/poseidon.js +87 -0
- package/dist/esm/program.d.ts +37 -0
- package/dist/esm/program.js +53 -0
- package/dist/esm/proof.d.ts +183 -0
- package/dist/esm/proof.js +281 -0
- package/dist/esm/prover.d.ts +54 -0
- package/dist/esm/prover.js +75 -0
- package/dist/esm/random.d.ts +16 -0
- package/dist/esm/random.js +21 -0
- package/dist/esm/relayer.d.ts +318 -0
- package/dist/esm/relayer.js +249 -0
- package/dist/esm/retry.d.ts +32 -0
- package/dist/esm/retry.js +71 -0
- package/dist/esm/shield/alt.d.ts +87 -0
- package/dist/esm/shield/alt.js +186 -0
- package/dist/esm/shield/computeBudget.d.ts +61 -0
- package/dist/esm/shield/computeBudget.js +61 -0
- package/dist/esm/shield/errors.d.ts +58 -0
- package/dist/esm/shield/errors.js +115 -0
- package/dist/esm/shield/finalize.d.ts +45 -0
- package/dist/esm/shield/finalize.js +83 -0
- package/dist/esm/shield/index.d.ts +35 -0
- package/dist/esm/shield/index.js +32 -0
- package/dist/esm/shield/ix.d.ts +54 -0
- package/dist/esm/shield/ix.js +82 -0
- package/dist/esm/shield/owner.d.ts +36 -0
- package/dist/esm/shield/owner.js +122 -0
- package/dist/esm/shield/ports.d.ts +43 -0
- package/dist/esm/shield/ports.js +147 -0
- package/dist/esm/shield/preflight.d.ts +30 -0
- package/dist/esm/shield/preflight.js +151 -0
- package/dist/esm/shield/shield.d.ts +68 -0
- package/dist/esm/shield/shield.js +492 -0
- package/dist/esm/shield/types.d.ts +202 -0
- package/dist/esm/shield/types.js +1 -0
- package/dist/esm/utxo.d.ts +235 -0
- package/dist/esm/utxo.js +382 -0
- package/dist/src/client.d.ts +407 -0
- package/dist/src/client.js +951 -0
- package/dist/src/compactNote.d.ts +107 -0
- package/dist/src/compactNote.js +167 -0
- package/dist/src/config.d.ts +82 -0
- package/dist/src/config.js +57 -0
- package/dist/src/events.d.ts +77 -0
- package/dist/src/events.js +167 -0
- package/dist/src/idl/privacy_pool.d.ts +5 -0
- package/dist/src/idl/privacy_pool.js +15218 -0
- package/dist/src/index.d.ts +16 -0
- package/dist/src/index.js +67 -0
- package/dist/src/merkle.d.ts +77 -0
- package/dist/src/merkle.js +156 -0
- package/dist/src/poseidon.d.ts +29 -0
- package/dist/src/poseidon.js +100 -0
- package/dist/src/program.d.ts +37 -0
- package/dist/src/program.js +61 -0
- package/dist/src/proof.d.ts +183 -0
- package/dist/src/proof.js +292 -0
- package/dist/src/prover.d.ts +54 -0
- package/dist/src/prover.js +112 -0
- package/dist/src/random.d.ts +16 -0
- package/dist/src/random.js +28 -0
- package/dist/src/relayer.d.ts +318 -0
- package/dist/src/relayer.js +257 -0
- package/dist/src/retry.d.ts +32 -0
- package/dist/src/retry.js +75 -0
- package/dist/src/shield/alt.d.ts +87 -0
- package/dist/src/shield/alt.js +194 -0
- package/dist/src/shield/computeBudget.d.ts +61 -0
- package/dist/src/shield/computeBudget.js +64 -0
- package/dist/src/shield/errors.d.ts +58 -0
- package/dist/src/shield/errors.js +121 -0
- package/dist/src/shield/finalize.d.ts +45 -0
- package/dist/src/shield/finalize.js +119 -0
- package/dist/src/shield/index.d.ts +35 -0
- package/dist/src/shield/index.js +68 -0
- package/dist/src/shield/ix.d.ts +54 -0
- package/dist/src/shield/ix.js +119 -0
- package/dist/src/shield/owner.d.ts +36 -0
- package/dist/src/shield/owner.js +126 -0
- package/dist/src/shield/ports.d.ts +43 -0
- package/dist/src/shield/ports.js +153 -0
- package/dist/src/shield/preflight.d.ts +30 -0
- package/dist/src/shield/preflight.js +154 -0
- package/dist/src/shield/shield.d.ts +68 -0
- package/dist/src/shield/shield.js +499 -0
- package/dist/src/shield/types.d.ts +202 -0
- package/dist/src/shield/types.js +2 -0
- package/dist/src/utxo.d.ts +235 -0
- package/dist/src/utxo.js +407 -0
- package/dist/tests/compact-note.test.d.ts +1 -0
- package/dist/tests/compact-note.test.js +173 -0
- package/dist/tests/config.test.d.ts +1 -0
- package/dist/tests/config.test.js +102 -0
- package/dist/tests/edge-cases.test.d.ts +1 -0
- package/dist/tests/edge-cases.test.js +220 -0
- package/dist/tests/encryption.test.d.ts +1 -0
- package/dist/tests/encryption.test.js +215 -0
- package/dist/tests/events.test.d.ts +1 -0
- package/dist/tests/events.test.js +78 -0
- package/dist/tests/multi-tree.test.d.ts +1 -0
- package/dist/tests/multi-tree.test.js +405 -0
- package/dist/tests/pda.test.d.ts +1 -0
- package/dist/tests/pda.test.js +229 -0
- package/dist/tests/poseidon-builder-parity.test.d.ts +1 -0
- package/dist/tests/poseidon-builder-parity.test.js +72 -0
- package/dist/tests/poseidon.test.d.ts +1 -0
- package/dist/tests/poseidon.test.js +142 -0
- package/dist/tests/proof.test.d.ts +1 -0
- package/dist/tests/proof.test.js +296 -0
- package/dist/tests/relayer.test.d.ts +1 -0
- package/dist/tests/relayer.test.js +271 -0
- package/dist/tests/sdk.integration.test.d.ts +1 -0
- package/dist/tests/sdk.integration.test.js +330 -0
- package/dist/tests/shield-owner.test.d.ts +1 -0
- package/dist/tests/shield-owner.test.js +89 -0
- package/dist/tests/shield-preflight.test.d.ts +1 -0
- package/dist/tests/shield-preflight.test.js +87 -0
- package/dist/tests/shield-realproof.test.d.ts +1 -0
- package/dist/tests/shield-realproof.test.js +272 -0
- package/dist/tests/shield.test.d.ts +1 -0
- package/dist/tests/shield.test.js +403 -0
- package/dist/tests/utxo.test.d.ts +1 -0
- package/dist/tests/utxo.test.js +140 -0
- package/package.json +105 -11
- package/poseidon.d.ts +2 -0
- package/poseidon.js +4 -0
- package/proof.d.ts +2 -0
- package/proof.js +4 -0
- package/prover.d.ts +2 -0
- package/prover.js +4 -0
- package/shield.d.ts +2 -0
- package/shield.js +4 -0
- 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/README.md
CHANGED
|
@@ -1,35 +1,834 @@
|
|
|
1
|
-
# @
|
|
1
|
+
# @veilo/sdk-core
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
TypeScript SDK for the Veilo Privacy Pool Anchor program.
|
|
4
4
|
|
|
5
|
-
This package
|
|
5
|
+
This package provides a complete UTXO-based privacy protocol implementation on Solana with:
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
7
|
+
- **UTXO Model**: Full support for unspent transaction outputs with Poseidon commitments
|
|
8
|
+
- **Multi-Tree Support**: Multiple concurrent Merkle trees for improved scalability
|
|
9
|
+
- **ZK Proofs**: Integration with Circom circuits for private transactions
|
|
10
|
+
- **Private Swaps**: Atomic cross-pool swaps via Jupiter (native SOL + SPL tokens)
|
|
11
|
+
- **Note Encryption**: NaCl-based encrypted UTXO notes and blind mailbox delivery
|
|
12
|
+
- **Event Scanning**: Reconstruct Merkle trees from on-chain events
|
|
13
|
+
- **Poseidon Hashing**: BN254-curve compatible hashing using circomlibjs
|
|
14
|
+
- **Relayer Support**: Built-in relayer infrastructure for private withdrawals and swaps
|
|
13
15
|
|
|
14
|
-
> **Status:**
|
|
15
|
-
>
|
|
16
|
-
> On-chain, `proof: Vec<u8>` is treated as opaque bytes (hook for a future verifier).
|
|
16
|
+
> **Status:** Active development. The SDK supports full transaction privacy with ZK-SNARK proofs.
|
|
17
|
+
> Proofs are generated off-chain and verified on-chain using Groth16.
|
|
17
18
|
|
|
18
19
|
---
|
|
19
20
|
|
|
20
|
-
## 1.
|
|
21
|
+
## 1. Installation
|
|
21
22
|
|
|
22
|
-
|
|
23
|
+
```bash
|
|
24
|
+
npm install @veilo/sdk-core
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## 2. Prerequisites
|
|
30
|
+
|
|
31
|
+
- A running Solana validator (localnet/devnet/mainnet)
|
|
32
|
+
- The `privacy-pool` program deployed to the network
|
|
33
|
+
- A funded keypair
|
|
23
34
|
|
|
24
35
|
```bash
|
|
25
|
-
|
|
26
|
-
|
|
36
|
+
export ANCHOR_PROVIDER_URL=https://api.devnet.solana.com
|
|
37
|
+
export ANCHOR_WALLET=$HOME/.config/solana/id.json
|
|
27
38
|
```
|
|
28
39
|
|
|
29
|
-
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## 3. Build
|
|
30
43
|
|
|
31
44
|
```bash
|
|
32
|
-
npm
|
|
45
|
+
npm run build
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## 4. SDK API
|
|
51
|
+
|
|
52
|
+
### 4.1 PDA Helpers
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
import {
|
|
56
|
+
getPoolPdas,
|
|
57
|
+
getNoteTreePda,
|
|
58
|
+
getGlobalConfigPda,
|
|
59
|
+
getNullifierMarkerPda,
|
|
60
|
+
getSwapExecutorPda,
|
|
61
|
+
} from "@veilo/sdk-core";
|
|
62
|
+
import { PublicKey } from "@solana/web3.js";
|
|
63
|
+
|
|
64
|
+
const programId = new PublicKey(
|
|
65
|
+
"YourProgram1111111111111111111111111111111111",
|
|
66
|
+
);
|
|
67
|
+
const mintAddress = new PublicKey(
|
|
68
|
+
"So11111111111111111111111111111111111111112",
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
// Pool PDAs (config, vault, nullifiers)
|
|
72
|
+
const { config, vault, nullifiers } = getPoolPdas(programId, mintAddress);
|
|
73
|
+
|
|
74
|
+
// Note tree PDA for tree ID 0
|
|
75
|
+
const noteTree = getNoteTreePda(programId, mintAddress, 0);
|
|
76
|
+
|
|
77
|
+
// Global config PDA (one per program)
|
|
78
|
+
const globalConfig = getGlobalConfigPda(programId);
|
|
79
|
+
|
|
80
|
+
// Nullifier marker PDA (prevents double-spend)
|
|
81
|
+
const nullifier = new Uint8Array(32);
|
|
82
|
+
const marker = getNullifierMarkerPda(programId, mintAddress, nullifier);
|
|
83
|
+
|
|
84
|
+
// Swap executor PDA (for cross-pool swaps)
|
|
85
|
+
const relayerPubkey = new PublicKey("...");
|
|
86
|
+
const executor = getSwapExecutorPda(
|
|
87
|
+
programId,
|
|
88
|
+
sourceMint,
|
|
89
|
+
destMint,
|
|
90
|
+
inputNullifier0, // Uint8Array[32]
|
|
91
|
+
relayerPubkey,
|
|
92
|
+
);
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**PDA seeds (v3):**
|
|
96
|
+
|
|
97
|
+
| Account | Seeds |
|
|
98
|
+
| ---------------- | ------------------------------------------------------------------- |
|
|
99
|
+
| Config | `["privacy_config_v3", mint]` |
|
|
100
|
+
| Vault | `["privacy_vault_v3", mint]` |
|
|
101
|
+
| Note Tree | `["privacy_note_tree_v3", mint, tree_id]` |
|
|
102
|
+
| Nullifiers | `["privacy_nullifiers_v3", mint]` |
|
|
103
|
+
| Nullifier Marker | `["nullifier_v3", mint, nullifier]` |
|
|
104
|
+
| Global Config | `["global_config_v1"]` |
|
|
105
|
+
| Swap Executor | `["swap_executor_v1", source_mint, dest_mint, nullifier0, relayer]` |
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
### 4.2 Pool Initialization
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
import {
|
|
113
|
+
initializeGlobalConfig,
|
|
114
|
+
initializePool,
|
|
115
|
+
updatePoolConfig,
|
|
116
|
+
addMerkleTree,
|
|
117
|
+
getPoolConfig,
|
|
118
|
+
updateGlobalConfig,
|
|
119
|
+
} from "@veilo/sdk-core";
|
|
120
|
+
import { NATIVE_SOL_MINT, sol } from "@veilo/sdk-core/config";
|
|
121
|
+
|
|
122
|
+
// Initialize global config (once per program)
|
|
123
|
+
await initializeGlobalConfig({ program, admin: adminKeypair });
|
|
124
|
+
|
|
125
|
+
// Update global config
|
|
126
|
+
await updateGlobalConfig({
|
|
127
|
+
program,
|
|
128
|
+
admin: adminKeypair,
|
|
129
|
+
newAdmin: newAdminPubkey, // optional
|
|
130
|
+
paused: false, // optional
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// Initialize a pool for native SOL
|
|
134
|
+
await initializePool({
|
|
135
|
+
program,
|
|
136
|
+
payer: adminKeypair,
|
|
137
|
+
admin: adminKeypair,
|
|
138
|
+
mintAddress: NATIVE_SOL_MINT,
|
|
139
|
+
minDepositAmount: sol(0.1),
|
|
140
|
+
maxDepositAmount: sol(100),
|
|
141
|
+
minWithdrawAmount: sol(0.1),
|
|
142
|
+
maxWithdrawAmount: sol(100),
|
|
143
|
+
feeBps: 50, // 0.5%
|
|
144
|
+
feeErrorMarginBps: 10, // 0.1% margin
|
|
145
|
+
minWithdrawalFee: 1_000_000n,
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Add Merkle tree (tree ID 0)
|
|
149
|
+
await addMerkleTree({
|
|
150
|
+
program,
|
|
151
|
+
payer: adminKeypair,
|
|
152
|
+
mintAddress: NATIVE_SOL_MINT,
|
|
153
|
+
treeId: 0,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// Update pool fee
|
|
157
|
+
await updatePoolConfig({
|
|
158
|
+
program,
|
|
159
|
+
admin: adminKeypair,
|
|
160
|
+
mintAddress: NATIVE_SOL_MINT,
|
|
161
|
+
feeBps: 30,
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// Read pool configuration
|
|
165
|
+
const poolConfig = await getPoolConfig(program, NATIVE_SOL_MINT);
|
|
166
|
+
console.log("TVL:", poolConfig.totalTvl, " Fee:", poolConfig.feeBps, "bps");
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
### 4.3 On-Chain Account Queries
|
|
172
|
+
|
|
173
|
+
```ts
|
|
174
|
+
import {
|
|
175
|
+
fetchPoolConfig,
|
|
176
|
+
checkNullifierSpent,
|
|
177
|
+
getTreeInfo,
|
|
178
|
+
getAllTreeInfo,
|
|
179
|
+
getBestTreeForDeposit,
|
|
180
|
+
} from "@veilo/sdk-core";
|
|
181
|
+
|
|
182
|
+
// Fetch decoded PrivacyConfig account
|
|
183
|
+
const config = await fetchPoolConfig(program, NATIVE_SOL_MINT);
|
|
184
|
+
|
|
185
|
+
// Check if a nullifier has been spent
|
|
186
|
+
const spent = await checkNullifierSpent(program, NATIVE_SOL_MINT, nullifier);
|
|
187
|
+
|
|
188
|
+
// Info for a single tree
|
|
189
|
+
const info = await getTreeInfo(program, NATIVE_SOL_MINT, 0);
|
|
190
|
+
// info = { treeId, leafCount, root, isFull }
|
|
191
|
+
|
|
192
|
+
// Info for all trees in a pool
|
|
193
|
+
const allInfo = await getAllTreeInfo(program, NATIVE_SOL_MINT);
|
|
194
|
+
|
|
195
|
+
// Pick the best tree for depositing (least full with capacity)
|
|
196
|
+
const best = await getBestTreeForDeposit(program, NATIVE_SOL_MINT);
|
|
197
|
+
// best = { treeId, leafCount, root }
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
### 4.4 UTXO Management
|
|
203
|
+
|
|
204
|
+
```ts
|
|
205
|
+
import {
|
|
206
|
+
generateKeypair,
|
|
207
|
+
keypairFromPrivateKey,
|
|
208
|
+
createUTXO,
|
|
209
|
+
createOwnedUTXO,
|
|
210
|
+
createOwnedZeroUTXO,
|
|
211
|
+
deriveNullifier,
|
|
212
|
+
type Keypair,
|
|
213
|
+
type UTXO,
|
|
214
|
+
type SerializedUTXO,
|
|
215
|
+
type InputUTXO,
|
|
216
|
+
} from "@veilo/sdk-core";
|
|
217
|
+
import { NATIVE_SOL_MINT } from "@veilo/sdk-core/config";
|
|
218
|
+
import { pubkeyToField } from "@veilo/sdk-core";
|
|
219
|
+
|
|
220
|
+
// Generate a random UTXO keypair
|
|
221
|
+
const keypair: Keypair = generateKeypair();
|
|
222
|
+
// keypair = { privateKey: bigint, publicKey: bigint }
|
|
223
|
+
|
|
224
|
+
// Restore from private key
|
|
225
|
+
const restored = keypairFromPrivateKey(privateKeyBigInt);
|
|
226
|
+
|
|
227
|
+
// Create an owned UTXO (includes private key)
|
|
228
|
+
const ownedUtxo: SerializedUTXO = createOwnedUTXO({
|
|
229
|
+
amount: 1_000_000_000n,
|
|
230
|
+
privateKey: keypair.privateKey,
|
|
231
|
+
mintAddress: NATIVE_SOL_MINT,
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
// Create a zero-value UTXO (for unused inputs/outputs)
|
|
235
|
+
const zeroUtxo = createOwnedZeroUTXO(pubkeyToField(NATIVE_SOL_MINT), keypair);
|
|
236
|
+
|
|
237
|
+
// Derive nullifier for spending
|
|
238
|
+
const pathIndex = 0; // leaf index in Merkle tree
|
|
239
|
+
const treeId = 0;
|
|
240
|
+
const nullifier = deriveNullifier(
|
|
241
|
+
ownedUtxo.privateKey,
|
|
242
|
+
ownedUtxo.commitment,
|
|
243
|
+
pathIndex,
|
|
244
|
+
treeId,
|
|
245
|
+
);
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
**UTXO commitment:** `Poseidon(amount, pubkey, blinding, mintAddress)`
|
|
249
|
+
|
|
250
|
+
**Nullifier:** `Poseidon(privateKey, commitment, pathIndex, treeId)`
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
### 4.5 UTXO Encryption
|
|
255
|
+
|
|
256
|
+
The SDK provides NaCl-based encryption for UTXO notes (relayer storage) and blind mailbox delivery (wallet-to-wallet).
|
|
257
|
+
|
|
258
|
+
```ts
|
|
259
|
+
import {
|
|
260
|
+
deriveEncryptionKeypair,
|
|
261
|
+
encryptUTXONote,
|
|
262
|
+
decryptUTXONote,
|
|
263
|
+
encryptBlindMailboxNote,
|
|
264
|
+
decryptBlindMailboxNote,
|
|
265
|
+
fetchAndDecryptNotes,
|
|
266
|
+
computeSignature,
|
|
267
|
+
inputUTXOToCircuitFormat,
|
|
268
|
+
type EncryptedNote,
|
|
269
|
+
type BlindMailboxNote,
|
|
270
|
+
type BlindMailboxNoteData,
|
|
271
|
+
type DecryptedNote,
|
|
272
|
+
} from "@veilo/sdk-core";
|
|
273
|
+
import { Keypair as SolanaKeypair } from "@solana/web3.js";
|
|
274
|
+
|
|
275
|
+
// Derive a NaCl encryption keypair from a UTXO private key
|
|
276
|
+
const encKeypair = deriveEncryptionKeypair(utxoKeypair.privateKey);
|
|
277
|
+
// encKeypair = { publicKey: Uint8Array[32], secretKey: Uint8Array[32] }
|
|
278
|
+
|
|
279
|
+
// Encrypt a UTXO note for relay storage
|
|
280
|
+
const encryptedNote: EncryptedNote = encryptUTXONote(
|
|
281
|
+
serializedUtxo, // SerializedUTXO
|
|
282
|
+
encKeypair.publicKey, // NaCl public key (Uint8Array[32])
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
// Decrypt a UTXO note
|
|
286
|
+
const decrypted: SerializedUTXO | null = decryptUTXONote(
|
|
287
|
+
encryptedNote,
|
|
288
|
+
encKeypair.secretKey, // NaCl secret key (Uint8Array[32])
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
// Blind mailbox: encrypt for a Solana wallet keypair
|
|
292
|
+
const recipientKeypair = SolanaKeypair.generate();
|
|
293
|
+
|
|
294
|
+
const mailboxNote: BlindMailboxNote = encryptBlindMailboxNote(
|
|
295
|
+
serializedUtxo,
|
|
296
|
+
recipientKeypair.publicKey.toBytes(), // ed25519 public key → X25519 DH
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
// Recipient decrypts using their Solana secret key (64-byte)
|
|
300
|
+
const mailboxDecrypted: DecryptedNote | null = decryptBlindMailboxNote(
|
|
301
|
+
mailboxNote,
|
|
302
|
+
recipientKeypair.secretKey, // 64-byte Solana secret key
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
// Compute UTXO signature (used in circuit)
|
|
306
|
+
const sig = computeSignature(utxoKeypair.privateKey, utxo.commitment);
|
|
307
|
+
|
|
308
|
+
// Format an InputUTXO for snarkjs circuit
|
|
309
|
+
const circuitFormat = inputUTXOToCircuitFormat(inputUtxo);
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
### 4.6 Merkle Tree Operations
|
|
315
|
+
|
|
316
|
+
```ts
|
|
317
|
+
import { MerkleTree } from "@veilo/sdk-core";
|
|
318
|
+
|
|
319
|
+
// Create a new Merkle tree (default depth: 22)
|
|
320
|
+
const tree = new MerkleTree();
|
|
321
|
+
|
|
322
|
+
// Insert commitments
|
|
323
|
+
const index = tree.insert(ownedUtxo.commitment);
|
|
324
|
+
|
|
325
|
+
// Current root
|
|
326
|
+
const root = tree.root();
|
|
327
|
+
|
|
328
|
+
// Merkle path for proof generation
|
|
329
|
+
const path = tree.path(index);
|
|
330
|
+
// path = { pathElements: Uint8Array[], pathIndices: number[] }
|
|
331
|
+
|
|
332
|
+
// Total leaves inserted
|
|
333
|
+
const count = tree.totalLeaves;
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
---
|
|
337
|
+
|
|
338
|
+
### 4.7 Transaction Operations
|
|
339
|
+
|
|
340
|
+
All transaction functions return `Promise<string>` (the transaction signature).
|
|
341
|
+
|
|
342
|
+
#### Deposits
|
|
343
|
+
|
|
344
|
+
```ts
|
|
345
|
+
import { deposit, type DepositResult } from "@veilo/sdk-core";
|
|
346
|
+
|
|
347
|
+
const result: DepositResult = await deposit({
|
|
348
|
+
program,
|
|
349
|
+
depositor: depositorKeypair,
|
|
350
|
+
mintAddress: NATIVE_SOL_MINT,
|
|
351
|
+
amount: 1_000_000_000n, // 1 SOL
|
|
352
|
+
recipientPubkey: pubkeyToField(depositorKeypair.publicKey), // bigint
|
|
353
|
+
tree,
|
|
354
|
+
proofBuilder,
|
|
355
|
+
treeId: 0,
|
|
356
|
+
});
|
|
357
|
+
// result = { outputUTXOs, leafIndices, root }
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
#### Withdrawals
|
|
361
|
+
|
|
362
|
+
```ts
|
|
363
|
+
import { withdraw, type WithdrawResult } from "@veilo/sdk-core";
|
|
364
|
+
|
|
365
|
+
const result: WithdrawResult = await withdraw({
|
|
366
|
+
program,
|
|
367
|
+
relayer: relayerKeypair,
|
|
368
|
+
mintAddress: NATIVE_SOL_MINT,
|
|
369
|
+
amount: 900_000_000n, // 0.9 SOL
|
|
370
|
+
fee: 100_000_000n, // 0.1 SOL to relayer
|
|
371
|
+
inputs: [inputUtxo1, zeroInputUtxo],
|
|
372
|
+
outputs: [changeUtxo, zeroOutputUtxo],
|
|
373
|
+
recipient: recipientKeypair.publicKey,
|
|
374
|
+
tree,
|
|
375
|
+
proofBuilder,
|
|
376
|
+
treeId: 0,
|
|
377
|
+
});
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
#### Private Transfers
|
|
381
|
+
|
|
382
|
+
```ts
|
|
383
|
+
import { privateTransfer, type TransferResult } from "@veilo/sdk-core";
|
|
384
|
+
|
|
385
|
+
const result: TransferResult = await privateTransfer({
|
|
386
|
+
program,
|
|
387
|
+
relayer: relayerKeypair,
|
|
388
|
+
mintAddress: NATIVE_SOL_MINT,
|
|
389
|
+
inputs: [inputUtxo1, zeroInputUtxo],
|
|
390
|
+
outputs: [recipientUtxo, changeUtxo],
|
|
391
|
+
recipient: relayerKeypair.publicKey,
|
|
392
|
+
tree,
|
|
393
|
+
proofBuilder,
|
|
394
|
+
treeId: 0,
|
|
395
|
+
});
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
#### Low-Level `transact`
|
|
399
|
+
|
|
400
|
+
```ts
|
|
401
|
+
import { transact } from "@veilo/sdk-core";
|
|
402
|
+
|
|
403
|
+
const signature: string = await transact({
|
|
404
|
+
program,
|
|
405
|
+
relayer: relayerKeypair,
|
|
406
|
+
mintAddress: NATIVE_SOL_MINT,
|
|
407
|
+
inputTreeId: 0,
|
|
408
|
+
outputTreeId: 0,
|
|
409
|
+
root: tree.root(),
|
|
410
|
+
publicAmount: 0n,
|
|
411
|
+
inputNullifiers: [nullifier1, nullifier2],
|
|
412
|
+
outputCommitments: [output1.commitment, output2.commitment],
|
|
413
|
+
extData: { recipient, relayer: relayerPubkey, fee: 0n, refund: 0n, claimant },
|
|
414
|
+
proof,
|
|
415
|
+
});
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
---
|
|
419
|
+
|
|
420
|
+
#### Wallet-agnostic shielding
|
|
421
|
+
|
|
422
|
+
`shield()` builds an unsigned v0 deposit transaction for any Solana wallet to
|
|
423
|
+
sign. The signer funds the deposit, while the resulting private note can belong
|
|
424
|
+
to a different Veilo user. The SDK never receives a wallet secret key and never
|
|
425
|
+
broadcasts on the caller's behalf.
|
|
426
|
+
|
|
427
|
+
```ts
|
|
428
|
+
import {
|
|
429
|
+
createTransactionProver,
|
|
430
|
+
resolveShieldOwner,
|
|
431
|
+
shield,
|
|
432
|
+
} from "@veilo/sdk-core";
|
|
433
|
+
|
|
434
|
+
const owner = await resolveShieldOwner({ username: "alice" });
|
|
435
|
+
const result = await shield({
|
|
436
|
+
connection,
|
|
437
|
+
amount: 5_000_000n,
|
|
438
|
+
mint: USDC_MINT,
|
|
439
|
+
owner,
|
|
440
|
+
signer: { publicKey: wallet.publicKey },
|
|
441
|
+
prover: createTransactionProver({ wasmPath, zkeyPath }),
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
const signed = await wallet.signTransaction(result.transaction);
|
|
445
|
+
const signature = await connection.sendRawTransaction(signed.serialize());
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
Use `rebuild()` if the blockhash or deadline expires, and call
|
|
449
|
+
`finalizeShield()` after confirmation to obtain the authoritative Merkle leaf
|
|
450
|
+
index. See [SHIELD_INTEGRATION.md](./SHIELD_INTEGRATION.md) for the complete
|
|
451
|
+
build → sign → submit → finalize flow, retry rules, owner-key invariant, and
|
|
452
|
+
fee/rent requirements.
|
|
453
|
+
|
|
454
|
+
---
|
|
455
|
+
|
|
456
|
+
### 4.8 Private Swaps
|
|
457
|
+
|
|
458
|
+
Cross-pool private swaps via Jupiter/Raydium. For native SOL source pools the SDK automatically composes the required `fund_native_source` + `transact_swap` instructions into a single atomic transaction.
|
|
459
|
+
|
|
460
|
+
```ts
|
|
461
|
+
import {
|
|
462
|
+
transactSwap,
|
|
463
|
+
getSwapExecutorPda,
|
|
464
|
+
fundNativeSource,
|
|
465
|
+
type SwapParams,
|
|
466
|
+
type SwapProofStruct,
|
|
467
|
+
} from "@veilo/sdk-core";
|
|
468
|
+
|
|
469
|
+
// Execute a private swap (returns transaction signature)
|
|
470
|
+
const signature: string = await transactSwap({
|
|
471
|
+
program,
|
|
472
|
+
relayer: relayerKeypair,
|
|
473
|
+
sourceMint, // NATIVE_SOL_MINT or SPL token mint
|
|
474
|
+
destMint,
|
|
475
|
+
sourceRoot: tree.root(),
|
|
476
|
+
sourceTreeId: 0,
|
|
477
|
+
destTreeId: 0,
|
|
478
|
+
inputNullifiers: [nullifier0, nullifier1],
|
|
479
|
+
outputCommitments: [changeCommitment, destCommitment],
|
|
480
|
+
proof, // SwapProofStruct
|
|
481
|
+
swapParams: {
|
|
482
|
+
minAmountOut: 950_000_000n,
|
|
483
|
+
deadline: BigInt(Math.floor(Date.now() / 1000) + 60),
|
|
484
|
+
sourceMint,
|
|
485
|
+
destMint,
|
|
486
|
+
destAmount: 950_000_000n,
|
|
487
|
+
swapDataHash: new Uint8Array(32), // SHA-256 of DEX ix bytes, or zeros
|
|
488
|
+
},
|
|
489
|
+
swapAmount: 1_000_000_000n,
|
|
490
|
+
swapData: jupiterInstructionBytes, // Buffer
|
|
491
|
+
extData: { recipient, relayer: relayerPubkey, fee, refund: 0n, claimant },
|
|
492
|
+
sourceVaultTokenAccount,
|
|
493
|
+
sourceMintAccount,
|
|
494
|
+
destVaultTokenAccount,
|
|
495
|
+
destMintAccount,
|
|
496
|
+
relayerTokenAccount,
|
|
497
|
+
swapProgram: JUPITER_PROGRAM_ID,
|
|
498
|
+
jupiterEventAuthority,
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
// Build the fund_native_source instruction standalone (advanced)
|
|
502
|
+
const fundIx = await fundNativeSource({
|
|
503
|
+
program,
|
|
504
|
+
relayer: relayerKeypair,
|
|
505
|
+
sourceMint: NATIVE_SOL_MINT,
|
|
506
|
+
destMint,
|
|
507
|
+
inputNullifier0: nullifier0,
|
|
508
|
+
swapAmount: 1_000_000_000n,
|
|
509
|
+
});
|
|
510
|
+
// Returns TransactionInstruction — must be first ix in same tx as transact_swap
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
> **Note:** `transactSwap` handles the atomicity requirement automatically. Only use `fundNativeSource` directly if you are building transactions manually.
|
|
514
|
+
|
|
515
|
+
---
|
|
516
|
+
|
|
517
|
+
### 4.9 Event Scanning & Tree Reconstruction
|
|
518
|
+
|
|
519
|
+
```ts
|
|
520
|
+
import {
|
|
521
|
+
scanCommitmentEvents,
|
|
522
|
+
scanNullifierEvents,
|
|
523
|
+
buildTreeFromEvents,
|
|
524
|
+
type CommitmentEvent,
|
|
525
|
+
type NullifierSpentEvent,
|
|
526
|
+
} from "@veilo/sdk-core";
|
|
527
|
+
|
|
528
|
+
// Scan all commitment events for a mint (paginated by signature)
|
|
529
|
+
const { events, latestSignature } = await scanCommitmentEvents({
|
|
530
|
+
program,
|
|
531
|
+
mintAddress: NATIVE_SOL_MINT,
|
|
532
|
+
treeId: 0,
|
|
533
|
+
beforeSignature: undefined, // or last known signature for pagination
|
|
534
|
+
limit: 1000,
|
|
535
|
+
});
|
|
536
|
+
// events: CommitmentEvent[]
|
|
537
|
+
// CommitmentEvent = { commitment, leafIndex, newRoot, timestamp, mintAddress, treeId }
|
|
538
|
+
|
|
539
|
+
// Scan spent nullifier events
|
|
540
|
+
const { events: nullEvents } = await scanNullifierEvents({
|
|
541
|
+
program,
|
|
542
|
+
mintAddress: NATIVE_SOL_MINT,
|
|
543
|
+
beforeSignature: undefined,
|
|
544
|
+
limit: 1000,
|
|
545
|
+
});
|
|
546
|
+
// NullifierSpentEvent = { nullifier, mintAddress, treeId }
|
|
547
|
+
|
|
548
|
+
// Reconstruct a Merkle tree from on-chain events
|
|
549
|
+
const {
|
|
550
|
+
tree,
|
|
551
|
+
events: allEvents,
|
|
552
|
+
latestSignature: sig,
|
|
553
|
+
} = await buildTreeFromEvents({
|
|
554
|
+
program,
|
|
555
|
+
mintAddress: NATIVE_SOL_MINT,
|
|
556
|
+
treeId: 0,
|
|
557
|
+
});
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
---
|
|
561
|
+
|
|
562
|
+
### 4.10 Proof Generation
|
|
563
|
+
|
|
564
|
+
```ts
|
|
565
|
+
import {
|
|
566
|
+
prepareTransactionInputs,
|
|
567
|
+
formatInputsForSnarkjs,
|
|
568
|
+
computeExtDataHash,
|
|
569
|
+
encodeSnarkjsProofToTransactionProof,
|
|
570
|
+
packProofToBytes,
|
|
571
|
+
computeSwapParamsHash,
|
|
572
|
+
computeSwapDataHash,
|
|
573
|
+
type ExtData,
|
|
574
|
+
type TransactionCircuitInputs,
|
|
575
|
+
} from "@veilo/sdk-core";
|
|
576
|
+
|
|
577
|
+
// Prepare inputs for the transaction circuit
|
|
578
|
+
const circuitInputs: TransactionCircuitInputs = prepareTransactionInputs({
|
|
579
|
+
root: tree.root(),
|
|
580
|
+
publicAmount: 1_000_000_000n,
|
|
581
|
+
extData: { recipient, relayer: relayerPubkey, fee: 0n, refund: 0n, claimant },
|
|
582
|
+
mintAddress: NATIVE_SOL_MINT,
|
|
583
|
+
inputs: [input1, input2],
|
|
584
|
+
outputs: [output1, output2],
|
|
585
|
+
inputTreeId: 0,
|
|
586
|
+
outputTreeId: 0,
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
// Format for snarkjs (converts bigints / Uint8Arrays to strings)
|
|
590
|
+
const snarkjsInputs = formatInputsForSnarkjs(circuitInputs);
|
|
591
|
+
|
|
592
|
+
// After proof generation:
|
|
593
|
+
// const { proof } = await snarkjs.groth16.fullProve(snarkjsInputs, wasmPath, zkeyPath);
|
|
594
|
+
// const transactionProof = encodeSnarkjsProofToTransactionProof(proof);
|
|
595
|
+
|
|
596
|
+
// Compute ext data hash (matches on-chain computation)
|
|
597
|
+
const extDataHash = computeExtDataHash({
|
|
598
|
+
recipient,
|
|
599
|
+
relayer,
|
|
600
|
+
fee: 0n,
|
|
601
|
+
refund: 0n,
|
|
602
|
+
claimant,
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
// Compute swap param/data hashes (for swap circuit inputs)
|
|
606
|
+
const swapParamsHash = computeSwapParamsHash(swapParams);
|
|
607
|
+
const swapDataHash = computeSwapDataHash(jupiterInstructionBytes);
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
---
|
|
611
|
+
|
|
612
|
+
### 4.11 Fee Utilities
|
|
613
|
+
|
|
614
|
+
```ts
|
|
615
|
+
import {
|
|
616
|
+
computeWithdrawalFee,
|
|
617
|
+
computeSwapFee,
|
|
618
|
+
DEFAULT_FEE_BPS,
|
|
619
|
+
} from "@veilo/sdk-core/config";
|
|
620
|
+
|
|
621
|
+
// Compute protocol fee for a withdrawal
|
|
622
|
+
const fee = computeWithdrawalFee(amount, feeBps, minWithdrawalFee);
|
|
623
|
+
|
|
624
|
+
// Compute protocol fee for a swap
|
|
625
|
+
const swapFee = computeSwapFee(swapAmount, feeBps);
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
---
|
|
629
|
+
|
|
630
|
+
### 4.12 Relayer & Admin Management
|
|
631
|
+
|
|
632
|
+
```ts
|
|
633
|
+
import { addRelayer, setPaused } from "@veilo/sdk-core";
|
|
634
|
+
|
|
635
|
+
// Authorise a new relayer for a pool
|
|
636
|
+
await addRelayer({
|
|
637
|
+
program,
|
|
638
|
+
admin: adminKeypair,
|
|
639
|
+
mintAddress: NATIVE_SOL_MINT,
|
|
640
|
+
newRelayer: relayerPubkey,
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
// Pause or unpause a pool
|
|
644
|
+
await setPaused({
|
|
645
|
+
program,
|
|
646
|
+
admin: adminKeypair,
|
|
647
|
+
mintAddress: NATIVE_SOL_MINT,
|
|
648
|
+
paused: true,
|
|
649
|
+
});
|
|
650
|
+
```
|
|
651
|
+
|
|
652
|
+
---
|
|
653
|
+
|
|
654
|
+
### 4.13 Poseidon Utilities
|
|
655
|
+
|
|
656
|
+
```ts
|
|
657
|
+
import {
|
|
658
|
+
initPoseidon,
|
|
659
|
+
poseidon1,
|
|
660
|
+
poseidon2,
|
|
661
|
+
poseidon3,
|
|
662
|
+
poseidon4,
|
|
663
|
+
pubkeyToField,
|
|
664
|
+
bytesToBigIntBE,
|
|
665
|
+
bigIntToBytesBE,
|
|
666
|
+
BN254_FR_MODULUS,
|
|
667
|
+
} from "@veilo/sdk-core";
|
|
668
|
+
|
|
669
|
+
// Must be called once before using hash functions
|
|
670
|
+
await initPoseidon();
|
|
671
|
+
|
|
672
|
+
const h1 = poseidon1(12345n);
|
|
673
|
+
const h2 = poseidon2(12345n, 67890n);
|
|
674
|
+
const h3 = poseidon3(12345n, 67890n, 11111n);
|
|
675
|
+
const h4 = poseidon4(12345n, 67890n, 11111n, 22222n);
|
|
676
|
+
|
|
677
|
+
// Convert a Solana PublicKey to a BN254 field element
|
|
678
|
+
const field = pubkeyToField(NATIVE_SOL_MINT);
|
|
679
|
+
|
|
680
|
+
// Byte ↔ bigint helpers (big-endian)
|
|
681
|
+
const n = bytesToBigIntBE(bytes32);
|
|
682
|
+
const b = bigIntToBytesBE(someField, 32);
|
|
683
|
+
```
|
|
684
|
+
|
|
685
|
+
---
|
|
686
|
+
|
|
687
|
+
### 4.14 Error Utilities
|
|
688
|
+
|
|
689
|
+
```ts
|
|
690
|
+
import { parseOnChainError } from "@veilo/sdk-core";
|
|
691
|
+
|
|
692
|
+
try {
|
|
693
|
+
await transactSwap({ ... });
|
|
694
|
+
} catch (err) {
|
|
695
|
+
// Returns a human-readable error string from Anchor/program errors
|
|
696
|
+
const msg = parseOnChainError(err);
|
|
697
|
+
console.error("Swap failed:", msg);
|
|
698
|
+
}
|
|
699
|
+
```
|
|
700
|
+
|
|
701
|
+
---
|
|
702
|
+
|
|
703
|
+
## 5. Architecture
|
|
704
|
+
|
|
705
|
+
### Transaction Model
|
|
706
|
+
|
|
707
|
+
Veilo uses a UTXO privacy model inspired by Zcash and Tornado Cash Nova:
|
|
708
|
+
|
|
709
|
+
- **Inputs**: 2 UTXOs (zero-value for deposits)
|
|
710
|
+
- **Outputs**: 2 UTXOs (zero-value for withdrawals)
|
|
711
|
+
- **Public Amount**: net pool change — positive = deposit, negative = withdrawal, zero = private transfer or swap
|
|
712
|
+
|
|
713
|
+
Each transaction consumes 2 input UTXOs (Merkle proofs), creates 2 output UTXOs (commitments inserted to tree), and generates 2 nullifiers to prevent double-spend.
|
|
714
|
+
|
|
715
|
+
### Swap Architecture
|
|
716
|
+
|
|
717
|
+
Private swaps require two instructions in a **single atomic transaction**:
|
|
718
|
+
|
|
719
|
+
1. `fund_native_source` — pre-funds the swap executor with SOL from the vault (native SOL pools only)
|
|
720
|
+
2. `transact_swap` — verifies the ZK proof, spends input UTXOs, creates output UTXOs, and executes the DEX swap
|
|
721
|
+
|
|
722
|
+
`transactSwap()` handles this automatically. The on-chain program validates atomicity via the instructions sysvar.
|
|
723
|
+
|
|
724
|
+
### Privacy Guarantees
|
|
725
|
+
|
|
726
|
+
- **Commitment hiding**: amount, owner, blinding are hidden via Poseidon
|
|
727
|
+
- **Nullifier uniqueness**: each UTXO can only be spent once
|
|
728
|
+
- **Unlinkability**: no public link between inputs and outputs
|
|
729
|
+
- **ZK proofs**: Groth16 verified on-chain
|
|
730
|
+
|
|
731
|
+
### Constants
|
|
732
|
+
|
|
733
|
+
```ts
|
|
734
|
+
import {
|
|
735
|
+
NATIVE_SOL_MINT, // PublicKey.default — native SOL pools
|
|
736
|
+
MERKLE_TREE_DEPTH, // 22
|
|
737
|
+
ROOT_HISTORY_SIZE, // 256
|
|
738
|
+
DEFAULT_FEE_BPS, // 50 (0.5%)
|
|
739
|
+
sol, // sol(1) === 1_000_000_000n
|
|
740
|
+
} from "@veilo/sdk-core/config";
|
|
741
|
+
```
|
|
742
|
+
|
|
743
|
+
### Type Exports
|
|
744
|
+
|
|
745
|
+
```ts
|
|
746
|
+
import type {
|
|
747
|
+
// UTXO
|
|
748
|
+
Keypair,
|
|
749
|
+
UTXO,
|
|
750
|
+
SerializedUTXO,
|
|
751
|
+
InputUTXO,
|
|
752
|
+
// Encryption
|
|
753
|
+
EncryptedNote,
|
|
754
|
+
BlindMailboxNote,
|
|
755
|
+
BlindMailboxNoteData,
|
|
756
|
+
DecryptedNote,
|
|
757
|
+
// Proof
|
|
758
|
+
ExtData,
|
|
759
|
+
TransactionCircuitInputs,
|
|
760
|
+
TransactionProofStruct,
|
|
761
|
+
RawProof,
|
|
762
|
+
TransactionProofBuilder,
|
|
763
|
+
// Swap
|
|
764
|
+
SwapProofStruct,
|
|
765
|
+
SwapParams,
|
|
766
|
+
// Tree / events
|
|
767
|
+
MerklePath,
|
|
768
|
+
CircuitMerklePath,
|
|
769
|
+
TreeInfo,
|
|
770
|
+
CommitmentEvent,
|
|
771
|
+
NullifierSpentEvent,
|
|
772
|
+
// Results
|
|
773
|
+
DepositResult,
|
|
774
|
+
WithdrawResult,
|
|
775
|
+
TransferResult,
|
|
776
|
+
// Config
|
|
777
|
+
PoolInitConfig,
|
|
778
|
+
PrivacyConfigAccount,
|
|
779
|
+
GlobalConfigAccount,
|
|
780
|
+
} from "@veilo/sdk-core";
|
|
781
|
+
```
|
|
782
|
+
|
|
783
|
+
---
|
|
784
|
+
|
|
785
|
+
## 6. Development
|
|
786
|
+
|
|
787
|
+
```bash
|
|
788
|
+
# Run tests (requires devnet or local validator)
|
|
789
|
+
npm test
|
|
790
|
+
```
|
|
791
|
+
|
|
792
|
+
---
|
|
793
|
+
|
|
794
|
+
## 7. Resources
|
|
795
|
+
|
|
796
|
+
- **Repository**: https://github.com/VeiloSolana/veilo-sdk
|
|
797
|
+
- **Circomlibjs**: https://github.com/iden3/circomlibjs
|
|
798
|
+
- **Poseidon Hash**: https://www.poseidon-hash.info/
|
|
799
|
+
|
|
800
|
+
---
|
|
801
|
+
|
|
802
|
+
## License
|
|
803
|
+
|
|
804
|
+
ISC
|
|
805
|
+
|
|
806
|
+
This package provides a complete UTXO-based privacy protocol implementation on Solana with:
|
|
807
|
+
|
|
808
|
+
- **UTXO Model**: Full support for unspent transaction outputs with Poseidon commitments
|
|
809
|
+
- **Multi-Tree Support**: Multiple concurrent Merkle trees for improved scalability
|
|
810
|
+
- **ZK Proofs**: Integration with Circom circuits for private transactions
|
|
811
|
+
- **Flexible Operations**: Deposits, withdrawals, and private transfers
|
|
812
|
+
- **Poseidon Hashing**: BN254-curve compatible hashing using circomlibjs
|
|
813
|
+
- **Merkle Trees**: Off-chain Merkle tree management with proof generation
|
|
814
|
+
- **Relayer Support**: Built-in relayer infrastructure for private withdrawals
|
|
815
|
+
|
|
816
|
+
> **Status:** Active development. The SDK supports full transaction privacy with ZK-SNARK proofs.
|
|
817
|
+
> Proofs are generated off-chain and verified on-chain using Groth16.
|
|
818
|
+
|
|
819
|
+
---
|
|
820
|
+
|
|
821
|
+
## 1. Installation
|
|
822
|
+
|
|
823
|
+
```bash
|
|
824
|
+
npm install @veilo/sdk-core
|
|
825
|
+
```
|
|
826
|
+
|
|
827
|
+
Or from source:
|
|
828
|
+
|
|
829
|
+
```bash
|
|
830
|
+
cd core-sdk
|
|
831
|
+
npm install
|
|
33
832
|
```
|
|
34
833
|
|
|
35
834
|
---
|
|
@@ -38,20 +837,20 @@ npm install @zkprivacysol/sdk-core
|
|
|
38
837
|
|
|
39
838
|
You need:
|
|
40
839
|
|
|
41
|
-
- A running Solana validator (localnet
|
|
840
|
+
- A running Solana validator (localnet/devnet/mainnet):
|
|
42
841
|
|
|
43
842
|
```bash
|
|
44
843
|
solana-test-validator
|
|
45
844
|
```
|
|
46
845
|
|
|
47
|
-
- The `privacy-pool` program
|
|
48
|
-
- The `privacy-pool` Anchor IDL available
|
|
846
|
+
- The `privacy-pool` program deployed to the network
|
|
847
|
+
- The `privacy-pool` Anchor IDL available:
|
|
49
848
|
|
|
50
849
|
```text
|
|
51
|
-
|
|
850
|
+
idl/idl/privacy_pool.json
|
|
52
851
|
```
|
|
53
852
|
|
|
54
|
-
- A funded keypair
|
|
853
|
+
- A funded keypair:
|
|
55
854
|
|
|
56
855
|
```bash
|
|
57
856
|
solana config set --url http://127.0.0.1:8899
|
|
@@ -59,7 +858,7 @@ You need:
|
|
|
59
858
|
solana airdrop 10
|
|
60
859
|
```
|
|
61
860
|
|
|
62
|
-
Environment variables
|
|
861
|
+
Environment variables:
|
|
63
862
|
|
|
64
863
|
```bash
|
|
65
864
|
export ANCHOR_PROVIDER_URL=http://127.0.0.1:8899
|
|
@@ -68,372 +867,597 @@ export ANCHOR_WALLET=$HOME/.config/solana/id.json
|
|
|
68
867
|
|
|
69
868
|
---
|
|
70
869
|
|
|
71
|
-
## 3. Build
|
|
72
|
-
|
|
73
|
-
From `packages/sdk-core`:
|
|
870
|
+
## 3. Build
|
|
74
871
|
|
|
75
872
|
```bash
|
|
76
|
-
# Typecheck & build to dist/
|
|
77
873
|
npm run build
|
|
78
|
-
|
|
79
|
-
# Run unit + integration tests
|
|
80
|
-
npm test
|
|
81
874
|
```
|
|
82
875
|
|
|
83
|
-
|
|
876
|
+
---
|
|
84
877
|
|
|
85
|
-
|
|
86
|
-
- `createRandomNote` / `encodeNoteToBytes` / `commitNote` / `createNoteWithCommitment`
|
|
87
|
-
- Ensures 32-byte commitments, deterministic encoding, etc.
|
|
878
|
+
## 4. SDK API
|
|
88
879
|
|
|
89
|
-
|
|
90
|
-
- Loads the `privacy-pool` IDL from `privacy-pool/target/idl/privacy_pool.json`
|
|
91
|
-
- Constructs an Anchor `Program` with a provider from `ANCHOR_PROVIDER_URL` / `ANCHOR_WALLET`
|
|
92
|
-
- Runs end-to-end flow:
|
|
93
|
-
1. `initializePool` (configures denoms + fee)
|
|
94
|
-
2. `createNoteAndDeposit` (creates note, commits it, calls on-chain `depositFixed` and updates the Merkle root)
|
|
95
|
-
3. `addRelayer`
|
|
96
|
-
4. `withdrawViaRelayer` (using a Merkle root that actually contains the note + a demo nullifier, empty proof)
|
|
97
|
-
5. Asserts vault TVL decreased, recipient gained funds, relayer received fee.
|
|
880
|
+
### 4.1 PDA Helpers
|
|
98
881
|
|
|
99
|
-
|
|
882
|
+
```ts
|
|
883
|
+
import {
|
|
884
|
+
getPoolPdas,
|
|
885
|
+
getNoteTreePda,
|
|
886
|
+
getGlobalConfigPda,
|
|
887
|
+
getNullifierMarkerPda,
|
|
888
|
+
} from "@veilo/sdk-core";
|
|
889
|
+
import { PublicKey } from "@solana/web3.js";
|
|
100
890
|
|
|
101
|
-
|
|
891
|
+
const programId = new PublicKey(
|
|
892
|
+
"YourProgram1111111111111111111111111111111111",
|
|
893
|
+
);
|
|
894
|
+
const mintAddress = new PublicKey(
|
|
895
|
+
"So11111111111111111111111111111111111111112",
|
|
896
|
+
); // Native SOL
|
|
102
897
|
|
|
103
|
-
|
|
898
|
+
// Get pool PDAs
|
|
899
|
+
const { config, vault, nullifiers } = getPoolPdas(programId, mintAddress);
|
|
104
900
|
|
|
105
|
-
|
|
901
|
+
// Get note tree PDA for tree ID 0
|
|
902
|
+
const noteTree = getNoteTreePda(programId, mintAddress, 0);
|
|
106
903
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
import { PublicKey } from "@solana/web3.js";
|
|
904
|
+
// Get global config
|
|
905
|
+
const globalConfig = getGlobalConfigPda(programId);
|
|
110
906
|
|
|
111
|
-
|
|
112
|
-
const
|
|
907
|
+
// Get nullifier marker PDA
|
|
908
|
+
const nullifier = new Uint8Array(32);
|
|
909
|
+
const marker = getNullifierMarkerPda(programId, mintAddress, 0, nullifier);
|
|
113
910
|
```
|
|
114
911
|
|
|
115
|
-
|
|
912
|
+
**PDA seeds (v3):**
|
|
116
913
|
|
|
117
|
-
- `["privacy_config_v3"]`
|
|
118
|
-
- `["privacy_vault_v3"]`
|
|
119
|
-
- `["privacy_note_tree_v3"]`
|
|
120
|
-
- `["privacy_nullifiers_v3"]`
|
|
914
|
+
- Config: `["privacy_config_v3", mint_address]`
|
|
915
|
+
- Vault: `["privacy_vault_v3", mint_address]`
|
|
916
|
+
- Note Tree: `["privacy_note_tree_v3", mint_address, tree_id]`
|
|
917
|
+
- Nullifiers: `["privacy_nullifiers_v3", mint_address]`
|
|
918
|
+
- Nullifier Marker: `["privacy_nullifier_v3", mint_address, tree_id, nullifier]`
|
|
919
|
+
- Global Config: `["global_config_v1"]`
|
|
121
920
|
|
|
122
921
|
---
|
|
123
922
|
|
|
124
|
-
### 4.2 Pool
|
|
923
|
+
### 4.2 Pool Initialization
|
|
125
924
|
|
|
126
925
|
```ts
|
|
127
926
|
import * as anchor from "@coral-xyz/anchor";
|
|
128
|
-
import {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
927
|
+
import {
|
|
928
|
+
initializeGlobalConfig,
|
|
929
|
+
initializePool,
|
|
930
|
+
updatePoolConfig,
|
|
931
|
+
addMerkleTree,
|
|
932
|
+
getPoolConfig,
|
|
933
|
+
} from "@veilo/sdk-core";
|
|
934
|
+
import { NATIVE_SOL_MINT, sol } from "@veilo/sdk-core/config";
|
|
935
|
+
|
|
936
|
+
// Initialize global config (once per program)
|
|
937
|
+
await initializeGlobalConfig({
|
|
938
|
+
program,
|
|
939
|
+
admin: adminKeypair,
|
|
940
|
+
});
|
|
141
941
|
|
|
142
|
-
|
|
942
|
+
// Initialize a pool for native SOL
|
|
943
|
+
await initializePool({
|
|
944
|
+
program,
|
|
945
|
+
payer: adminKeypair,
|
|
946
|
+
admin: adminKeypair,
|
|
947
|
+
mintAddress: NATIVE_SOL_MINT,
|
|
948
|
+
minDepositAmount: sol(0.1), // 0.1 SOL
|
|
949
|
+
maxDepositAmount: sol(100), // 100 SOL
|
|
950
|
+
minWithdrawAmount: sol(0.1),
|
|
951
|
+
maxWithdrawAmount: sol(100),
|
|
952
|
+
feeBps: 50, // 0.5%
|
|
953
|
+
feeErrorMarginBps: 10, // 0.1% margin
|
|
954
|
+
minWithdrawalFee: 1_000_000n, // 0.001 SOL minimum
|
|
955
|
+
});
|
|
143
956
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
957
|
+
// Add first Merkle tree (tree ID 0)
|
|
958
|
+
await addMerkleTree({
|
|
959
|
+
program,
|
|
960
|
+
payer: adminKeypair,
|
|
961
|
+
mintAddress: NATIVE_SOL_MINT,
|
|
962
|
+
treeId: 0,
|
|
963
|
+
});
|
|
964
|
+
|
|
965
|
+
// Update pool configuration
|
|
966
|
+
await updatePoolConfig({
|
|
967
|
+
program,
|
|
968
|
+
admin: adminKeypair,
|
|
969
|
+
mintAddress: NATIVE_SOL_MINT,
|
|
970
|
+
feeBps: 30, // Change to 0.3%
|
|
971
|
+
});
|
|
972
|
+
|
|
973
|
+
// Read pool configuration
|
|
974
|
+
const poolConfig = await getPoolConfig(program, NATIVE_SOL_MINT);
|
|
975
|
+
console.log("TVL:", poolConfig.totalTvl);
|
|
976
|
+
console.log("Fee:", poolConfig.feeBps, "bps");
|
|
977
|
+
console.log("Num Trees:", poolConfig.numTrees);
|
|
978
|
+
```
|
|
148
979
|
|
|
149
980
|
---
|
|
150
981
|
|
|
151
|
-
### 4.3
|
|
982
|
+
### 4.3 UTXO Management
|
|
152
983
|
|
|
153
|
-
|
|
984
|
+
The SDK uses a UTXO (Unspent Transaction Output) model with Poseidon commitments:
|
|
154
985
|
|
|
155
986
|
```ts
|
|
156
987
|
import {
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
988
|
+
generateKeypair,
|
|
989
|
+
keypairFromPrivateKey,
|
|
990
|
+
createUTXO,
|
|
991
|
+
createOwnedUTXO,
|
|
992
|
+
createOwnedZeroUTXO,
|
|
993
|
+
deriveNullifier,
|
|
994
|
+
type Keypair,
|
|
995
|
+
type UTXO,
|
|
996
|
+
type SerializedUTXO,
|
|
997
|
+
type InputUTXO,
|
|
998
|
+
} from "@veilo/sdk-core";
|
|
999
|
+
import { NATIVE_SOL_MINT } from "@veilo/sdk-core/config";
|
|
1000
|
+
import { pubkeyToField } from "@veilo/sdk-core";
|
|
1001
|
+
|
|
1002
|
+
// Generate a random keypair
|
|
1003
|
+
const keypair: Keypair = generateKeypair();
|
|
1004
|
+
// keypair = { privateKey: bigint, publicKey: bigint }
|
|
1005
|
+
|
|
1006
|
+
// Restore keypair from private key
|
|
1007
|
+
const restored = keypairFromPrivateKey(privateKeyBigInt);
|
|
1008
|
+
|
|
1009
|
+
// Create an owned UTXO (includes private key)
|
|
1010
|
+
const ownedUtxo: SerializedUTXO = createOwnedUTXO({
|
|
1011
|
+
amount: 1_000_000_000n,
|
|
1012
|
+
privateKey: keypair.privateKey,
|
|
1013
|
+
mintAddress: NATIVE_SOL_MINT,
|
|
170
1014
|
});
|
|
171
1015
|
|
|
172
|
-
//
|
|
173
|
-
const
|
|
1016
|
+
// Create a zero UTXO (for unused inputs/outputs)
|
|
1017
|
+
const zeroUtxo = createOwnedZeroUTXO(pubkeyToField(NATIVE_SOL_MINT), keypair);
|
|
1018
|
+
|
|
1019
|
+
// Derive nullifier for spending
|
|
1020
|
+
const nullifier = deriveNullifier(
|
|
1021
|
+
ownedUtxo.privateKey,
|
|
1022
|
+
ownedUtxo.commitment,
|
|
1023
|
+
0, // pathIndex in Merkle tree
|
|
1024
|
+
0, // treeId
|
|
1025
|
+
);
|
|
1026
|
+
```
|
|
174
1027
|
|
|
175
|
-
|
|
176
|
-
const commitment = commitNote(note);
|
|
1028
|
+
**UTXO commitment formula:**
|
|
177
1029
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
value: 1_000_000n,
|
|
181
|
-
owner,
|
|
182
|
-
});
|
|
183
|
-
// full.commitment is 32 bytes
|
|
1030
|
+
```
|
|
1031
|
+
commitment = Poseidon(amount, pubkey, blinding, mintAddress)
|
|
184
1032
|
```
|
|
185
1033
|
|
|
186
|
-
|
|
1034
|
+
**Nullifier formula:**
|
|
187
1035
|
|
|
188
|
-
```text
|
|
189
|
-
value (u64 LE, 8 bytes)
|
|
190
|
-
|| owner pubkey (32 bytes)
|
|
191
|
-
|| rho (32 bytes random)
|
|
192
|
-
|| r (32 bytes random)
|
|
193
1036
|
```
|
|
1037
|
+
nullifier = Poseidon(privateKey, commitment, pathIndex, treeId)
|
|
1038
|
+
```
|
|
1039
|
+
|
|
1040
|
+
---
|
|
194
1041
|
|
|
195
|
-
|
|
1042
|
+
### 4.4 Merkle Tree Operations
|
|
196
1043
|
|
|
197
1044
|
```ts
|
|
198
|
-
|
|
199
|
-
```
|
|
1045
|
+
import { MerkleTree } from "@veilo/sdk-core";
|
|
200
1046
|
|
|
201
|
-
|
|
1047
|
+
// Create a new Merkle tree (default depth: 20)
|
|
1048
|
+
const tree = new MerkleTree();
|
|
202
1049
|
|
|
203
|
-
|
|
1050
|
+
// Insert commitments
|
|
1051
|
+
const index1 = tree.insert(ownedUtxo1.commitment);
|
|
1052
|
+
const index2 = tree.insert(ownedUtxo2.commitment);
|
|
204
1053
|
|
|
205
|
-
|
|
1054
|
+
// Get current root
|
|
1055
|
+
const root = tree.root();
|
|
206
1056
|
|
|
207
|
-
|
|
1057
|
+
// Get Merkle path for proof generation
|
|
1058
|
+
const path = tree.path(index1);
|
|
1059
|
+
// path = { pathElements: Uint8Array[], pathIndices: number[] }
|
|
208
1060
|
|
|
209
|
-
|
|
1061
|
+
// Get number of leaves
|
|
1062
|
+
const numLeaves = tree.totalLeaves;
|
|
210
1063
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
merkleLeafFromCommitment,
|
|
214
|
-
merkleHashPair,
|
|
215
|
-
merkleRootFromLeaves,
|
|
216
|
-
MerkleTree,
|
|
217
|
-
} from "@zkprivacysol/sdk-core/merkle";
|
|
218
|
-
|
|
219
|
-
// Stateless helpers
|
|
220
|
-
const leaf = merkleLeafFromCommitment(commitment);
|
|
221
|
-
const parent = merkleHashPair(left, right);
|
|
222
|
-
const root = merkleRootFromLeaves([leaf1, leaf2, leaf3]);
|
|
223
|
-
|
|
224
|
-
// Simple incremental tree (toy)
|
|
225
|
-
const tree = new MerkleTree();
|
|
226
|
-
const { index, root: newRoot } = tree.insert(commitment);
|
|
227
|
-
const path = tree.getPath(index); // Merkle path for proofs
|
|
1064
|
+
// Custom tree depth
|
|
1065
|
+
const deepTree = new MerkleTree(25); // 25 levels
|
|
228
1066
|
```
|
|
229
1067
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
- Uses SHA-256 under the hood, returning `Uint8Array` of length 32.
|
|
233
|
-
- Pads with a “zero node” derived from hashing the all-zero leaf repeatedly up the tree.
|
|
234
|
-
- This is intentionally “toy” to keep the SDK usable while the real circuit/Merkle design is still in flux.
|
|
235
|
-
- On-chain, the program only stores the **latest Merkle root** in the `NoteTree` account (v3 layout exposes a `currentRoot` field).
|
|
1068
|
+
The Merkle tree uses Poseidon hash for all internal nodes.
|
|
236
1069
|
|
|
237
1070
|
---
|
|
238
1071
|
|
|
239
|
-
### 4.5
|
|
1072
|
+
### 4.5 Transaction Operations
|
|
240
1073
|
|
|
241
|
-
|
|
1074
|
+
The SDK supports three types of transactions:
|
|
1075
|
+
|
|
1076
|
+
#### Deposits (publicAmount > 0)
|
|
242
1077
|
|
|
243
1078
|
```ts
|
|
244
|
-
import
|
|
245
|
-
|
|
1079
|
+
import { deposit } from "@veilo/sdk-core";
|
|
1080
|
+
|
|
1081
|
+
// Create output UTXO
|
|
1082
|
+
const outputUtxo = createOwnedUTXO({
|
|
1083
|
+
amount: 1_000_000_000n, // 1 SOL
|
|
1084
|
+
mintAddress: pubkeyToField(NATIVE_SOL_MINT),
|
|
1085
|
+
keypair: utxoKeypair,
|
|
1086
|
+
});
|
|
246
1087
|
|
|
247
|
-
|
|
248
|
-
const
|
|
1088
|
+
// Zero inputs for deposit
|
|
1089
|
+
const input1 = createOwnedZeroUTXO(pubkeyToField(NATIVE_SOL_MINT), utxoKeypair);
|
|
1090
|
+
const input2 = createOwnedZeroUTXO(pubkeyToField(NATIVE_SOL_MINT), utxoKeypair);
|
|
1091
|
+
const output2 = createOwnedZeroUTXO(
|
|
1092
|
+
pubkeyToField(NATIVE_SOL_MINT),
|
|
1093
|
+
utxoKeypair,
|
|
1094
|
+
);
|
|
249
1095
|
|
|
250
|
-
await
|
|
1096
|
+
await deposit({
|
|
251
1097
|
program,
|
|
252
|
-
depositor:
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
1098
|
+
depositor: depositorKeypair,
|
|
1099
|
+
mintAddress: NATIVE_SOL_MINT,
|
|
1100
|
+
inputTreeId: 0,
|
|
1101
|
+
outputTreeId: 0,
|
|
1102
|
+
root: tree.root(),
|
|
1103
|
+
publicAmount: 1_000_000_000n, // Positive = deposit
|
|
1104
|
+
inputs: [input1, input2],
|
|
1105
|
+
outputs: [outputUtxo, output2],
|
|
1106
|
+
recipient: depositorKeypair.publicKey,
|
|
1107
|
+
fee: 0n,
|
|
1108
|
+
refund: 0n,
|
|
1109
|
+
proof: mockProof,
|
|
256
1110
|
});
|
|
1111
|
+
|
|
1112
|
+
// Insert outputs into tree
|
|
1113
|
+
tree.insert(outputUtxo.commitment);
|
|
1114
|
+
tree.insert(output2.commitment);
|
|
257
1115
|
```
|
|
258
1116
|
|
|
259
|
-
|
|
1117
|
+
#### Withdrawals (publicAmount < 0)
|
|
260
1118
|
|
|
261
1119
|
```ts
|
|
262
|
-
import
|
|
263
|
-
|
|
264
|
-
|
|
1120
|
+
import { withdraw } from "@veilo/sdk-core";
|
|
1121
|
+
|
|
1122
|
+
// Prepare input with Merkle path
|
|
1123
|
+
const input1: InputUTXO = {
|
|
1124
|
+
...ownedUtxo1,
|
|
1125
|
+
pathIndex: 0,
|
|
1126
|
+
pathElements: tree.path(0).pathElements,
|
|
1127
|
+
};
|
|
1128
|
+
|
|
1129
|
+
const zeroInput2: InputUTXO = {
|
|
1130
|
+
...createOwnedZeroUTXO(pubkeyToField(NATIVE_SOL_MINT), utxoKeypair),
|
|
1131
|
+
pathIndex: 0,
|
|
1132
|
+
pathElements: tree.path(0).pathElements,
|
|
1133
|
+
};
|
|
1134
|
+
|
|
1135
|
+
// Zero outputs
|
|
1136
|
+
const output1 = createOwnedZeroUTXO(
|
|
1137
|
+
pubkeyToField(NATIVE_SOL_MINT),
|
|
1138
|
+
utxoKeypair,
|
|
1139
|
+
);
|
|
1140
|
+
const output2 = createOwnedZeroUTXO(
|
|
1141
|
+
pubkeyToField(NATIVE_SOL_MINT),
|
|
1142
|
+
utxoKeypair,
|
|
1143
|
+
);
|
|
1144
|
+
|
|
1145
|
+
await withdraw({
|
|
1146
|
+
program,
|
|
1147
|
+
relayer: relayerKeypair,
|
|
1148
|
+
mintAddress: NATIVE_SOL_MINT,
|
|
1149
|
+
inputTreeId: 0,
|
|
1150
|
+
outputTreeId: 0,
|
|
1151
|
+
root: tree.root(),
|
|
1152
|
+
publicAmount: -900_000_000n, // Negative = withdrawal (0.9 SOL)
|
|
1153
|
+
inputs: [input1, zeroInput2],
|
|
1154
|
+
outputs: [output1, output2],
|
|
1155
|
+
recipient: recipientKeypair.publicKey,
|
|
1156
|
+
fee: 100_000_000n, // 0.1 SOL to relayer
|
|
1157
|
+
refund: 0n,
|
|
1158
|
+
proof,
|
|
1159
|
+
});
|
|
1160
|
+
```
|
|
265
1161
|
|
|
266
|
-
|
|
267
|
-
const wallet = provider.wallet as anchor.Wallet;
|
|
1162
|
+
#### Private Transfers (publicAmount = 0)
|
|
268
1163
|
|
|
269
|
-
|
|
1164
|
+
```ts
|
|
1165
|
+
import {
|
|
1166
|
+
privateTransfer,
|
|
1167
|
+
generateKeypair,
|
|
1168
|
+
createOwnedUTXO,
|
|
1169
|
+
} from "@veilo/sdk-core";
|
|
1170
|
+
|
|
1171
|
+
// Create new outputs for recipient
|
|
1172
|
+
const recipientKeypair = generateKeypair();
|
|
1173
|
+
const output1 = createOwnedUTXO({
|
|
1174
|
+
amount: 1_000_000_000n,
|
|
1175
|
+
privateKey: recipientKeypair.privateKey,
|
|
1176
|
+
mintAddress: NATIVE_SOL_MINT,
|
|
1177
|
+
});
|
|
270
1178
|
|
|
271
|
-
|
|
1179
|
+
await privateTransfer({
|
|
272
1180
|
program,
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
1181
|
+
relayer: relayerKeypair,
|
|
1182
|
+
mintAddress: NATIVE_SOL_MINT,
|
|
1183
|
+
inputTreeId: 0,
|
|
1184
|
+
outputTreeId: 0,
|
|
1185
|
+
root: tree.root(),
|
|
1186
|
+
publicAmount: 0n, // Zero = private transfer
|
|
1187
|
+
inputs: [input1, zeroInput2],
|
|
1188
|
+
outputs: [output1, output2],
|
|
1189
|
+
recipient: relayerKeypair.publicKey,
|
|
1190
|
+
fee: 0n,
|
|
1191
|
+
refund: 0n,
|
|
1192
|
+
proof,
|
|
277
1193
|
});
|
|
278
1194
|
|
|
279
|
-
//
|
|
1195
|
+
// Insert new outputs
|
|
1196
|
+
tree.insert(output1.commitment);
|
|
1197
|
+
tree.insert(output2.commitment);
|
|
280
1198
|
```
|
|
281
1199
|
|
|
282
|
-
|
|
1200
|
+
#### Low-Level Transaction Function
|
|
1201
|
+
|
|
1202
|
+
For advanced use cases, use the unified `transact` function directly:
|
|
283
1203
|
|
|
284
1204
|
```ts
|
|
285
|
-
import {
|
|
286
|
-
import { MerkleTree } from "@zkprivacysol/sdk-core/merkle";
|
|
1205
|
+
import { transact } from "@veilo/sdk-core";
|
|
287
1206
|
|
|
288
|
-
|
|
1207
|
+
await transact({
|
|
1208
|
+
program,
|
|
1209
|
+
relayer: relayerKeypair,
|
|
1210
|
+
mintAddress: NATIVE_SOL_MINT,
|
|
1211
|
+
inputTreeId: 0,
|
|
1212
|
+
outputTreeId: 0,
|
|
1213
|
+
root: tree.root(),
|
|
1214
|
+
publicAmount: 0n,
|
|
1215
|
+
inputNullifiers: [nullifier1, nullifier2],
|
|
1216
|
+
outputCommitments: [output1.commitment, output2.commitment],
|
|
1217
|
+
extData: {
|
|
1218
|
+
recipient: recipientPubkey,
|
|
1219
|
+
relayer: relayerPubkey,
|
|
1220
|
+
fee: 0n,
|
|
1221
|
+
refund: 0n,
|
|
1222
|
+
claimant: claimantPubkey,
|
|
1223
|
+
},
|
|
1224
|
+
proof,
|
|
1225
|
+
});
|
|
1226
|
+
```
|
|
289
1227
|
|
|
290
|
-
|
|
291
|
-
await createNoteDepositWithMerkle({
|
|
292
|
-
program,
|
|
293
|
-
depositor: wallet,
|
|
294
|
-
denomIndex: 0,
|
|
295
|
-
valueLamports: sol(1),
|
|
296
|
-
tree,
|
|
297
|
-
});
|
|
1228
|
+
---
|
|
298
1229
|
|
|
299
|
-
|
|
300
|
-
// `merklePath` can be used as witness for the zk circuit
|
|
301
|
-
```
|
|
1230
|
+
### 4.6 Proof Generation
|
|
302
1231
|
|
|
303
|
-
|
|
1232
|
+
The SDK provides utilities for preparing circuit inputs:
|
|
304
1233
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
1234
|
+
```ts
|
|
1235
|
+
import {
|
|
1236
|
+
prepareTransactionInputs,
|
|
1237
|
+
formatInputsForSnarkjs,
|
|
1238
|
+
computeExtDataHash,
|
|
1239
|
+
encodeSnarkjsProofToTransactionProof,
|
|
1240
|
+
packProofToBytes,
|
|
1241
|
+
type ExtData,
|
|
1242
|
+
type TransactionCircuitInputs,
|
|
1243
|
+
} from "@veilo/sdk-core";
|
|
1244
|
+
|
|
1245
|
+
// Prepare inputs for the circuit
|
|
1246
|
+
const circuitInputs: TransactionCircuitInputs = prepareTransactionInputs({
|
|
1247
|
+
root: tree.root(),
|
|
1248
|
+
publicAmount: 1_000_000_000n,
|
|
1249
|
+
extData: {
|
|
1250
|
+
recipient: recipientPubkey,
|
|
1251
|
+
relayer: relayerPubkey,
|
|
1252
|
+
fee: 0n,
|
|
1253
|
+
refund: 0n,
|
|
1254
|
+
claimant: claimantPubkey,
|
|
1255
|
+
},
|
|
1256
|
+
mintAddress: NATIVE_SOL_MINT,
|
|
1257
|
+
inputs: [input1, input2],
|
|
1258
|
+
outputs: [output1, output2],
|
|
1259
|
+
inputTreeId: 0,
|
|
1260
|
+
outputTreeId: 0,
|
|
1261
|
+
});
|
|
308
1262
|
|
|
309
|
-
|
|
1263
|
+
// Format for snarkjs (converts Uint8Array to string representations)
|
|
1264
|
+
const snarkjsInputs = formatInputsForSnarkjs(circuitInputs);
|
|
310
1265
|
|
|
311
|
-
|
|
1266
|
+
// Use with snarkjs to generate proof
|
|
1267
|
+
// const { proof, publicSignals } = await snarkjs.groth16.fullProve(
|
|
1268
|
+
// snarkjsInputs,
|
|
1269
|
+
// wasmPath,
|
|
1270
|
+
// zkeyPath
|
|
1271
|
+
// );
|
|
312
1272
|
|
|
313
|
-
|
|
1273
|
+
// Convert snarkjs proof to on-chain format
|
|
1274
|
+
// const transactionProof = encodeSnarkjsProofToTransactionProof(proof);
|
|
1275
|
+
```
|
|
314
1276
|
|
|
315
|
-
|
|
316
|
-
import * as anchor from "@coral-xyz/anchor";
|
|
317
|
-
import { addRelayer } from "@zkprivacysol/sdk-core";
|
|
318
|
-
import { Keypair } from "@solana/web3.js";
|
|
1277
|
+
---
|
|
319
1278
|
|
|
320
|
-
|
|
321
|
-
const wallet = provider.wallet as anchor.Wallet;
|
|
1279
|
+
### 4.7 Relayer Management
|
|
322
1280
|
|
|
323
|
-
|
|
1281
|
+
```ts
|
|
1282
|
+
import { addRelayer, setPaused } from "@veilo/sdk-core";
|
|
324
1283
|
|
|
1284
|
+
// Add a relayer
|
|
325
1285
|
await addRelayer({
|
|
326
1286
|
program,
|
|
327
|
-
admin:
|
|
328
|
-
|
|
1287
|
+
admin: adminKeypair,
|
|
1288
|
+
mintAddress: NATIVE_SOL_MINT,
|
|
1289
|
+
newRelayer: relayerPubkey,
|
|
1290
|
+
});
|
|
1291
|
+
|
|
1292
|
+
// Pause/unpause the pool
|
|
1293
|
+
await setPaused({
|
|
1294
|
+
program,
|
|
1295
|
+
admin: adminKeypair,
|
|
1296
|
+
mintAddress: NATIVE_SOL_MINT,
|
|
1297
|
+
paused: true,
|
|
329
1298
|
});
|
|
330
1299
|
```
|
|
331
1300
|
|
|
332
|
-
|
|
1301
|
+
---
|
|
1302
|
+
|
|
1303
|
+
### 4.8 Poseidon Utilities
|
|
333
1304
|
|
|
334
1305
|
```ts
|
|
335
|
-
import {
|
|
336
|
-
|
|
1306
|
+
import {
|
|
1307
|
+
initPoseidon,
|
|
1308
|
+
poseidon1,
|
|
1309
|
+
poseidon2,
|
|
1310
|
+
poseidon3,
|
|
1311
|
+
poseidon4,
|
|
1312
|
+
pubkeyToField,
|
|
1313
|
+
} from "@veilo/sdk-core";
|
|
1314
|
+
|
|
1315
|
+
// Initialize Poseidon (required once before using hash functions)
|
|
1316
|
+
await initPoseidon();
|
|
1317
|
+
|
|
1318
|
+
// Hash functions
|
|
1319
|
+
const hash1 = poseidon1(12345n);
|
|
1320
|
+
const hash2 = poseidon2(12345n, 67890n);
|
|
1321
|
+
const hash3 = poseidon3(12345n, 67890n, 11111n);
|
|
1322
|
+
const hash4 = poseidon4(12345n, 67890n, 11111n, 22222n);
|
|
1323
|
+
|
|
1324
|
+
// Convert Solana PublicKey to field element
|
|
1325
|
+
const fieldElement = pubkeyToField(NATIVE_SOL_MINT);
|
|
1326
|
+
```
|
|
337
1327
|
|
|
338
|
-
|
|
339
|
-
const recipient = Keypair.generate();
|
|
1328
|
+
---
|
|
340
1329
|
|
|
341
|
-
|
|
342
|
-
const nullifier = new Uint8Array(32).fill(3); // demo only
|
|
1330
|
+
## 5. Architecture
|
|
343
1331
|
|
|
344
|
-
|
|
345
|
-
const proofBytes = new Uint8Array([]); // currently ignored by on-chain program
|
|
1332
|
+
### Transaction Model
|
|
346
1333
|
|
|
347
|
-
|
|
348
|
-
program,
|
|
349
|
-
relayer,
|
|
350
|
-
recipient: recipient.publicKey,
|
|
351
|
-
denomIndex: 0,
|
|
352
|
-
root,
|
|
353
|
-
nullifier,
|
|
354
|
-
proof: proofBytes,
|
|
355
|
-
});
|
|
356
|
-
```
|
|
1334
|
+
Veilo uses a UTXO-based privacy model inspired by Zcash and Tornado Cash Nova:
|
|
357
1335
|
|
|
358
|
-
|
|
1336
|
+
1. **Inputs**: 2 UTXOs (can be zero for deposits)
|
|
1337
|
+
2. **Outputs**: 2 UTXOs (can be zero for withdrawals)
|
|
1338
|
+
3. **Public Amount**: Net change (positive = deposit, negative = withdrawal, zero = private transfer)
|
|
359
1339
|
|
|
360
|
-
|
|
361
|
-
- `merklePath`,
|
|
362
|
-
- `feeBps`,
|
|
363
|
-
- a `builder: ProofBuilder` callback
|
|
1340
|
+
Each transaction:
|
|
364
1341
|
|
|
365
|
-
|
|
1342
|
+
- Consumes 2 input UTXOs (proven via Merkle paths)
|
|
1343
|
+
- Creates 2 output UTXOs (commitments added to tree)
|
|
1344
|
+
- Generates 2 nullifiers (prevents double-spending)
|
|
1345
|
+
- Optionally transfers funds in/out of the pool
|
|
366
1346
|
|
|
367
|
-
|
|
1347
|
+
### Privacy Guarantees
|
|
368
1348
|
|
|
369
|
-
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
- The front-end sends a withdraw request to that service:
|
|
374
|
-
- `root`, `nullifier`, `denomIndex`, `recipient`, plus any private witness data.
|
|
375
|
-
- The relayer:
|
|
376
|
-
1. Builds & verifies the zk proof off-chain.
|
|
377
|
-
2. Packs it into bytes (e.g. via a `packProofToBytes` helper).
|
|
378
|
-
3. Calls the on-chain `withdraw` via Anchor, using the same program/PDAs as the SDK.
|
|
1349
|
+
- **Commitment hiding**: Amount, owner, and blinding factor are hidden via Poseidon hash
|
|
1350
|
+
- **Nullifier uniqueness**: Each UTXO can only be spent once
|
|
1351
|
+
- **Unlinkability**: No public link between inputs and outputs
|
|
1352
|
+
- **Zero-knowledge proofs**: Transactions proven valid without revealing private data
|
|
379
1353
|
|
|
380
|
-
|
|
1354
|
+
### Multi-Tree Support
|
|
381
1355
|
|
|
382
|
-
|
|
1356
|
+
The protocol supports multiple concurrent Merkle trees per pool:
|
|
383
1357
|
|
|
384
|
-
|
|
1358
|
+
- Improves scalability by reducing tree depth
|
|
1359
|
+
- Allows parallel insertions
|
|
1360
|
+
- Each tree has independent state
|
|
385
1361
|
|
|
386
|
-
|
|
1362
|
+
### Constants
|
|
387
1363
|
|
|
388
|
-
|
|
1364
|
+
```ts
|
|
1365
|
+
import {
|
|
1366
|
+
NATIVE_SOL_MINT, // PublicKey.default (all zeros) for native SOL
|
|
1367
|
+
MERKLE_TREE_DEPTH, // 22 levels
|
|
1368
|
+
ROOT_HISTORY_SIZE, // 256 historical roots
|
|
1369
|
+
DEFAULT_FEE_BPS, // 50 (0.5%)
|
|
1370
|
+
sol, // Helper: sol(1) = 1_000_000_000n lamports
|
|
1371
|
+
} from "@veilo/sdk-core/config";
|
|
1372
|
+
|
|
1373
|
+
import { BN254_FR_MODULUS } from "@veilo/sdk-core";
|
|
1374
|
+
```
|
|
389
1375
|
|
|
390
|
-
|
|
391
|
-
solana-test-validator
|
|
392
|
-
```
|
|
1376
|
+
### Type Exports
|
|
393
1377
|
|
|
394
|
-
|
|
1378
|
+
The SDK exports the following types for TypeScript users:
|
|
395
1379
|
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
1380
|
+
```ts
|
|
1381
|
+
import type {
|
|
1382
|
+
// UTXO types
|
|
1383
|
+
Keypair,
|
|
1384
|
+
UTXO,
|
|
1385
|
+
SerializedUTXO,
|
|
1386
|
+
InputUTXO,
|
|
1387
|
+
|
|
1388
|
+
// Proof types
|
|
1389
|
+
ExtData,
|
|
1390
|
+
TransactionCircuitInputs,
|
|
1391
|
+
TransactionProofStruct,
|
|
1392
|
+
RawProof,
|
|
1393
|
+
TransactionProofBuilder,
|
|
1394
|
+
|
|
1395
|
+
// Merkle types
|
|
1396
|
+
MerklePath,
|
|
1397
|
+
CircuitMerklePath,
|
|
1398
|
+
|
|
1399
|
+
// Config types
|
|
1400
|
+
PoolInitConfig,
|
|
1401
|
+
PrivacyConfigAccount,
|
|
1402
|
+
GlobalConfigAccount,
|
|
1403
|
+
} from "@veilo/sdk-core";
|
|
1404
|
+
```
|
|
401
1405
|
|
|
402
|
-
|
|
1406
|
+
### SPL Token Support
|
|
403
1407
|
|
|
404
|
-
|
|
405
|
-
solana config set --url http://127.0.0.1:8899
|
|
406
|
-
solana-keygen new --outfile ~/.config/solana/id.json
|
|
407
|
-
solana airdrop 10
|
|
408
|
-
```
|
|
1408
|
+
The SDK supports both native SOL and SPL tokens:
|
|
409
1409
|
|
|
410
|
-
|
|
1410
|
+
```ts
|
|
1411
|
+
import { PublicKey } from "@solana/web3.js";
|
|
1412
|
+
|
|
1413
|
+
// For native SOL
|
|
1414
|
+
const solMint = NATIVE_SOL_MINT; // PublicKey.default
|
|
1415
|
+
|
|
1416
|
+
// For SPL tokens
|
|
1417
|
+
const usdcMint = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
|
|
1418
|
+
|
|
1419
|
+
// Initialize pool for SPL token
|
|
1420
|
+
await initializePool({
|
|
1421
|
+
program,
|
|
1422
|
+
payer: adminKeypair,
|
|
1423
|
+
admin: adminKeypair,
|
|
1424
|
+
mintAddress: usdcMint, // Use SPL token mint
|
|
1425
|
+
minDepositAmount: 1_000_000n, // 1 USDC (6 decimals)
|
|
1426
|
+
// ... other params
|
|
1427
|
+
});
|
|
1428
|
+
```
|
|
1429
|
+
|
|
1430
|
+
When using SPL tokens, the SDK automatically handles associated token accounts.
|
|
1431
|
+
|
|
1432
|
+
---
|
|
411
1433
|
|
|
412
|
-
|
|
413
|
-
export ANCHOR_PROVIDER_URL=http://127.0.0.1:8899
|
|
414
|
-
export ANCHOR_WALLET=$HOME/.config/solana/id.json
|
|
415
|
-
```
|
|
1434
|
+
## 6. Development
|
|
416
1435
|
|
|
417
|
-
|
|
1436
|
+
### Environment Variables
|
|
418
1437
|
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
1438
|
+
```bash
|
|
1439
|
+
export ANCHOR_PROVIDER_URL=http://127.0.0.1:8899
|
|
1440
|
+
export ANCHOR_WALLET=$HOME/.config/solana/id.json
|
|
1441
|
+
```
|
|
423
1442
|
|
|
424
1443
|
---
|
|
425
1444
|
|
|
426
|
-
##
|
|
1445
|
+
## 7. Limitations
|
|
1446
|
+
|
|
1447
|
+
- **Off-chain tree management**: Merkle trees must be maintained by relayers/clients
|
|
1448
|
+
- **Proof generation not included**: You must integrate your own circuit/prover
|
|
1449
|
+
- **Development status**: Active development, APIs may change
|
|
1450
|
+
|
|
1451
|
+
---
|
|
1452
|
+
|
|
1453
|
+
## 8. Resources
|
|
1454
|
+
|
|
1455
|
+
- **Repository**: https://github.com/VeiloSolana/veilo-sdk
|
|
1456
|
+
- **Circomlibjs**: https://github.com/iden3/circomlibjs
|
|
1457
|
+
- **Poseidon Hash**: https://www.poseidon-hash.info/
|
|
1458
|
+
|
|
1459
|
+
---
|
|
427
1460
|
|
|
428
|
-
|
|
429
|
-
- Toy implementation, primarily for demos/tests.
|
|
430
|
-
- No persisted tree; you’re expected to maintain state in your own service.
|
|
431
|
-
- **On-chain NoteTree only stores the latest root.**
|
|
432
|
-
- Historical roots/nullifiers must be mirrored off-chain.
|
|
433
|
-
- **Proofs are relayer-only.**
|
|
434
|
-
- SDK does **not** generate Groth16/Plonk proofs.
|
|
435
|
-
- On-chain program currently only sees `Vec<u8>` and does not verify it yet.
|
|
436
|
-
- **API is still evolving.**
|
|
437
|
-
- Types, exports, and function signatures may change as the circuit + relayer design solidifies.
|
|
1461
|
+
## License
|
|
438
1462
|
|
|
439
|
-
|
|
1463
|
+
ISC
|