@cloak.dev/sdk 0.1.7 → 0.2.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 +421 -278
- package/dist/chunk-YX5SCAMR.js +594 -0
- package/dist/index.cjs +6201 -1652
- package/dist/index.d.cts +2705 -447
- package/dist/index.d.ts +2705 -447
- package/dist/index.js +5729 -1609
- package/dist/{utxo-LSTVI4HH.js → utxo-PFJT3ETR.js} +7 -5
- package/package.json +4 -4
- package/CHANGELOG.md +0 -173
- package/dist/chunk-2SOX3JNO.js +0 -255
package/README.md
CHANGED
|
@@ -1,53 +1,81 @@
|
|
|
1
1
|
# @cloak.dev/sdk
|
|
2
2
|
|
|
3
|
-
TypeScript SDK for
|
|
3
|
+
TypeScript SDK for Cloak: shielded transactions on Solana. Shield SOL, USDC or USDT into a
|
|
4
|
+
per-mint pool, send privately inside the pool, withdraw to any address, or swap shielded SOL to
|
|
5
|
+
USDC/USDT with the output landing in a public token account. Groth16 proofs are generated
|
|
6
|
+
client-side (snarkjs) and verified on-chain by the Cloak program.
|
|
4
7
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
- 🔒 **Private Transfers**: Shield, send, and reclaim SOL or SPL tokens via the UTXO model and zero-knowledge proofs
|
|
8
|
-
- 🔁 **Shield-to-shield**: Move funds privately between UTXOs (`transfer`) without ever de-shielding
|
|
9
|
-
- 💱 **Token Swaps**: Swap shielded SOL for SPL tokens via Jupiter (`swapUtxo`) — SOL → USDC, BRZ, etc.
|
|
10
|
-
- 🔐 **Type-Safe**: Full TypeScript types; the `getConfig()` snapshot is structurally narrower than the constructor input so secrets cannot be re-exposed by accident
|
|
11
|
-
- 🌐 **Cross-Platform**: Browser (wallet adapter) and Node.js (`Keypair`) modes
|
|
12
|
-
- ⚡ **Functional API**: `transact` / `transfer` / `partialWithdraw` / `fullWithdraw` / `swapUtxo` — pass connection + program + relay + signer per call
|
|
8
|
+
Version 0.2.0 targets the mainnet program deployed on 2026-08-24 and the ceremony-frozen
|
|
9
|
+
`cloak-transaction-0.2.0` circuit bundle. See [Version and compatibility](#version-and-compatibility).
|
|
13
10
|
|
|
14
11
|
## Installation
|
|
15
12
|
|
|
16
13
|
```bash
|
|
17
14
|
npm install @cloak.dev/sdk @solana/web3.js
|
|
18
|
-
#
|
|
19
|
-
|
|
20
|
-
# or
|
|
21
|
-
pnpm add @cloak.dev/sdk @solana/web3.js
|
|
15
|
+
# swaps and SPL pools also need:
|
|
16
|
+
npm install @solana/spl-token
|
|
22
17
|
```
|
|
23
18
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
19
|
+
Node >= 18. Ships ESM and CJS builds with type declarations. Runs in Node and in the browser
|
|
20
|
+
(wallet-adapter signing); the on-chain Merkle-tree rebuild fallback is not available in browsers.
|
|
21
|
+
|
|
22
|
+
## Networks
|
|
23
|
+
|
|
24
|
+
### Mainnet (defaults)
|
|
25
|
+
|
|
26
|
+
| Setting | Value |
|
|
27
|
+
| --- | --- |
|
|
28
|
+
| Program | `zh1eLd6rSphLejbFfJEneUwzHRfMKxgzrgkfwA6qRkW` (`CLOAK_PROGRAM_ID`) |
|
|
29
|
+
| Relay | `https://api.cloak.ag`, passed as `relayUrl` or via `CLOAK_RELAY_URL` (see below) |
|
|
30
|
+
| Circuits | `https://storage.googleapis.com/cloak-circuits/circuits/0.2.0` (the SDK default) |
|
|
31
|
+
| Pools | WSOL (`NATIVE_SOL_MINT`), USDC `EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v`, USDT `Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB` |
|
|
32
|
+
| RPC | any mainnet RPC; use `new Connection(url, "confirmed")` |
|
|
33
|
+
|
|
34
|
+
The relay URL is never defaulted. `transact` resolves it from the `relayUrl` option, then from
|
|
35
|
+
`CLOAK_RELAY_URL`, and with neither set it throws before anything is signed or sent. This is
|
|
36
|
+
deliberate: a script that forgets the option must not ship its proof to production. Name production
|
|
37
|
+
explicitly:
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
relayUrl: "https://api.cloak.ag"
|
|
27
41
|
```
|
|
28
42
|
|
|
29
|
-
|
|
43
|
+
Every flow needs it, deposits included: the SDK registers the wallet's viewing key through the relay
|
|
44
|
+
before its first transaction (see [Recovery and discovery](#recovery-and-discovery-viewing-key)),
|
|
45
|
+
deposits fetch the signed risk quote the program requires from `<relayUrl>/range-quote`, and sends,
|
|
46
|
+
withdrawals and swaps are submitted through it. `enforceViewingKeyRegistration: false` skips only the
|
|
47
|
+
registration step (the shipped examples set it on a local fork); it is not a no-relay mode.
|
|
48
|
+
|
|
49
|
+
Do not expose `programId`, the relay URL or the circuits base as end-user input. Wallet adapters are
|
|
50
|
+
the in-app signer; keypair files belong to scripts only.
|
|
51
|
+
|
|
52
|
+
### Devnet and local
|
|
53
|
+
|
|
54
|
+
The same SDK runs against a devnet or local deployment by pointing the three coordinates at it:
|
|
30
55
|
|
|
31
|
-
|
|
56
|
+
```bash
|
|
57
|
+
SOLANA_RPC_URL=https://api.devnet.solana.com # or http://127.0.0.1:8899 for a local fork
|
|
58
|
+
CLOAK_RELAY_URL=<relay serving that deployment>
|
|
59
|
+
CLOAK_PROGRAM_ID=<program id of that deployment> # examples read this; pass it as programId in your code
|
|
60
|
+
```
|
|
32
61
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
- `transact`, `partialWithdraw`, and `fullWithdraw` already include stale-root retry handling.
|
|
36
|
-
- For simple CLI sends, require only `SOLANA_RPC_URL` and `KEYPAIR_PATH`.
|
|
62
|
+
The shipped examples default to a local fork (`http://127.0.0.1:8899`) with a local relay
|
|
63
|
+
(`http://127.0.0.1:5500`); see [Examples](#examples).
|
|
37
64
|
|
|
38
|
-
|
|
65
|
+
## Quick start: private SOL send (single file, keypair)
|
|
39
66
|
|
|
40
|
-
Use this
|
|
67
|
+
Shield from the signer, then unshield to the recipient. Use this shape for one-shot scripts.
|
|
41
68
|
|
|
42
69
|
```ts
|
|
43
70
|
import { readFileSync } from "fs";
|
|
44
71
|
import {
|
|
45
72
|
CLOAK_PROGRAM_ID,
|
|
46
73
|
NATIVE_SOL_MINT,
|
|
47
|
-
|
|
74
|
+
createRecoverableDepositUtxo,
|
|
48
75
|
createZeroUtxo,
|
|
49
76
|
fullWithdraw,
|
|
50
77
|
generateUtxoKeypair,
|
|
78
|
+
getNkFromUtxoPrivateKey,
|
|
51
79
|
transact,
|
|
52
80
|
} from "@cloak.dev/sdk";
|
|
53
81
|
import { Connection, Keypair, PublicKey } from "@solana/web3.js";
|
|
@@ -59,45 +87,54 @@ async function main() {
|
|
|
59
87
|
}
|
|
60
88
|
|
|
61
89
|
const rpcUrl = process.env.SOLANA_RPC_URL;
|
|
90
|
+
const relayUrl = process.env.CLOAK_RELAY_URL;
|
|
62
91
|
const keypairPath = process.env.KEYPAIR_PATH;
|
|
63
|
-
if (!rpcUrl || !keypairPath) {
|
|
64
|
-
throw new Error("Set SOLANA_RPC_URL and KEYPAIR_PATH");
|
|
92
|
+
if (!rpcUrl || !relayUrl || !keypairPath) {
|
|
93
|
+
throw new Error("Set SOLANA_RPC_URL, CLOAK_RELAY_URL and KEYPAIR_PATH");
|
|
65
94
|
}
|
|
66
95
|
|
|
67
96
|
const connection = new Connection(rpcUrl, "confirmed");
|
|
68
97
|
const signer = Keypair.fromSecretKey(
|
|
69
98
|
Uint8Array.from(JSON.parse(readFileSync(keypairPath, "utf8"))),
|
|
70
99
|
);
|
|
71
|
-
|
|
72
100
|
const recipient = new PublicKey(recipientArg);
|
|
73
|
-
const
|
|
101
|
+
const amount = BigInt(lamportsArg);
|
|
74
102
|
|
|
103
|
+
// The wallet's viewing base (nk). A real wallet derives it from its seed; a throwaway
|
|
104
|
+
// keypair keeps this script self-contained.
|
|
75
105
|
const owner = await generateUtxoKeypair();
|
|
76
|
-
const
|
|
106
|
+
const nk = getNkFromUtxoPrivateKey(owner.privateKey);
|
|
77
107
|
|
|
108
|
+
// Shield. The note's keypair and blinding are derived from (nk, noteSalt) and the salt is
|
|
109
|
+
// published inside the nk-encrypted chain note, so a scan holding only nk can rebuild it.
|
|
110
|
+
const { utxo, noteSalt } = await createRecoverableDepositUtxo(amount, nk, NATIVE_SOL_MINT);
|
|
78
111
|
const deposited = await transact(
|
|
79
112
|
{
|
|
80
113
|
inputUtxos: [await createZeroUtxo(NATIVE_SOL_MINT)],
|
|
81
|
-
outputUtxos: [
|
|
82
|
-
externalAmount:
|
|
114
|
+
outputUtxos: [utxo],
|
|
115
|
+
externalAmount: amount,
|
|
83
116
|
depositor: signer.publicKey,
|
|
84
117
|
},
|
|
85
118
|
{
|
|
86
119
|
connection,
|
|
87
120
|
programId: CLOAK_PROGRAM_ID,
|
|
121
|
+
relayUrl,
|
|
88
122
|
depositorKeypair: signer,
|
|
89
|
-
|
|
90
|
-
|
|
123
|
+
chainNoteViewingKeyNk: nk,
|
|
124
|
+
chainNoteSalt: noteSalt,
|
|
91
125
|
},
|
|
92
126
|
);
|
|
93
127
|
|
|
94
|
-
|
|
128
|
+
// Unshield to the recipient. outputUtxos[0] is the deposited note ([1] is the zero pad).
|
|
129
|
+
// The program takes its fee here (0.005 SOL + 0.3%).
|
|
130
|
+
const withdrawn = await fullWithdraw([deposited.outputUtxos[0]], recipient, {
|
|
95
131
|
connection,
|
|
96
132
|
programId: CLOAK_PROGRAM_ID,
|
|
133
|
+
relayUrl,
|
|
97
134
|
depositorKeypair: signer,
|
|
98
135
|
walletPublicKey: signer.publicKey,
|
|
136
|
+
chainNoteViewingKeyNk: nk,
|
|
99
137
|
cachedMerkleTree: deposited.merkleTree,
|
|
100
|
-
enforceViewingKeyRegistration: false,
|
|
101
138
|
});
|
|
102
139
|
|
|
103
140
|
console.log(withdrawn.signature);
|
|
@@ -112,327 +149,433 @@ main().catch((e) => {
|
|
|
112
149
|
Run:
|
|
113
150
|
|
|
114
151
|
```bash
|
|
115
|
-
SOLANA_RPC_URL="https
|
|
152
|
+
SOLANA_RPC_URL="https://<your mainnet rpc>" \
|
|
153
|
+
CLOAK_RELAY_URL="https://api.cloak.ag" \
|
|
116
154
|
KEYPAIR_PATH="/absolute/path/to/id.json" \
|
|
117
155
|
npx tsx send-sol-private.ts <recipientPubkey> <lamports>
|
|
118
156
|
```
|
|
119
157
|
|
|
120
|
-
|
|
158
|
+
Rules for minimal scripts:
|
|
121
159
|
|
|
122
|
-
-
|
|
123
|
-
-
|
|
124
|
-
|
|
125
|
-
-
|
|
160
|
+
- Take lamports from the CLI and keep all amount math in `bigint`; never parse SOL with float math.
|
|
161
|
+
- `<lamports>` must be at least `MIN_DEPOSIT_LAMPORTS` (0.01 SOL) and the withdrawal must exceed
|
|
162
|
+
its fee (see [Fees and limits](#fees-and-limits)).
|
|
163
|
+
- Use `KEYPAIR_PATH`; do not read raw private keys from the environment.
|
|
164
|
+
- Keep `programId` fixed to `CLOAK_PROGRAM_ID`.
|
|
165
|
+
- `transact`, `partialWithdraw` and `fullWithdraw` already retry on a stale Merkle root; do not
|
|
166
|
+
wrap them in your own retry loop.
|
|
126
167
|
|
|
127
|
-
###
|
|
168
|
+
### Browser (wallet adapter)
|
|
128
169
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
170
|
+
Replace `depositorKeypair` with the adapter's signers:
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
await transact(params, {
|
|
174
|
+
connection,
|
|
175
|
+
programId: CLOAK_PROGRAM_ID,
|
|
176
|
+
relayUrl,
|
|
177
|
+
signTransaction: (tx) => wallet.signTransaction(tx),
|
|
178
|
+
signMessage: (message) => wallet.signMessage(message), // signs the viewing-key registration challenge
|
|
179
|
+
depositorPublicKey: wallet.publicKey,
|
|
180
|
+
walletPublicKey: wallet.publicKey,
|
|
181
|
+
chainNoteViewingKeyNk: nk,
|
|
182
|
+
chainNoteSalt: noteSalt,
|
|
183
|
+
});
|
|
139
184
|
```
|
|
140
185
|
|
|
141
|
-
`
|
|
186
|
+
`signTransaction` must accept both a legacy `Transaction` and a `VersionedTransaction` (v0 with
|
|
187
|
+
address lookup tables); deposits use either depending on the path taken. `signMessage` is required
|
|
188
|
+
unless `enforceViewingKeyRegistration: false` is set; without it the call fails with
|
|
189
|
+
"Viewing key registration is mandatory: signMessage (wallet) or depositorKeypair is required."
|
|
142
190
|
|
|
143
|
-
|
|
191
|
+
## The UTXO model
|
|
144
192
|
|
|
145
|
-
|
|
193
|
+
- A shielded balance is a set of notes (`Utxo`): `{ amount, keypair, blinding, mintAddress, index?, commitment? }`.
|
|
194
|
+
Amounts are `bigint` in base units (lamports, or 10^-6 for USDC/USDT).
|
|
195
|
+
- Every transaction is 2-in / 2-out. Pad unused slots with `createZeroUtxo(mint)`.
|
|
196
|
+
- `externalAmount > 0` is a deposit (funds enter from `depositor`), `< 0` is a withdrawal to
|
|
197
|
+
`recipient`, `0` is a private send inside the pool.
|
|
198
|
+
- Pools are per mint. All inputs and outputs of one transaction live in the same pool;
|
|
199
|
+
`NATIVE_SOL_MINT` is the SOL pool.
|
|
200
|
+
- `TransactResult.outputUtxos` are the notes you can spend next (leaf `index` set). Persist them
|
|
201
|
+
with `serializeUtxo` / `deserializeUtxo`. Pass `result.merkleTree` as `cachedMerkleTree` to the
|
|
202
|
+
next call to skip re-fetching commitments.
|
|
203
|
+
- The Merkle tree is read at `confirmed`, so a note is spendable as soon as its deposit is confirmed.
|
|
146
204
|
|
|
147
|
-
|
|
205
|
+
Entry points: `transact` (general), `transfer`, `partialWithdraw`, `fullWithdraw`,
|
|
206
|
+
`swapUtxo` / `swapWithChange`.
|
|
148
207
|
|
|
149
|
-
|
|
208
|
+
### Private send (shield-to-shield)
|
|
150
209
|
|
|
151
|
-
|
|
210
|
+
```ts
|
|
211
|
+
import { deriveViewingKeyFromNk, transfer } from "@cloak.dev/sdk";
|
|
152
212
|
|
|
153
|
-
|
|
213
|
+
const result = await transfer(
|
|
214
|
+
[myNote], // inputs to spend (same pool)
|
|
215
|
+
recipientUtxoPublicKey, // bigint: the recipient's UTXO public key
|
|
216
|
+
amount,
|
|
217
|
+
{
|
|
218
|
+
connection,
|
|
219
|
+
programId: CLOAK_PROGRAM_ID,
|
|
220
|
+
relayUrl,
|
|
221
|
+
depositorKeypair: signer,
|
|
222
|
+
walletPublicKey: signer.publicKey,
|
|
223
|
+
chainNoteViewingKeyNk: myNk,
|
|
224
|
+
// The recipient's X25519 viewing public key. With it the recipient can discover the note
|
|
225
|
+
// from chain with their own key (scanRecipientDeliveryNotes). Without it the note is still
|
|
226
|
+
// valid but must be handed over out of band.
|
|
227
|
+
recipientViewingPublicKey: deriveViewingKeyFromNk(recipientNk).publicKey,
|
|
228
|
+
cachedMerkleTree: previous.merkleTree,
|
|
229
|
+
},
|
|
230
|
+
);
|
|
231
|
+
```
|
|
154
232
|
|
|
155
|
-
|
|
233
|
+
`transfer` builds the recipient note as output 0 and returns the change (owned by the input's
|
|
234
|
+
keypair) as output 1. Calling `transact` directly with `externalAmount: 0n` does the same; keep the
|
|
235
|
+
recipient note at `outputUtxos[0]`, which is the one the delivery carrier is built for.
|
|
236
|
+
The recipient shares two values with the sender: their UTXO public key (`UtxoKeypair.publicKey`)
|
|
237
|
+
and their viewing public key (`deriveViewingKeyFromNk(nk).publicKey`).
|
|
156
238
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
fullWithdraw,
|
|
164
|
-
generateUtxoKeypair,
|
|
165
|
-
transact,
|
|
166
|
-
} from "@cloak.dev/sdk";
|
|
167
|
-
import { Connection, Keypair, PublicKey } from "@solana/web3.js";
|
|
239
|
+
### Withdraw (unshield)
|
|
240
|
+
|
|
241
|
+
```ts
|
|
242
|
+
await fullWithdraw([note], recipientWallet, options); // whole note
|
|
243
|
+
await partialWithdraw([note], recipientWallet, amount, options); // amount out, change stays shielded
|
|
244
|
+
```
|
|
168
245
|
|
|
169
|
-
|
|
170
|
-
|
|
246
|
+
For SPL pools the recipient receives tokens in their associated token account. The program charges
|
|
247
|
+
its fee on the withdrawn amount ([Fees and limits](#fees-and-limits)).
|
|
171
248
|
|
|
172
|
-
|
|
173
|
-
const owner = await generateUtxoKeypair();
|
|
249
|
+
### SPL pools (USDC, USDT)
|
|
174
250
|
|
|
175
|
-
|
|
176
|
-
const amountLamports = 10_000_000n;
|
|
177
|
-
const output = await createUtxo(amountLamports, owner, NATIVE_SOL_MINT);
|
|
251
|
+
Same API with the pool mint on every note:
|
|
178
252
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
253
|
+
```ts
|
|
254
|
+
const usdc = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v");
|
|
255
|
+
const { utxo, noteSalt } = await createRecoverableDepositUtxo(2_000_000n, nk, usdc); // 2.00 USDC
|
|
256
|
+
await transact(
|
|
257
|
+
{ inputUtxos: [await createZeroUtxo(usdc)], outputUtxos: [utxo], externalAmount: 2_000_000n, depositor: signer.publicKey },
|
|
258
|
+
{ connection, programId: CLOAK_PROGRAM_ID, relayUrl, depositorKeypair: signer, chainNoteViewingKeyNk: nk, chainNoteSalt: noteSalt },
|
|
259
|
+
);
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
Minimum SPL deposit is 1.00 token. SOL deposits fit in a single v0 packet with the production
|
|
263
|
+
lookup table; SPL deposits currently still create a small supplemental lookup table
|
|
264
|
+
(about 0.0056 SOL rent, reclaimable by the depositor). Details: [docs/DEPOSIT-SIZE-NOTES.md](docs/DEPOSIT-SIZE-NOTES.md).
|
|
265
|
+
|
|
266
|
+
### Shielded swap (SOL to USDC/USDT)
|
|
267
|
+
|
|
268
|
+
Spends shielded SOL and delivers the output token to a public associated token account, routed
|
|
269
|
+
through Jupiter. Swap input is SOL only (the pool is wSOL-locked).
|
|
270
|
+
|
|
271
|
+
```ts
|
|
272
|
+
import { swapWithChange } from "@cloak.dev/sdk";
|
|
273
|
+
import { getAssociatedTokenAddressSync } from "@solana/spl-token";
|
|
274
|
+
|
|
275
|
+
const recipientAta = getAssociatedTokenAddressSync(usdc, recipientWallet);
|
|
276
|
+
|
|
277
|
+
const swap = await swapWithChange(
|
|
278
|
+
[solNote],
|
|
279
|
+
swapAmount, // lamports leaving the pool; the program's fee is taken from this
|
|
280
|
+
usdc,
|
|
281
|
+
recipientAta,
|
|
282
|
+
minOutputAmount, // proof-bound floor; quote the fee-adjusted amount and use the quote's threshold
|
|
186
283
|
{
|
|
187
284
|
connection,
|
|
188
285
|
programId: CLOAK_PROGRAM_ID,
|
|
189
|
-
relayUrl
|
|
286
|
+
relayUrl,
|
|
190
287
|
depositorKeypair: signer,
|
|
288
|
+
walletPublicKey: signer.publicKey,
|
|
289
|
+
chainNoteViewingKeyNk: nk,
|
|
290
|
+
swapSlippageBps: 100,
|
|
191
291
|
},
|
|
292
|
+
recipientWallet, // required on mainnet: the wallet the swap's risk quote is issued for
|
|
192
293
|
);
|
|
193
|
-
console.log("Deposit signature:", deposited.signature);
|
|
194
294
|
|
|
195
|
-
//
|
|
196
|
-
|
|
197
|
-
const withdrawn = await fullWithdraw(deposited.outputUtxos, recipient, {
|
|
198
|
-
connection,
|
|
199
|
-
programId: CLOAK_PROGRAM_ID,
|
|
200
|
-
relayUrl: "https://api.cloak.ag",
|
|
201
|
-
depositorKeypair: signer,
|
|
202
|
-
cachedMerkleTree: deposited.merkleTree,
|
|
203
|
-
});
|
|
204
|
-
console.log("Withdraw signature:", withdrawn.signature);
|
|
295
|
+
swap.signature; // the executed swap; swapWithChange resolves only once execution has completed
|
|
296
|
+
swap.refund; // persist: the refund-fallback secret (see below)
|
|
205
297
|
```
|
|
206
298
|
|
|
207
|
-
|
|
299
|
+
`swapUtxo` is the lower-level form taking `UtxoSwapParams` and an explicit change note. Quote the
|
|
300
|
+
amount after the protocol fee (`swapAmount - calculateFeeBigint(swapAmount)` for SOL) so
|
|
301
|
+
`minOutputAmount` is achievable. `outputMint`, `recipientAta` and `minOutputAmount` are bound into
|
|
302
|
+
the proof and cannot be changed after it is generated.
|
|
208
303
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
import {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
const result = await transact(
|
|
233
|
-
{
|
|
234
|
-
inputUtxos: [await createZeroUtxo(NATIVE_SOL_MINT), await createZeroUtxo(NATIVE_SOL_MINT)],
|
|
235
|
-
outputUtxos: [output, await createZeroUtxo(NATIVE_SOL_MINT)],
|
|
236
|
-
externalAmount: amount,
|
|
237
|
-
depositor: wallet.publicKey,
|
|
238
|
-
},
|
|
239
|
-
{
|
|
240
|
-
connection,
|
|
241
|
-
programId: CLOAK_PROGRAM_ID,
|
|
242
|
-
relayUrl: "https://api.cloak.ag",
|
|
243
|
-
wallet: {
|
|
244
|
-
publicKey: wallet.publicKey,
|
|
245
|
-
signTransaction: wallet.signTransaction,
|
|
246
|
-
},
|
|
247
|
-
},
|
|
248
|
-
);
|
|
249
|
-
console.log("Deposited:", result.signature);
|
|
250
|
-
}, [connection, wallet]);
|
|
251
|
-
|
|
252
|
-
return <button onClick={onClick}>Deposit 0.01 SOL</button>;
|
|
304
|
+
#### Timed-out swaps
|
|
305
|
+
|
|
306
|
+
`swapWithChange` polls the swap status and throws if the swap is refunded, cancelled or fails, or if
|
|
307
|
+
polling runs out (`swapStatusMaxAttempts` x `swapStatusDelayMs`, default 60 x 2 s); the result above
|
|
308
|
+
exists only for a completed swap. On a timeout the program returns the principal, net of the swap
|
|
309
|
+
fee, to the SOL pool as a refund note, which is spent through the normal withdraw path. When the
|
|
310
|
+
swap was submitted with `chainNoteViewingKeyNk` (`swap.refund.derivedFromNk === true`) that note is
|
|
311
|
+
discoverable from the key alone:
|
|
312
|
+
|
|
313
|
+
```ts
|
|
314
|
+
import { discoverSwapRefunds, fullWithdraw, NATIVE_SOL_MINT, type Utxo } from "@cloak.dev/sdk";
|
|
315
|
+
|
|
316
|
+
const refunds = await discoverSwapRefunds(connection, CLOAK_PROGRAM_ID, nk); // DiscoveredSwapRefund[]
|
|
317
|
+
for (const refund of refunds) {
|
|
318
|
+
const note: Utxo = {
|
|
319
|
+
amount: refund.amount, // principal net of the swap fee
|
|
320
|
+
keypair: refund.keypair,
|
|
321
|
+
blinding: refund.blinding,
|
|
322
|
+
mintAddress: NATIVE_SOL_MINT,
|
|
323
|
+
commitment: refund.commitment,
|
|
324
|
+
index: Number(refund.leafIndex),
|
|
325
|
+
};
|
|
326
|
+
await fullWithdraw([note], recipientWallet, options);
|
|
253
327
|
}
|
|
254
328
|
```
|
|
255
329
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
`
|
|
259
|
-
|
|
330
|
+
Swaps that executed leave no refund note, and an empty result only says that none of the scanned
|
|
331
|
+
leaves derive from this `nk`. A swap submitted without `chainNoteViewingKeyNk` has `swap.refund`
|
|
332
|
+
(hex `privateKey`, `publicKey`, `blinding`) as the only copy of its secret; rebuild the note from it
|
|
333
|
+
with the on-chain `SwapState.sol_amount` as the amount and
|
|
334
|
+
`computeSwapRefundCommitment(amount, publicKey, blinding)` as the commitment, then look up the leaf
|
|
335
|
+
index as in [Recovery and discovery](#recovery-and-discovery-viewing-key).
|
|
260
336
|
|
|
261
|
-
##
|
|
337
|
+
## Recovery and discovery (viewing key)
|
|
262
338
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
inputUtxos: [await createZeroUtxo(), await createZeroUtxo()],
|
|
269
|
-
outputUtxos: [outputUtxo, await createZeroUtxo()],
|
|
270
|
-
externalAmount: 10_000_000n, // positive = deposit
|
|
271
|
-
depositor: signer.publicKey,
|
|
272
|
-
},
|
|
273
|
-
{ connection, programId: CLOAK_PROGRAM_ID, relayUrl: "https://api.cloak.ag", depositorKeypair: signer },
|
|
274
|
-
);
|
|
275
|
-
console.log("Leaf index:", result.commitmentIndices[0]);
|
|
276
|
-
console.log("Output UTXO:", result.outputUtxos[0]);
|
|
277
|
-
```
|
|
339
|
+
The viewing base `nk` is derived from the wallet's spend key (`getNkFromUtxoPrivateKey(privateKey)`
|
|
340
|
+
or `expandSpendKey(skSpend).nsk`). It decrypts the chain notes the SDK attaches to transactions and
|
|
341
|
+
opens notes delivered by other people's private sends. Recoverable deposit notes and swap refund
|
|
342
|
+
notes also derive their secrets from it, which is what makes key-only recovery possible, so store
|
|
343
|
+
it with the same care as the spend key.
|
|
278
344
|
|
|
279
|
-
|
|
345
|
+
Before a wallet's first transaction the SDK registers `nk` with the relay for compliance scanning:
|
|
346
|
+
it requests a challenge from `<relayUrl>/viewing-key/challenge`, signs it with `signMessage` (wallet)
|
|
347
|
+
or `depositorKeypair`, and posts `nk` with the signature to `<relayUrl>/viewing-key/register`.
|
|
348
|
+
Nothing viewing-key related is written on-chain. The step is on by default;
|
|
349
|
+
`enforceViewingKeyRegistration: false` disables it.
|
|
280
350
|
|
|
281
|
-
```
|
|
282
|
-
import {
|
|
351
|
+
```ts
|
|
352
|
+
import { scanTransactions, fetchCommitments, type Utxo } from "@cloak.dev/sdk";
|
|
283
353
|
|
|
284
|
-
|
|
285
|
-
const result = await fullWithdraw(myUtxos, recipientPublicKey, {
|
|
354
|
+
const scan = await scanTransactions({
|
|
286
355
|
connection,
|
|
287
356
|
programId: CLOAK_PROGRAM_ID,
|
|
288
|
-
|
|
289
|
-
|
|
357
|
+
viewingKeyNk: nk,
|
|
358
|
+
ownerUtxoPublicKey: owner.publicKey, // authenticates delivered notes' commitments
|
|
359
|
+
deliveryMints: [NATIVE_SOL_MINT],
|
|
360
|
+
untilSignature: lastScan?.lastSignature, // incremental scans
|
|
290
361
|
});
|
|
291
362
|
|
|
292
|
-
//
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
programId: CLOAK_PROGRAM_ID,
|
|
296
|
-
relayUrl: "https://api.cloak.ag",
|
|
297
|
-
depositorKeypair: signer,
|
|
298
|
-
});
|
|
363
|
+
scan.recoveredDepositNotes; // own deposits built with createRecoverableDepositUtxo, in spendable form
|
|
364
|
+
scan.deliveredNotes; // notes sent to this wallet (amount + blinding; the keypair is your own)
|
|
365
|
+
scan.transactions; // history rows for compliance reporting
|
|
299
366
|
```
|
|
300
367
|
|
|
301
|
-
|
|
368
|
+
`scanRecipientDeliveryNotes` returns only the delivered notes. Deposit recovery applies to deposits
|
|
369
|
+
built with `createRecoverableDepositUtxo` (the default in the examples); a deposit built with
|
|
370
|
+
`createUtxo` has a random blinding that is written nowhere and appears as history only.
|
|
302
371
|
|
|
303
|
-
|
|
304
|
-
|
|
372
|
+
A recovered or delivered note needs its leaf index before it can be spent. Look it up by commitment.
|
|
373
|
+
The two record types differ: `RecoveredDepositNoteRecord` carries the note's own `keypair`, a
|
|
374
|
+
`bigint` `commitment` and a `PublicKey` `mintAddress`; `DeliveredNote` carries a hex `commitment`,
|
|
375
|
+
a base58 `mint` (set only when the scan was given `ownerUtxoPublicKey`) and no keypair, because the
|
|
376
|
+
keypair is yours.
|
|
305
377
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
378
|
+
```ts
|
|
379
|
+
import { PublicKey } from "@solana/web3.js";
|
|
380
|
+
|
|
381
|
+
const entries = await fetchCommitments(relayUrl, { mint: NATIVE_SOL_MINT });
|
|
382
|
+
const indexOf = (commitment: bigint) =>
|
|
383
|
+
entries.find((e) => BigInt("0x" + e.commitment.replace(/^0x/, "")) === commitment)?.index;
|
|
384
|
+
|
|
385
|
+
// Own deposit recovered from nk
|
|
386
|
+
const recovered = scan.recoveredDepositNotes[0];
|
|
387
|
+
const ownNote: Utxo = {
|
|
388
|
+
amount: recovered.amount,
|
|
389
|
+
keypair: recovered.keypair,
|
|
390
|
+
blinding: recovered.blinding,
|
|
391
|
+
mintAddress: recovered.mintAddress,
|
|
392
|
+
commitment: recovered.commitment,
|
|
393
|
+
index: indexOf(recovered.commitment),
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
// Note delivered by someone else's private send
|
|
397
|
+
const delivered = scan.deliveredNotes[0];
|
|
398
|
+
if (!delivered.commitmentVerified || !delivered.mint) {
|
|
399
|
+
throw new Error("scan with ownerUtxoPublicKey to authenticate delivered notes");
|
|
400
|
+
}
|
|
401
|
+
const deliveredCommitment = BigInt("0x" + delivered.commitment);
|
|
402
|
+
const deliveredNote: Utxo = {
|
|
403
|
+
amount: delivered.amount,
|
|
404
|
+
keypair: owner, // the UtxoKeypair whose publicKey the sender used
|
|
405
|
+
blinding: delivered.blinding,
|
|
406
|
+
mintAddress: new PublicKey(delivered.mint),
|
|
407
|
+
commitment: deliveredCommitment,
|
|
408
|
+
index: indexOf(deliveredCommitment),
|
|
409
|
+
};
|
|
312
410
|
```
|
|
313
411
|
|
|
314
|
-
|
|
412
|
+
`discoverSwapRefunds(connection, programId, nk)` returns `DiscoveredSwapRefund` records (`keypair`,
|
|
413
|
+
`blinding`, `amount`, `commitment`, `leafIndex: bigint`); build the `Utxo` from one as shown under
|
|
414
|
+
[Timed-out swaps](#timed-out-swaps).
|
|
315
415
|
|
|
316
|
-
|
|
317
|
-
import { swapUtxo } from "@cloak.dev/sdk";
|
|
416
|
+
`toComplianceReport(scan)` and `formatComplianceCsv(report)` turn a scan into a report.
|
|
318
417
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
);
|
|
418
|
+
## Nullifiers and spent checks
|
|
419
|
+
|
|
420
|
+
Two exports share a name and are not interchangeable:
|
|
421
|
+
|
|
422
|
+
- `computeUtxoNullifier(utxo)` is the circuit's scheme: `Poseidon(commitment, index, signature)`.
|
|
423
|
+
This is the value the program records when a note is spent. Use it for spent checks.
|
|
424
|
+
- `computeNullifier(skSpend, leafIndex)` is `Poseidon(sk0, sk1, leafIndex)` from the legacy
|
|
425
|
+
`withdraw_regular` circuit. It does not match anything the 0.2.0 program stores.
|
|
426
|
+
|
|
427
|
+
Prefer the helpers, which use the UTXO scheme:
|
|
428
|
+
|
|
429
|
+
```ts
|
|
430
|
+
import { verifyUtxos, preflightNullifiers } from "@cloak.dev/sdk";
|
|
431
|
+
|
|
432
|
+
const { spent } = await verifyUtxos(notes, connection, CLOAK_PROGRAM_ID);
|
|
433
|
+
await preflightNullifiers(notes, connection, CLOAK_PROGRAM_ID); // throws UtxoAlreadySpentError
|
|
334
434
|
```
|
|
335
435
|
|
|
336
|
-
|
|
436
|
+
`transact` runs the preflight itself before generating a proof.
|
|
337
437
|
|
|
338
|
-
|
|
339
|
-
- **Variable Fee**: 0.3% of withdrawn amount
|
|
438
|
+
## Fees and limits
|
|
340
439
|
|
|
341
|
-
|
|
440
|
+
Fees are collected on-chain by the program from each pool's `PoolConfig` account
|
|
441
|
+
(`["pool_config", mint]`). They apply to withdrawals and swaps, on the amount leaving the pool.
|
|
442
|
+
Deposits and private sends carry no protocol fee.
|
|
342
443
|
|
|
343
|
-
|
|
344
|
-
|
|
444
|
+
| Pool | Fixed fee | Rate | Minimum deposit |
|
|
445
|
+
| --- | --- | --- | --- |
|
|
446
|
+
| SOL | 0.005 SOL | 0.3% | 0.01 SOL |
|
|
447
|
+
| USDC | 0.45 USDC | 0.3% | 1.00 USDC |
|
|
448
|
+
| USDT | 0.45 USDT | 0.3% | 1.00 USDT |
|
|
345
449
|
|
|
346
|
-
|
|
347
|
-
|
|
450
|
+
A withdrawal or swap must exceed its fee or the program rejects it; deposits below the minimum are
|
|
451
|
+
rejected with `DepositTooSmall`.
|
|
452
|
+
|
|
453
|
+
`utils/fees.ts` mirrors the SOL pool for estimates in the UI:
|
|
454
|
+
|
|
455
|
+
```ts
|
|
456
|
+
import { calculateFeeBigint, getDistributableAmount, isWithdrawAmountSufficient } from "@cloak.dev/sdk";
|
|
457
|
+
|
|
458
|
+
calculateFeeBigint(1_000_000_000n); // 8_000_000n (0.005 SOL + 0.3% of 1 SOL)
|
|
459
|
+
getDistributableAmount(100_000_000); // 94_700_000 (0.1 SOL withdrawal, net to recipient)
|
|
460
|
+
isWithdrawAmountSufficient(6_000_000n); // true: 0.006 SOL covers the 0.005018 SOL fee
|
|
348
461
|
```
|
|
349
462
|
|
|
350
|
-
|
|
463
|
+
For USDC/USDT compute `450_000n + amount * 3n / 1000n`. Treat these as estimates: the deployed
|
|
464
|
+
`PoolConfig` is the source of truth. Solana transaction fees and, for SPL deposits, the supplemental
|
|
465
|
+
lookup-table rent are separate.
|
|
351
466
|
|
|
352
|
-
|
|
467
|
+
## Circuit artifacts
|
|
353
468
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
469
|
+
Proving uses the `transaction` circuit from bundle `cloak-transaction-0.2.0` (multi-party ceremony,
|
|
470
|
+
6 contributors plus a public final beacon; 42,672 constraints, 9 public inputs). The verifying key is
|
|
471
|
+
embedded in the program.
|
|
472
|
+
|
|
473
|
+
| Artifact | SHA-256 |
|
|
474
|
+
| --- | --- |
|
|
475
|
+
| `transaction_js/transaction.wasm` | `02ec02e954ae3932827ad9de51afa597ca95569aa97fec8410879c937a58aa2b` |
|
|
476
|
+
| `transaction_final.zkey` | `9da7db8cb1370fc497d36a0365f1f107ab0b0c13ca66fa9f0287e5f96ee68d25` |
|
|
477
|
+
| `transaction.vkey.json` | `deb40e7b94eae17db2975d23dcf26c26db2a36a4f02d14a25830dee3e88fb93c` |
|
|
478
|
+
|
|
479
|
+
The SDK downloads the artifacts once per process from
|
|
480
|
+
`DEFAULT_TRANSACTION_CIRCUITS_URL` (`https://storage.googleapis.com/cloak-circuits/circuits/0.2.0`),
|
|
481
|
+
hashes them, and refuses to prove if any digest differs from the pinned values. No configuration is
|
|
482
|
+
needed for mainnet.
|
|
483
|
+
|
|
484
|
+
To serve the artifacts yourself (a mirror or a local directory containing
|
|
485
|
+
`transaction_js/transaction.wasm` and `transaction_final.zkey` with the same digests):
|
|
486
|
+
|
|
487
|
+
```ts
|
|
488
|
+
import { resolveCircuitsBase, setCircuitsPath } from "@cloak.dev/sdk";
|
|
489
|
+
|
|
490
|
+
// explicit argument, else CLOAK_CIRCUITS_PATH / CLOAK_CIRCUITS, else the pinned default
|
|
491
|
+
setCircuitsPath(resolveCircuitsBase());
|
|
364
492
|
```
|
|
365
493
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
-
|
|
373
|
-
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
S->>S: Decode ix data, extract vkc bytes, filter target_vkc
|
|
395
|
-
alt Relay enrichment enabled
|
|
396
|
-
S->>R: POST /admin/compliance/decrypt (admin signed)
|
|
397
|
-
R-->>S: Decrypted metadata rows (amount/recipient/type)
|
|
398
|
-
S->>S: Join on commitment + public_amount hints
|
|
399
|
-
end
|
|
400
|
-
S-->>U: Matching transactions
|
|
494
|
+
Never write the artifact URL out by hand: the version segment and the digests are declared together
|
|
495
|
+
in `src/config/circuit-release.ts` so they cannot drift.
|
|
496
|
+
|
|
497
|
+
## Version and compatibility
|
|
498
|
+
|
|
499
|
+
- SDK 0.2.0 pairs with the mainnet program at `zh1eLd6rSphLejbFfJEneUwzHRfMKxgzrgkfwA6qRkW`
|
|
500
|
+
(deployed 2026-08-24) and the 0.2.0 circuit bundle. All flows in this README were exercised on
|
|
501
|
+
mainnet on 2026-08-25: deposit, private send and withdraw on SOL, USDC and USDT; swaps SOL to
|
|
502
|
+
USDC and USDT; viewing-key discovery followed by a spend; keypair-only deposit recovery.
|
|
503
|
+
- Proofs generated with the 0.1.x circuits are rejected by the mainnet program. SDK 0.1.x builds
|
|
504
|
+
(which ship the 0.1.0 bundle) are not usable against it; upgrade to 0.2.0.
|
|
505
|
+
- The note-based `CloakSDK` class (`deposit` / `withdraw` / `send` / `swap` on `CloakNote`) targets
|
|
506
|
+
the pre-0.2.0 instruction layout and the unpublished 0.1.0 `withdraw_*` circuits. It is still
|
|
507
|
+
exported but does not work against the current program. Use the UTXO API above.
|
|
508
|
+
|
|
509
|
+
## Examples
|
|
510
|
+
|
|
511
|
+
```bash
|
|
512
|
+
npm run example:fast-send # deposit SOL, withdraw to a recipient
|
|
513
|
+
npm run example:transfer # deposit, private send, recipient withdraw
|
|
514
|
+
npm run example:fast-usdc-send # deposit SOL, swap to USDC, deposit USDC, withdraw USDC
|
|
515
|
+
npm run example:usdc-pool-transfer # USDC pool: A deposits, sends to B, B spends
|
|
516
|
+
npm run example:swap # SOL -> USDC swap with interactive route retry
|
|
517
|
+
npm run example:swap-usdc # same, with the canonical swap markers
|
|
518
|
+
npm run example:swap-brz # BRZ with automatic USDC fallback
|
|
519
|
+
npm run example:swap-recovery # pending / timed-out swap handling
|
|
520
|
+
npm run example:history-scan # every flow, then full and incremental viewing-key scans
|
|
521
|
+
npm run test:examples # dry-run all of the above (no wallet, no RPC)
|
|
401
522
|
```
|
|
402
523
|
|
|
403
|
-
|
|
524
|
+
Environment read by the examples:
|
|
404
525
|
|
|
405
|
-
|
|
526
|
+
| Variable | Default |
|
|
527
|
+
| --- | --- |
|
|
528
|
+
| `SOLANA_RPC_URL` | `http://127.0.0.1:8899` (local fork) |
|
|
529
|
+
| `CLOAK_RELAY_URL` | `http://127.0.0.1:5500` (local relay) |
|
|
530
|
+
| `CLOAK_PROGRAM_ID` | `CLOAK_PROGRAM_ID` |
|
|
531
|
+
| `CLOAK_CIRCUITS_PATH` | the pinned 0.2.0 bundle |
|
|
532
|
+
| `CLOAK_ALT_ADDRESSES` | unset; the production lookup tables are resolved automatically |
|
|
533
|
+
|
|
534
|
+
The examples fund throwaway keypairs from `~/.config/solana/id.json` and print stable
|
|
535
|
+
`FULL_SIG|<example>|<step>|<signature>` markers. They import `@cloak.ag/sdk`, which
|
|
536
|
+
`tsconfig.json` maps to `src/index.ts`; outside this repo import `@cloak.dev/sdk`.
|
|
537
|
+
|
|
538
|
+
## Error handling
|
|
539
|
+
|
|
540
|
+
```ts
|
|
406
541
|
import {
|
|
407
542
|
CloakError,
|
|
408
543
|
UtxoAlreadySpentError,
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
CLOAK_PROGRAM_ID,
|
|
544
|
+
SanctionsQuoteError,
|
|
545
|
+
RelayInternalError,
|
|
546
|
+
SettlementVerificationError,
|
|
413
547
|
} from "@cloak.dev/sdk";
|
|
414
548
|
|
|
415
549
|
try {
|
|
416
|
-
await fullWithdraw(
|
|
417
|
-
connection,
|
|
418
|
-
programId: CLOAK_PROGRAM_ID,
|
|
419
|
-
relayUrl: "https://api.cloak.ag",
|
|
420
|
-
depositorKeypair: signer,
|
|
421
|
-
});
|
|
550
|
+
await fullWithdraw([note], recipient, options);
|
|
422
551
|
} catch (error) {
|
|
423
552
|
if (error instanceof UtxoAlreadySpentError) {
|
|
424
|
-
//
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
} else if (error instanceof RootNotFoundError) {
|
|
428
|
-
// Relay/chain root mismatch; retry against a fresh tree.
|
|
553
|
+
// the note was spent elsewhere; rescan
|
|
554
|
+
} else if (error instanceof SettlementVerificationError) {
|
|
555
|
+
// error.outcome tells whether it is safe to retry; error.signature is what to look up
|
|
429
556
|
} else if (error instanceof CloakError) {
|
|
430
|
-
console.log(
|
|
431
|
-
console.log("Retryable:", error.retryable);
|
|
557
|
+
console.log(error.category, error.retryable);
|
|
432
558
|
}
|
|
433
559
|
}
|
|
434
560
|
```
|
|
435
561
|
|
|
562
|
+
Catch the typed errors instead of matching message strings. `confirmTransactSettlement` re-checks a
|
|
563
|
+
signature from chain state when an outcome was reported as unknown.
|
|
564
|
+
|
|
565
|
+
## Development
|
|
566
|
+
|
|
567
|
+
```bash
|
|
568
|
+
npm install
|
|
569
|
+
npm run lint # tsc --noEmit
|
|
570
|
+
npm test # jest
|
|
571
|
+
npm run build # tsup -> dist/
|
|
572
|
+
npm run verify:dist # dist is byte-identical to a fresh build
|
|
573
|
+
npm run test:examples # dry-run the examples
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
Notes: [docs/DEPOSIT-SIZE-NOTES.md](docs/DEPOSIT-SIZE-NOTES.md) (deposit transaction size,
|
|
577
|
+
confirmed-tree reads, supplemental lookup tables).
|
|
578
|
+
|
|
436
579
|
## Links
|
|
437
580
|
|
|
438
581
|
- Website: [https://cloak.ag](https://cloak.ag)
|