@cloak.dev/sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,268 @@
1
+ # @cloak.dev/sdk
2
+
3
+ TypeScript SDK for the Cloak Protocol - Private transactions on Solana using zero-knowledge proofs.
4
+
5
+ ## Features
6
+
7
+ - 🔒 **Private Transfers**: Send SOL privately using zero-knowledge proofs
8
+ - 👥 **Multi-Recipient**: Support for 1-5 recipients in a single transaction
9
+ - 💱 **Token Swaps**: Swap SOL for SPL tokens privately (SOL → USDC, etc.)
10
+ - 🔐 **Type-Safe**: Full TypeScript support with comprehensive types
11
+ - 🌐 **Cross-Platform**: Works in browser (React, Next.js) and Node.js
12
+ - ⚡ **Simple API**: Easy-to-use high-level client with wallet adapter support
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ npm install @cloak.dev/sdk @solana/web3.js
18
+ # or
19
+ yarn add @cloak.dev/sdk @solana/web3.js
20
+ # or
21
+ pnpm add @cloak.dev/sdk @solana/web3.js
22
+ ```
23
+
24
+ **Note**: For swap functionality, you'll also need `@solana/spl-token`:
25
+ ```bash
26
+ npm install @solana/spl-token
27
+ ```
28
+
29
+ ## Quick Start
30
+
31
+ ### Risk Quote Configuration (Recommended)
32
+
33
+ - Default production flow: set `CLOAK_RELAY_URL` and let relay handle Range quoting.
34
+ - Keep `RANGE_API_KEY` only in relay/service backend, not in SDK clients.
35
+ - SDK auto-uses `${CLOAK_RELAY_URL}/range-quote` when `relayUrl` is provided.
36
+ - SDK-side `rangeApiKey` exists only as a server-side fallback (no-relay scenarios).
37
+
38
+ ### Maintained Examples
39
+
40
+ ```bash
41
+ npm run example:fast-send
42
+ npm run example:fast-usdc-send
43
+ npm run example:usdc-pool-transfer
44
+ npm run example:swap
45
+ npm run example:swap-recovery
46
+ npm run example:swap-usdc
47
+ npm run example:swap-brz
48
+ npm run example:transfer
49
+ npm run test:examples
50
+ ```
51
+
52
+ `example:transfer` runs deposit -> shield-to-shield transfer (`public_amount=0`) -> recipient withdraw verification and prints stable `FULL_SIG|transfer|deposit|...`, `FULL_SIG|transfer|tx|...`, and `COMMITMENT_INDICES|transfer|[...]` markers.
53
+
54
+ `example:fast-usdc-send` is the one-shot private send path for USDC recipients (deposit SOL, swap privately to USDC, deliver to recipient ATA).
55
+
56
+ `example:usdc-pool-transfer` is the mint-scoped USDC pool transfer path (User A deposits USDC into Cloak USDC pool, sends shielded USDC to User B, and User B spends the received shielded note).
57
+
58
+ `example:swap-usdc` is the canonical Nora swap evidence path (SOL -> USDC) and prints quote/route details, `FULL_SIG|swap-usdc|transact_swap|...`, `FULL_SIG|swap-usdc|swap_completed|...`, and `RECIPIENT_USDC_BALANCE|...` markers.
59
+
60
+ `example:swap-recovery` focuses on pending/timeout behavior. It submits a swap, tracks `swap_phase` + `slots_remaining` from relay `/status`, and can optionally call `close_timed_out` (`AUTO_CLOSE_TIMED_OUT=1`) once `can_recover=true`.
61
+
62
+ `example:swap-brz` first attempts BRZ (`FtgGSFADXBtroxq8VCausXRr2of47QBf5AS1NtZCu4GD`) and automatically falls back to USDC if BRZ routing is unavailable. It always emits `QUOTE_ROUTE|...`, `SWAP_OUTPUT_MINT|...`, and (when fallback happens) `BRZ_FALLBACK_TO|...`.
63
+
64
+ `test:examples` runs all maintained examples in `CLOAK_EXAMPLE_DRY_RUN=1` mode so CI/local checks validate script wiring without requiring funded wallets or live RPC execution.
65
+
66
+ ### Node.js (with Keypair)
67
+
68
+ ```typescript
69
+ import { CloakSDK } from "@cloak.dev/sdk";
70
+ import { Connection, Keypair, PublicKey } from "@solana/web3.js";
71
+
72
+ // Initialize connection and keypair
73
+ const connection = new Connection("https://api.devnet.solana.com");
74
+ const keypair = Keypair.fromSecretKey(/* your secret key */);
75
+
76
+ // Initialize SDK
77
+ const sdk = new CloakSDK({
78
+ keypairBytes: keypair.secretKey,
79
+ network: "devnet",
80
+ });
81
+
82
+ // Deposit SOL into the privacy pool
83
+ const depositResult = await sdk.deposit(connection, 100_000_000); // 0.1 SOL
84
+ console.log("Deposited! Leaf index:", depositResult.leafIndex);
85
+
86
+ // Withdraw to a recipient
87
+ const withdrawResult = await sdk.withdraw(
88
+ connection,
89
+ depositResult.note,
90
+ new PublicKey("RECIPIENT_ADDRESS"),
91
+ { withdrawAll: true }
92
+ );
93
+ console.log("Withdrawn! TX:", withdrawResult.signature);
94
+ ```
95
+
96
+ ### React/Next.js (with Wallet Adapter)
97
+
98
+ ```typescript
99
+ import { CloakSDK } from "@cloak.dev/sdk";
100
+ import { useWallet, useConnection } from "@solana/wallet-adapter-react";
101
+ import { PublicKey } from "@solana/web3.js";
102
+
103
+ function PrivateTransfer() {
104
+ const { publicKey, signTransaction, sendTransaction } = useWallet();
105
+ const { connection } = useConnection();
106
+
107
+ // Initialize SDK with wallet adapter
108
+ const sdk = useMemo(() => {
109
+ if (!publicKey) return null;
110
+ return new CloakSDK({
111
+ network: "devnet",
112
+ wallet: {
113
+ publicKey,
114
+ signTransaction: (tx) => signTransaction!(tx),
115
+ sendTransaction: (tx, conn, opts) => sendTransaction(tx, conn, opts),
116
+ },
117
+ });
118
+ }, [publicKey, signTransaction, sendTransaction]);
119
+
120
+ const handleDeposit = async () => {
121
+ const result = await sdk.deposit(connection, 100_000_000);
122
+ console.log("Deposited!", result.signature);
123
+ };
124
+
125
+ return <button onClick={handleDeposit}>Deposit 0.1 SOL</button>;
126
+ }
127
+ ```
128
+
129
+ ## Core Methods
130
+
131
+ ### Deposit
132
+
133
+ Deposit SOL into the privacy pool:
134
+
135
+ ```typescript
136
+ const result = await sdk.deposit(connection, 100_000_000); // 0.1 SOL
137
+ // Save the note securely - you need it to withdraw!
138
+ console.log(result.note);
139
+ ```
140
+
141
+ ### Withdraw
142
+
143
+ Withdraw to a single recipient:
144
+
145
+ ```typescript
146
+ const result = await sdk.withdraw(
147
+ connection,
148
+ note,
149
+ recipientPublicKey,
150
+ { withdrawAll: true }
151
+ );
152
+ ```
153
+
154
+ ### Send to Multiple Recipients
155
+
156
+ Send to up to 5 recipients:
157
+
158
+ ```typescript
159
+ const result = await sdk.send(connection, note, [
160
+ { recipient: addr1, amount: 50_000_000 },
161
+ { recipient: addr2, amount: 47_000_000 },
162
+ ]);
163
+ ```
164
+
165
+ ### Swap
166
+
167
+ Swap SOL for SPL tokens:
168
+
169
+ ```typescript
170
+ const result = await sdk.swap(connection, note, recipientPublicKey, {
171
+ outputMint: "TOKEN_MINT_ADDRESS",
172
+ minOutputAmount: 1000000,
173
+ });
174
+ ```
175
+
176
+ ## Fee Structure
177
+
178
+ - **Fixed Fee**: 0.005 SOL (5,000,000 lamports)
179
+ - **Variable Fee**: 0.3% of deposit amount
180
+
181
+ Use `getDistributableAmount()` to calculate the amount after fees:
182
+
183
+ ```typescript
184
+ import { getDistributableAmount } from "@cloak.dev/sdk";
185
+
186
+ const deposited = 100_000_000; // 0.1 SOL
187
+ const afterFees = getDistributableAmount(deposited); // ~94,700,000 lamports
188
+ ```
189
+
190
+ ## Notes
191
+
192
+ A **Cloak Note** is a cryptographic commitment representing a private amount of SOL:
193
+
194
+ ```typescript
195
+ interface CloakNote {
196
+ version: string;
197
+ amount: number; // Amount in lamports
198
+ commitment: string; // Commitment hash
199
+ sk_spend: string; // Spending key (keep secret!)
200
+ r: string; // Randomness
201
+ timestamp: number;
202
+ network: Network;
203
+ leafIndex?: number; // Set after deposit
204
+ depositSignature?: string;
205
+ }
206
+ ```
207
+
208
+ **⚠️ Important**: Save your notes securely! Without the note, you cannot withdraw your funds.
209
+
210
+ ## Compliance Chain Scanning
211
+
212
+ Cloak supports a viewing-key commitment flow for compliance and self-discovery:
213
+
214
+ - The viewing key itself is never written on-chain.
215
+ - Only `viewing_key_commitment = SHA256(viewing_key_public)` is stored on-chain.
216
+ - A scanner recomputes this commitment from `viewing_key_public` and matches program transactions.
217
+
218
+ ```mermaid
219
+ sequenceDiagram
220
+ autonumber
221
+ participant U as User Wallet + SDK
222
+ participant R as Relay
223
+ participant C as Cloak Program (Solana)
224
+ participant S as Scanner
225
+
226
+ U->>U: Generate (vk_priv, vk_pub)
227
+ U->>U: vkc = SHA256(vk_pub)
228
+ U->>R: Register vk_priv (signed)
229
+ U->>R: POST /transact + metadata_bundle + viewing_key_commitment=vkc
230
+ R->>C: Submit instruction [proof|public_inputs|vkc]
231
+ C-->>R: Confirm tx signature
232
+ R-->>U: Return signature
233
+
234
+ S->>S: Compute target_vkc from vk_pub
235
+ S->>C: Fetch recent program txs
236
+ S->>S: Decode ix data, extract vkc bytes, filter target_vkc
237
+ alt Relay enrichment enabled
238
+ S->>R: POST /admin/compliance/decrypt (admin signed)
239
+ R-->>S: Decrypted metadata rows (amount/recipient/type)
240
+ S->>S: Join on commitment + public_amount hints
241
+ end
242
+ S-->>U: Matching transactions
243
+ ```
244
+
245
+ ## Error Handling
246
+
247
+ ```typescript
248
+ import { CloakError } from "@cloak.dev/sdk";
249
+
250
+ try {
251
+ await sdk.withdraw(connection, note, recipient);
252
+ } catch (error) {
253
+ if (error instanceof CloakError) {
254
+ console.log("Category:", error.category); // 'wallet', 'network', 'prover', etc.
255
+ console.log("Retryable:", error.retryable);
256
+ }
257
+ }
258
+ ```
259
+
260
+ ## Links
261
+
262
+ - Website: [https://cloak.ag](https://cloak.ag)
263
+ - Documentation: [https://docs.cloak.ag](https://docs.cloak.ag)
264
+ - GitHub: [https://github.com/cloak-ag/sdk](https://github.com/cloak-ag/sdk)
265
+
266
+ ## License
267
+
268
+ Apache-2.0
@@ -0,0 +1,255 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
8
+ // src/core/utxo.ts
9
+ import { PublicKey } from "@solana/web3.js";
10
+ import { buildPoseidon } from "circomlibjs";
11
+ import { blake3 } from "@noble/hashes/blake3";
12
+ var FIELD_MODULUS = BigInt("21888242871839275222246405745257275088548364400416034343698204186575808495617");
13
+ var UTXO_KEY_DOMAIN = new TextEncoder().encode("cloak_utxo_priv_v1");
14
+ var NATIVE_SOL_MINT = new PublicKey("So11111111111111111111111111111111111111112");
15
+ var poseidonInstance = null;
16
+ async function getPoseidon() {
17
+ if (!poseidonInstance) {
18
+ poseidonInstance = await buildPoseidon();
19
+ }
20
+ return poseidonInstance;
21
+ }
22
+ function randomFieldElement() {
23
+ const bytes = new Uint8Array(32);
24
+ if (typeof crypto !== "undefined" && crypto.getRandomValues) {
25
+ crypto.getRandomValues(bytes);
26
+ } else {
27
+ const { randomBytes } = __require("crypto");
28
+ const nodeBytes = randomBytes(32);
29
+ for (let i = 0; i < 32; i++) {
30
+ bytes[i] = nodeBytes[i];
31
+ }
32
+ }
33
+ let value = BigInt(0);
34
+ for (let i = 0; i < 32; i++) {
35
+ value = value << BigInt(8) | BigInt(bytes[i]);
36
+ }
37
+ return value % (FIELD_MODULUS >> BigInt(4));
38
+ }
39
+ async function generateUtxoKeypair() {
40
+ const poseidon = await getPoseidon();
41
+ const privateKey = randomFieldElement();
42
+ const publicKeyHash = poseidon([privateKey, BigInt(0)]);
43
+ const publicKey = poseidon.F.toObject(publicKeyHash);
44
+ return { privateKey, publicKey };
45
+ }
46
+ async function deriveUtxoKeypairFromSpendKey(skSpend) {
47
+ if (!skSpend || !(skSpend instanceof Uint8Array) || skSpend.length !== 32) {
48
+ throw new Error("skSpend must be 32 bytes");
49
+ }
50
+ const preimage = new Uint8Array(UTXO_KEY_DOMAIN.length + skSpend.length);
51
+ preimage.set(UTXO_KEY_DOMAIN, 0);
52
+ preimage.set(skSpend, UTXO_KEY_DOMAIN.length);
53
+ const hash = blake3(preimage);
54
+ let value = BigInt(0);
55
+ for (let i = 0; i < 32; i++) {
56
+ value = value << BigInt(8) | BigInt(hash[i]);
57
+ }
58
+ const privateKey = value % (FIELD_MODULUS >> BigInt(4));
59
+ const publicKey = await derivePublicKey(privateKey);
60
+ return { privateKey, publicKey };
61
+ }
62
+ async function derivePublicKey(privateKey) {
63
+ const poseidon = await getPoseidon();
64
+ const hash = poseidon([privateKey, BigInt(0)]);
65
+ return poseidon.F.toObject(hash);
66
+ }
67
+ async function createUtxo(amount, keypair, mintAddress = NATIVE_SOL_MINT) {
68
+ const blinding = randomFieldElement();
69
+ const utxo = {
70
+ amount,
71
+ keypair,
72
+ blinding,
73
+ mintAddress
74
+ };
75
+ utxo.commitment = await computeCommitment(utxo);
76
+ return utxo;
77
+ }
78
+ async function createZeroUtxo(mintAddress = NATIVE_SOL_MINT, salt) {
79
+ const poseidon = await getPoseidon();
80
+ const effectiveSalt = salt ?? randomFieldElement();
81
+ const publicKeyHash = poseidon([effectiveSalt, BigInt(0)]);
82
+ const publicKey = poseidon.F.toObject(publicKeyHash);
83
+ const keypair = { privateKey: effectiveSalt, publicKey };
84
+ const utxo = {
85
+ amount: BigInt(0),
86
+ keypair,
87
+ blinding: BigInt(0),
88
+ mintAddress
89
+ };
90
+ utxo.commitment = await computeCommitment(utxo);
91
+ utxo.index = 0;
92
+ return utxo;
93
+ }
94
+ function pubkeyToFieldElement(pubkey) {
95
+ const bytes = pubkey.toBytes();
96
+ let value = BigInt(0);
97
+ for (let i = 0; i < 32; i++) {
98
+ value = value << BigInt(8) | BigInt(bytes[i]);
99
+ }
100
+ return value % FIELD_MODULUS;
101
+ }
102
+ async function computeCommitment(utxo) {
103
+ const poseidon = await getPoseidon();
104
+ const mintField = pubkeyToFieldElement(utxo.mintAddress);
105
+ const hash = poseidon([
106
+ utxo.amount,
107
+ utxo.keypair.publicKey,
108
+ utxo.blinding,
109
+ mintField
110
+ ]);
111
+ return poseidon.F.toObject(hash);
112
+ }
113
+ async function computeSignature(privateKey, commitment, pathIndex) {
114
+ const poseidon = await getPoseidon();
115
+ const hash = poseidon([privateKey, commitment, pathIndex]);
116
+ return poseidon.F.toObject(hash);
117
+ }
118
+ async function computeNullifier(utxo) {
119
+ if (utxo.index === void 0) {
120
+ throw new Error("UTXO must have an index to compute nullifier");
121
+ }
122
+ const poseidon = await getPoseidon();
123
+ const commitment = utxo.commitment ?? await computeCommitment(utxo);
124
+ const pathIndex = BigInt(utxo.index);
125
+ const signature = await computeSignature(
126
+ utxo.keypair.privateKey,
127
+ commitment,
128
+ pathIndex
129
+ );
130
+ const hash = poseidon([commitment, pathIndex, signature]);
131
+ return poseidon.F.toObject(hash);
132
+ }
133
+ function serializeUtxo(utxo) {
134
+ const buffer = new ArrayBuffer(128);
135
+ const view = new DataView(buffer);
136
+ const amountBytes = bigintToBytes(utxo.amount, 8);
137
+ for (let i = 0; i < 8; i++) {
138
+ view.setUint8(i, amountBytes[i]);
139
+ }
140
+ const privkeyBytes = bigintToBytes(utxo.keypair.privateKey, 32);
141
+ for (let i = 0; i < 32; i++) {
142
+ view.setUint8(8 + i, privkeyBytes[i]);
143
+ }
144
+ const blindingBytes = bigintToBytes(utxo.blinding, 32);
145
+ for (let i = 0; i < 32; i++) {
146
+ view.setUint8(40 + i, blindingBytes[i]);
147
+ }
148
+ const mintBytes = utxo.mintAddress.toBytes();
149
+ for (let i = 0; i < 32; i++) {
150
+ view.setUint8(72 + i, mintBytes[i]);
151
+ }
152
+ view.setUint32(104, utxo.index ?? 0, true);
153
+ return new Uint8Array(buffer);
154
+ }
155
+ async function deserializeUtxo(bytes) {
156
+ const view = new DataView(bytes.buffer, bytes.byteOffset);
157
+ const amountBytes = bytes.slice(0, 8);
158
+ const amount = bytesToBigint(amountBytes);
159
+ const privkeyBytes = bytes.slice(8, 40);
160
+ const privateKey = bytesToBigint(privkeyBytes);
161
+ const publicKey = await derivePublicKey(privateKey);
162
+ const blindingBytes = bytes.slice(40, 72);
163
+ const blinding = bytesToBigint(blindingBytes);
164
+ const mintBytes = bytes.slice(72, 104);
165
+ const mintAddress = new PublicKey(mintBytes);
166
+ const index = view.getUint32(104, true);
167
+ const utxo = {
168
+ amount,
169
+ keypair: { privateKey, publicKey },
170
+ blinding,
171
+ mintAddress,
172
+ index: index > 0 ? index : void 0
173
+ };
174
+ utxo.commitment = await computeCommitment(utxo);
175
+ return utxo;
176
+ }
177
+ function bigintToBytes(value, length) {
178
+ const result = new Uint8Array(length);
179
+ let remaining = value;
180
+ for (let i = 0; i < length; i++) {
181
+ result[i] = Number(remaining & BigInt(255));
182
+ remaining >>= BigInt(8);
183
+ }
184
+ return result;
185
+ }
186
+ function bytesToBigint(bytes) {
187
+ let result = BigInt(0);
188
+ for (let i = bytes.length - 1; i >= 0; i--) {
189
+ result = result << BigInt(8) | BigInt(bytes[i]);
190
+ }
191
+ return result;
192
+ }
193
+ function bigintToHex(value) {
194
+ return value.toString(16).padStart(64, "0");
195
+ }
196
+ function hexToBigint(hex) {
197
+ const cleanHex = hex.startsWith("0x") ? hex.slice(2) : hex;
198
+ return BigInt("0x" + cleanHex);
199
+ }
200
+ function bigintToBytes32(value) {
201
+ const result = new Uint8Array(32);
202
+ let remaining = value;
203
+ for (let i = 31; i >= 0; i--) {
204
+ result[i] = Number(remaining & BigInt(255));
205
+ remaining >>= BigInt(8);
206
+ }
207
+ return result;
208
+ }
209
+ async function utxoEquals(a, b) {
210
+ const commitmentA = a.commitment ?? await computeCommitment(a);
211
+ const commitmentB = b.commitment ?? await computeCommitment(b);
212
+ return commitmentA === commitmentB;
213
+ }
214
+ function sumUtxoAmounts(utxos) {
215
+ return utxos.reduce((sum, utxo) => sum + utxo.amount, BigInt(0));
216
+ }
217
+ function selectUtxos(available, targetAmount) {
218
+ const sorted = [...available].sort(
219
+ (a, b) => Number(b.amount - a.amount)
220
+ );
221
+ const selected = [];
222
+ let total = BigInt(0);
223
+ for (const utxo of sorted) {
224
+ if (total >= targetAmount) break;
225
+ selected.push(utxo);
226
+ total += utxo.amount;
227
+ }
228
+ if (total < targetAmount) {
229
+ return null;
230
+ }
231
+ return selected;
232
+ }
233
+
234
+ export {
235
+ __require,
236
+ NATIVE_SOL_MINT,
237
+ randomFieldElement,
238
+ generateUtxoKeypair,
239
+ deriveUtxoKeypairFromSpendKey,
240
+ derivePublicKey,
241
+ createUtxo,
242
+ createZeroUtxo,
243
+ pubkeyToFieldElement,
244
+ computeCommitment,
245
+ computeSignature,
246
+ computeNullifier,
247
+ serializeUtxo,
248
+ deserializeUtxo,
249
+ bigintToHex,
250
+ hexToBigint,
251
+ bigintToBytes32,
252
+ utxoEquals,
253
+ sumUtxoAmounts,
254
+ selectUtxos
255
+ };