@cloakedagent/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.
Files changed (72) hide show
  1. package/README.md +90 -0
  2. package/dist/agent.d.ts +321 -0
  3. package/dist/agent.d.ts.map +1 -0
  4. package/dist/agent.js +877 -0
  5. package/dist/agent.js.map +1 -0
  6. package/dist/config.d.ts +33 -0
  7. package/dist/config.d.ts.map +1 -0
  8. package/dist/config.js +64 -0
  9. package/dist/config.js.map +1 -0
  10. package/dist/constants.d.ts +4 -0
  11. package/dist/constants.d.ts.map +1 -0
  12. package/dist/constants.js +9 -0
  13. package/dist/constants.js.map +1 -0
  14. package/dist/idl.json +1347 -0
  15. package/dist/index.d.ts +10 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +66 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/mcp/index.d.ts +7 -0
  20. package/dist/mcp/index.d.ts.map +1 -0
  21. package/dist/mcp/index.js +374 -0
  22. package/dist/mcp/index.js.map +1 -0
  23. package/dist/mcp/tools.d.ts +26 -0
  24. package/dist/mcp/tools.d.ts.map +1 -0
  25. package/dist/mcp/tools.js +320 -0
  26. package/dist/mcp/tools.js.map +1 -0
  27. package/dist/mcp/types.d.ts +61 -0
  28. package/dist/mcp/types.d.ts.map +1 -0
  29. package/dist/mcp/types.js +4 -0
  30. package/dist/mcp/types.js.map +1 -0
  31. package/dist/relayer.d.ts +130 -0
  32. package/dist/relayer.d.ts.map +1 -0
  33. package/dist/relayer.js +225 -0
  34. package/dist/relayer.js.map +1 -0
  35. package/dist/signer.d.ts +18 -0
  36. package/dist/signer.d.ts.map +1 -0
  37. package/dist/signer.js +34 -0
  38. package/dist/signer.js.map +1 -0
  39. package/dist/token.d.ts +320 -0
  40. package/dist/token.d.ts.map +1 -0
  41. package/dist/token.js +896 -0
  42. package/dist/token.js.map +1 -0
  43. package/dist/types.d.ts +66 -0
  44. package/dist/types.d.ts.map +1 -0
  45. package/dist/types.js +3 -0
  46. package/dist/types.js.map +1 -0
  47. package/dist/zk/browser-prover.d.ts +85 -0
  48. package/dist/zk/browser-prover.d.ts.map +1 -0
  49. package/dist/zk/browser-prover.js +260 -0
  50. package/dist/zk/browser-prover.js.map +1 -0
  51. package/dist/zk/discovery.d.ts +65 -0
  52. package/dist/zk/discovery.d.ts.map +1 -0
  53. package/dist/zk/discovery.js +143 -0
  54. package/dist/zk/discovery.js.map +1 -0
  55. package/dist/zk/index.d.ts +14 -0
  56. package/dist/zk/index.d.ts.map +1 -0
  57. package/dist/zk/index.js +47 -0
  58. package/dist/zk/index.js.map +1 -0
  59. package/dist/zk/ownership_proof.json +1 -0
  60. package/dist/zk/poseidon.d.ts +31 -0
  61. package/dist/zk/poseidon.d.ts.map +1 -0
  62. package/dist/zk/poseidon.js +103 -0
  63. package/dist/zk/poseidon.js.map +1 -0
  64. package/dist/zk/prover.d.ts +49 -0
  65. package/dist/zk/prover.d.ts.map +1 -0
  66. package/dist/zk/prover.js +120 -0
  67. package/dist/zk/prover.js.map +1 -0
  68. package/dist/zk/secrets.d.ts +62 -0
  69. package/dist/zk/secrets.d.ts.map +1 -0
  70. package/dist/zk/secrets.js +98 -0
  71. package/dist/zk/secrets.js.map +1 -0
  72. package/package.json +74 -0
package/dist/token.js ADDED
@@ -0,0 +1,896 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.CloakToken = void 0;
7
+ const web3_js_1 = require("@solana/web3.js");
8
+ const anchor_1 = require("@coral-xyz/anchor");
9
+ const bs58_1 = __importDefault(require("bs58"));
10
+ const constants_1 = require("./constants");
11
+ const idl_json_1 = __importDefault(require("./idl.json"));
12
+ const zk_1 = require("./zk");
13
+ const relayer_1 = require("./relayer");
14
+ // Seconds in a day for daily limit calculations
15
+ const SECONDS_PER_DAY = 86400;
16
+ /**
17
+ * CloakToken - Represents a Cloak payment token
18
+ *
19
+ * A Cloak token is a Solana keypair where:
20
+ * - The private key is the "token code" (what users save)
21
+ * - The derived PDA holds the SOL balance
22
+ * - Signing with the keypair authorizes redemptions
23
+ *
24
+ * Two modes of operation:
25
+ * - Agent mode (via constructor): Has token code, can spend
26
+ * - Owner mode (via forOwner): No token code, can manage (freeze/unfreeze/update/close)
27
+ */
28
+ class CloakToken {
29
+ /**
30
+ * Create a CloakToken from a token code (agent mode - can spend)
31
+ * @param tokenCode - Base58 encoded secret key
32
+ * @param rpcUrl - Solana RPC endpoint URL
33
+ */
34
+ constructor(tokenCode, rpcUrl) {
35
+ // Private mode fields
36
+ this._ownerCommitment = null;
37
+ this._agentSecret = null;
38
+ this._nonce = null;
39
+ // Cached PDAs (set by forPrivateOwner to avoid getter issues)
40
+ this._tokenStatePda = null;
41
+ this._vaultPda = null;
42
+ // Decode the base58 token code to get the secret key
43
+ const secretKey = bs58_1.default.decode(tokenCode);
44
+ // Create keypair from secret key
45
+ this.keypair = web3_js_1.Keypair.fromSecretKey(secretKey);
46
+ this.delegatePubkey = this.keypair.publicKey;
47
+ // Create connection to Solana
48
+ this.connection = new web3_js_1.Connection(rpcUrl, "confirmed");
49
+ // Derive the legacy PDA (seeds = ["token", delegate]) - kept for backward compatibility
50
+ const [pda, bump] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("token"), this.delegatePubkey.toBuffer()], constants_1.CLOAK_PROGRAM_ID);
51
+ this._pda = pda;
52
+ this._bump = bump;
53
+ }
54
+ /**
55
+ * Create a CloakToken for owner management (freeze, unfreeze, update, close, withdraw, deposit)
56
+ * Does NOT have delegate keypair - cannot spend.
57
+ * Use this when you have the delegate's public key but not the token code.
58
+ *
59
+ * @param delegatePubkey - Public key of the delegate (from URL or on-chain data)
60
+ * @param rpcUrl - Solana RPC endpoint URL
61
+ */
62
+ static forOwner(delegatePubkey, rpcUrl) {
63
+ const pubkey = typeof delegatePubkey === "string"
64
+ ? new web3_js_1.PublicKey(delegatePubkey)
65
+ : delegatePubkey;
66
+ // Create instance without going through constructor
67
+ const instance = Object.create(CloakToken.prototype);
68
+ instance.keypair = null;
69
+ instance.delegatePubkey = pubkey;
70
+ instance.connection = new web3_js_1.Connection(rpcUrl, "confirmed");
71
+ instance._ownerCommitment = null;
72
+ instance._agentSecret = null;
73
+ instance._nonce = null;
74
+ const [pda, bump] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("token"), pubkey.toBuffer()], constants_1.CLOAK_PROGRAM_ID);
75
+ instance._pda = pda;
76
+ instance._bump = bump;
77
+ return instance;
78
+ }
79
+ /**
80
+ * Create a CloakToken for private owner management (ZK proof-based)
81
+ * Uses the master secret and nonce to derive the agent secret and find the token.
82
+ *
83
+ * @param masterSecret - Master secret derived from wallet signature
84
+ * @param nonce - Agent index (0, 1, 2, ...)
85
+ * @param rpcUrl - Solana RPC endpoint URL
86
+ * @returns CloakToken instance for private management
87
+ */
88
+ static async forPrivateOwner(masterSecret, nonce, rpcUrl) {
89
+ const { agentSecret, commitment } = await (0, zk_1.deriveAgentSecrets)(masterSecret, nonce);
90
+ const connection = new web3_js_1.Connection(rpcUrl, "confirmed");
91
+ // Find token by commitment
92
+ const found = await (0, zk_1.findTokenByCommitment)(commitment, connection);
93
+ if (!found) {
94
+ throw new Error(`No private agent found for nonce ${nonce}`);
95
+ }
96
+ // Create instance for private management
97
+ const instance = Object.create(CloakToken.prototype);
98
+ instance.keypair = null;
99
+ instance.delegatePubkey = found.delegate;
100
+ instance.connection = connection;
101
+ instance._ownerCommitment = (0, zk_1.commitmentToBytes)(commitment);
102
+ instance._agentSecret = agentSecret;
103
+ instance._nonce = nonce;
104
+ // Legacy PDA (seeds = ["token", delegate]) - kept for backward compatibility
105
+ const [pda, bump] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("token"), found.delegate.toBuffer()], constants_1.CLOAK_PROGRAM_ID);
106
+ instance._pda = pda;
107
+ instance._bump = bump;
108
+ // Cache tokenStatePda and vaultPda for private mode operations
109
+ const [tokenStatePda] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("token_state"), found.delegate.toBuffer()], constants_1.CLOAK_PROGRAM_ID);
110
+ instance._tokenStatePda = tokenStatePda;
111
+ const [vaultPda] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("vault"), tokenStatePda.toBuffer()], constants_1.CLOAK_PROGRAM_ID);
112
+ instance._vaultPda = vaultPda;
113
+ return instance;
114
+ }
115
+ /**
116
+ * Check if this token is in private mode
117
+ */
118
+ get isPrivateMode() {
119
+ return this._ownerCommitment !== null && this._agentSecret !== null;
120
+ }
121
+ /**
122
+ * Get the owner commitment (private mode only)
123
+ */
124
+ get ownerCommitment() {
125
+ return this._ownerCommitment;
126
+ }
127
+ /**
128
+ * The token's public key (used to derive PDA)
129
+ */
130
+ get publicKey() {
131
+ return this.delegatePubkey;
132
+ }
133
+ /**
134
+ * The PDA that holds this token's SOL balance
135
+ */
136
+ get pda() {
137
+ return this._pda;
138
+ }
139
+ /**
140
+ * The bump seed used for PDA derivation
141
+ */
142
+ get bump() {
143
+ return this._bump;
144
+ }
145
+ /**
146
+ * Get the TokenState PDA address for this delegate
147
+ */
148
+ get tokenStatePda() {
149
+ // Use cached value if available (set by forPrivateOwner)
150
+ if (this._tokenStatePda) {
151
+ return this._tokenStatePda;
152
+ }
153
+ const [pda] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("token_state"), this.delegatePubkey.toBuffer()], constants_1.CLOAK_PROGRAM_ID);
154
+ return pda;
155
+ }
156
+ /**
157
+ * Get the Vault PDA address for this token
158
+ */
159
+ get vaultPda() {
160
+ // Use cached value if available (set by forPrivateOwner)
161
+ if (this._vaultPda) {
162
+ return this._vaultPda;
163
+ }
164
+ const tokenStatePda = this.tokenStatePda;
165
+ const [vault] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("vault"), tokenStatePda.toBuffer()], constants_1.CLOAK_PROGRAM_ID);
166
+ return vault;
167
+ }
168
+ /**
169
+ * Get the current SOL balance of this token
170
+ * @returns Balance in SOL
171
+ */
172
+ async getBalance() {
173
+ const lamports = await this.connection.getBalance(this.vaultPda);
174
+ return lamports / web3_js_1.LAMPORTS_PER_SOL;
175
+ }
176
+ /**
177
+ * Get the current balance in lamports
178
+ * @returns Balance in lamports
179
+ */
180
+ async getBalanceLamports() {
181
+ return await this.connection.getBalance(this.vaultPda);
182
+ }
183
+ /**
184
+ * Generate a new random Cloak token
185
+ * @param rpcUrl - Solana RPC endpoint URL
186
+ * @returns New token instance and its token code
187
+ */
188
+ static generate(rpcUrl) {
189
+ const keypair = web3_js_1.Keypair.generate();
190
+ const tokenCode = bs58_1.default.encode(keypair.secretKey);
191
+ const token = new CloakToken(tokenCode, rpcUrl);
192
+ return { token, tokenCode };
193
+ }
194
+ /**
195
+ * Derive the PDA for any public key (without needing full token)
196
+ * @param publicKey - Token's public key
197
+ * @returns The PDA address
198
+ */
199
+ static derivePda(publicKey) {
200
+ const [pda] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("token"), publicKey.toBuffer()], constants_1.CLOAK_PROGRAM_ID);
201
+ return pda;
202
+ }
203
+ /**
204
+ * Derive PDA with bump
205
+ * @param publicKey - Token's public key
206
+ * @returns The PDA address and bump
207
+ */
208
+ static derivePdaWithBump(publicKey) {
209
+ return web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("token"), publicKey.toBuffer()], constants_1.CLOAK_PROGRAM_ID);
210
+ }
211
+ /**
212
+ * Derive TokenState PDA for a delegate
213
+ */
214
+ static deriveTokenStatePda(delegate) {
215
+ const [pda] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("token_state"), delegate.toBuffer()], constants_1.CLOAK_PROGRAM_ID);
216
+ return pda;
217
+ }
218
+ /**
219
+ * Derive Vault PDA from TokenState PDA
220
+ */
221
+ static deriveVaultPda(tokenStatePda) {
222
+ const [vault] = web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("vault"), tokenStatePda.toBuffer()], constants_1.CLOAK_PROGRAM_ID);
223
+ return vault;
224
+ }
225
+ /**
226
+ * Create a new Cloak token with constraints on-chain
227
+ * @param connection - Solana connection
228
+ * @param owner - Owner wallet (Signer - can be wallet adapter or wrapped Keypair)
229
+ * @param options - Token creation options
230
+ * @returns New CloakToken instance and token code
231
+ */
232
+ static async create(connection, owner, options) {
233
+ const rpcUrl = connection.rpcEndpoint;
234
+ // Generate new delegate keypair
235
+ const delegate = web3_js_1.Keypair.generate();
236
+ const tokenCode = bs58_1.default.encode(delegate.secretKey);
237
+ // Create program instance - Signer matches Wallet interface
238
+ const provider = new anchor_1.AnchorProvider(connection, owner, { commitment: "confirmed" });
239
+ const program = new anchor_1.Program(idl_json_1.default, provider);
240
+ // Convert expiration to unix timestamp (0 = never)
241
+ const expiresAt = options.expiresAt
242
+ ? Math.floor(options.expiresAt.getTime() / 1000)
243
+ : 0;
244
+ // Derive PDAs
245
+ const tokenStatePda = CloakToken.deriveTokenStatePda(delegate.publicKey);
246
+ const vaultPda = CloakToken.deriveVaultPda(tokenStatePda);
247
+ // Call create_token instruction
248
+ const signature = await program.methods
249
+ .createToken(new anchor_1.BN(options.maxPerTx ?? 0), new anchor_1.BN(options.dailyLimit ?? 0), new anchor_1.BN(options.totalLimit ?? 0), new anchor_1.BN(expiresAt))
250
+ .accounts({
251
+ tokenState: tokenStatePda,
252
+ vault: vaultPda,
253
+ owner: owner.publicKey,
254
+ delegate: delegate.publicKey,
255
+ payer: owner.publicKey,
256
+ systemProgram: web3_js_1.SystemProgram.programId,
257
+ })
258
+ .rpc();
259
+ // If initial deposit requested, deposit funds
260
+ if (options.initialDeposit && options.initialDeposit > 0) {
261
+ await program.methods
262
+ .deposit(new anchor_1.BN(options.initialDeposit))
263
+ .accounts({
264
+ tokenState: tokenStatePda,
265
+ vault: vaultPda,
266
+ depositor: owner.publicKey,
267
+ systemProgram: web3_js_1.SystemProgram.programId,
268
+ })
269
+ .rpc();
270
+ }
271
+ const token = new CloakToken(tokenCode, rpcUrl);
272
+ return { token, tokenCode, signature };
273
+ }
274
+ /**
275
+ * Fetch full token state from on-chain
276
+ * @returns TokenState with all constraints and spending info
277
+ */
278
+ async getState() {
279
+ // Create a dummy wallet for read-only operations
280
+ const dummyWallet = this.keypair
281
+ ? new anchor_1.Wallet(this.keypair)
282
+ : { publicKey: this.delegatePubkey, signTransaction: async (tx) => tx, signAllTransactions: async (txs) => txs };
283
+ const provider = new anchor_1.AnchorProvider(this.connection, dummyWallet, { commitment: "confirmed" });
284
+ const program = new anchor_1.Program(idl_json_1.default, provider);
285
+ const tokenStatePda = this.tokenStatePda;
286
+ const vaultPda = this.vaultPda;
287
+ // Fetch token state account
288
+ const state = await program.account.tokenState.fetch(tokenStatePda);
289
+ // Fetch vault balance
290
+ const balance = await this.connection.getBalance(vaultPda);
291
+ // Get current time for status check
292
+ const now = Math.floor(Date.now() / 1000);
293
+ const currentDay = Math.floor(now / SECONDS_PER_DAY);
294
+ const lastDay = state.lastDay.toNumber();
295
+ // Calculate daily remaining (reset if new day)
296
+ const dailySpent = currentDay > lastDay ? 0 : state.dailySpent.toNumber();
297
+ const dailyLimit = state.dailyLimit.toNumber();
298
+ const dailyRemaining = dailyLimit === 0 ? Number.MAX_SAFE_INTEGER : Math.max(0, dailyLimit - dailySpent);
299
+ // Calculate total remaining
300
+ const totalSpent = state.totalSpent.toNumber();
301
+ const totalLimit = state.totalLimit.toNumber();
302
+ const totalRemaining = totalLimit === 0 ? Number.MAX_SAFE_INTEGER : Math.max(0, totalLimit - totalSpent);
303
+ // Determine status
304
+ const expiresAtTs = state.expiresAt.toNumber();
305
+ const isExpired = expiresAtTs !== 0 && now > expiresAtTs;
306
+ const isFrozen = state.frozen;
307
+ let status;
308
+ if (isFrozen) {
309
+ status = "frozen";
310
+ }
311
+ else if (isExpired) {
312
+ status = "expired";
313
+ }
314
+ else {
315
+ status = "active";
316
+ }
317
+ // Handle Option<Pubkey> for owner - null if private mode
318
+ const owner = state.owner ? state.owner : null;
319
+ const ownerCommitment = new Uint8Array(state.ownerCommitment);
320
+ const isPrivate = owner === null;
321
+ return {
322
+ address: tokenStatePda,
323
+ owner,
324
+ ownerCommitment,
325
+ delegate: state.delegate,
326
+ balance,
327
+ constraints: {
328
+ maxPerTx: state.maxPerTx.toNumber(),
329
+ dailyLimit,
330
+ totalLimit,
331
+ expiresAt: expiresAtTs === 0 ? null : new Date(expiresAtTs * 1000),
332
+ frozen: isFrozen,
333
+ },
334
+ spending: {
335
+ totalSpent,
336
+ dailySpent,
337
+ dailyRemaining,
338
+ totalRemaining,
339
+ },
340
+ status,
341
+ createdAt: new Date(state.createdAt.toNumber() * 1000),
342
+ isPrivate,
343
+ };
344
+ }
345
+ /**
346
+ * Spend from vault to destination (delegate signs)
347
+ * Requires agent mode (token code) - throws if in owner mode.
348
+ *
349
+ * Two fee payment modes:
350
+ *
351
+ * 1. **With feePayer (standard mode)**: User's wallet pays tx fee directly
352
+ * - No relayer involved
353
+ * - Normal tx fee (~5k lamports) paid by user wallet
354
+ * - Vault only pays the amount to destination
355
+ *
356
+ * 2. **Without feePayer (agent/MCP mode)**: Relayer pays, vault reimburses
357
+ * - Relayer fronts the tx fee
358
+ * - Vault reimburses relayer 10k lamports
359
+ * - Delegate doesn't need any SOL
360
+ *
361
+ * @param options - Spend options (destination, amount, optional feePayer)
362
+ * @returns Spend result with signature and remaining balances
363
+ */
364
+ async spend(options) {
365
+ if (!this.keypair) {
366
+ throw new Error("Cannot spend in owner mode - requires token code");
367
+ }
368
+ const tokenStatePda = this.tokenStatePda;
369
+ const vaultPda = this.vaultPda;
370
+ // If feePayer provided, user pays directly (no relayer)
371
+ if (options.feePayer) {
372
+ return this.spendWithFeePayer(options, tokenStatePda, vaultPda);
373
+ }
374
+ // No feePayer - use relayer
375
+ return this.spendViaRelayer(options, tokenStatePda, vaultPda);
376
+ }
377
+ /**
378
+ * Spend with user-provided fee payer (no relayer)
379
+ */
380
+ async spendWithFeePayer(options, tokenStatePda, vaultPda) {
381
+ const feePayer = options.feePayer;
382
+ const provider = new anchor_1.AnchorProvider(this.connection, feePayer, { commitment: "confirmed" });
383
+ const program = new anchor_1.Program(idl_json_1.default, provider);
384
+ // Build the spend instruction
385
+ const spendIx = await program.methods
386
+ .spend(new anchor_1.BN(options.amount))
387
+ .accounts({
388
+ tokenState: tokenStatePda,
389
+ vault: vaultPda,
390
+ delegate: this.keypair.publicKey,
391
+ feePayer: feePayer.publicKey,
392
+ destination: options.destination,
393
+ systemProgram: web3_js_1.SystemProgram.programId,
394
+ })
395
+ .instruction();
396
+ // Build transaction with user's wallet as fee payer
397
+ const tx = new web3_js_1.Transaction();
398
+ tx.add(spendIx);
399
+ tx.feePayer = feePayer.publicKey;
400
+ tx.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash;
401
+ // Delegate signs
402
+ tx.partialSign(this.keypair);
403
+ // Fee payer signs and sends via provider
404
+ const signature = await provider.sendAndConfirm(tx, [this.keypair]);
405
+ // Fetch updated state for remaining balances
406
+ const state = await this.getState();
407
+ return {
408
+ signature,
409
+ remainingBalance: state.balance,
410
+ dailyRemaining: state.spending.dailyRemaining,
411
+ };
412
+ }
413
+ /**
414
+ * Spend via relayer (relayer pays, vault reimburses)
415
+ */
416
+ async spendViaRelayer(options, tokenStatePda, vaultPda) {
417
+ const apiUrl = process.env.CLOAK_BACKEND_URL || undefined;
418
+ // Get relayer public key to use as fee payer
419
+ const feePayerPubkey = await (0, relayer_1.getRelayerPublicKey)(apiUrl);
420
+ // Create a dummy provider just for building the instruction
421
+ const dummyProvider = new anchor_1.AnchorProvider(this.connection, new anchor_1.Wallet(this.keypair), { commitment: "confirmed" });
422
+ const program = new anchor_1.Program(idl_json_1.default, dummyProvider);
423
+ // Build the spend instruction
424
+ const spendIx = await program.methods
425
+ .spend(new anchor_1.BN(options.amount))
426
+ .accounts({
427
+ tokenState: tokenStatePda,
428
+ vault: vaultPda,
429
+ delegate: this.keypair.publicKey,
430
+ feePayer: feePayerPubkey,
431
+ destination: options.destination,
432
+ systemProgram: web3_js_1.SystemProgram.programId,
433
+ })
434
+ .instruction();
435
+ // Build transaction with relayer as fee payer
436
+ const tx = new web3_js_1.Transaction();
437
+ tx.add(spendIx);
438
+ tx.feePayer = feePayerPubkey;
439
+ tx.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash;
440
+ // Delegate signs the transaction
441
+ tx.partialSign(this.keypair);
442
+ // Serialize and send to relayer for co-signing
443
+ const serializedTx = tx.serialize({ requireAllSignatures: false });
444
+ const txBase64 = serializedTx.toString("base64");
445
+ const signature = await (0, relayer_1.cosignSpendViaRelayer)(txBase64, apiUrl);
446
+ // Fetch updated state for remaining balances
447
+ const state = await this.getState();
448
+ return {
449
+ signature,
450
+ remainingBalance: state.balance,
451
+ dailyRemaining: state.spending.dailyRemaining,
452
+ };
453
+ }
454
+ /**
455
+ * Deposit SOL to vault (anyone can call)
456
+ * @param depositor - Signer of the depositor (wallet adapter or wrapped Keypair)
457
+ * @param amount - Amount in lamports
458
+ * @returns Transaction signature
459
+ */
460
+ async deposit(depositor, amount) {
461
+ const provider = new anchor_1.AnchorProvider(this.connection, depositor, { commitment: "confirmed" });
462
+ const program = new anchor_1.Program(idl_json_1.default, provider);
463
+ const tokenStatePda = this.tokenStatePda;
464
+ const vaultPda = this.vaultPda;
465
+ const signature = await program.methods
466
+ .deposit(new anchor_1.BN(amount))
467
+ .accounts({
468
+ tokenState: tokenStatePda,
469
+ vault: vaultPda,
470
+ depositor: depositor.publicKey,
471
+ systemProgram: web3_js_1.SystemProgram.programId,
472
+ })
473
+ .rpc();
474
+ return signature;
475
+ }
476
+ /**
477
+ * Freeze token (owner only) - emergency stop
478
+ * @param owner - Owner signer (wallet adapter or wrapped Keypair)
479
+ * @returns Transaction signature
480
+ */
481
+ async freeze(owner) {
482
+ const provider = new anchor_1.AnchorProvider(this.connection, owner, { commitment: "confirmed" });
483
+ const program = new anchor_1.Program(idl_json_1.default, provider);
484
+ const tokenStatePda = this.tokenStatePda;
485
+ const signature = await program.methods
486
+ .freeze()
487
+ .accounts({
488
+ tokenState: tokenStatePda,
489
+ owner: owner.publicKey,
490
+ })
491
+ .rpc();
492
+ return signature;
493
+ }
494
+ /**
495
+ * Unfreeze token (owner only)
496
+ * @param owner - Owner signer (wallet adapter or wrapped Keypair)
497
+ * @returns Transaction signature
498
+ */
499
+ async unfreeze(owner) {
500
+ const provider = new anchor_1.AnchorProvider(this.connection, owner, { commitment: "confirmed" });
501
+ const program = new anchor_1.Program(idl_json_1.default, provider);
502
+ const tokenStatePda = this.tokenStatePda;
503
+ const signature = await program.methods
504
+ .unfreeze()
505
+ .accounts({
506
+ tokenState: tokenStatePda,
507
+ owner: owner.publicKey,
508
+ })
509
+ .rpc();
510
+ return signature;
511
+ }
512
+ /**
513
+ * Update token constraints (owner only)
514
+ * @param owner - Owner signer (wallet adapter or wrapped Keypair)
515
+ * @param options - New constraint values (null = no change)
516
+ * @returns Transaction signature
517
+ */
518
+ async updateConstraints(owner, options) {
519
+ const provider = new anchor_1.AnchorProvider(this.connection, owner, { commitment: "confirmed" });
520
+ const program = new anchor_1.Program(idl_json_1.default, provider);
521
+ const tokenStatePda = this.tokenStatePda;
522
+ // Convert options to BN with null for unchanged values
523
+ const maxPerTx = options.maxPerTx !== undefined ? new anchor_1.BN(options.maxPerTx) : null;
524
+ const dailyLimit = options.dailyLimit !== undefined ? new anchor_1.BN(options.dailyLimit) : null;
525
+ const totalLimit = options.totalLimit !== undefined ? new anchor_1.BN(options.totalLimit) : null;
526
+ const expiresAt = options.expiresAt !== undefined
527
+ ? new anchor_1.BN(options.expiresAt ? Math.floor(options.expiresAt.getTime() / 1000) : 0)
528
+ : null;
529
+ const signature = await program.methods
530
+ .updateConstraints(maxPerTx, dailyLimit, totalLimit, expiresAt)
531
+ .accounts({
532
+ tokenState: tokenStatePda,
533
+ owner: owner.publicKey,
534
+ })
535
+ .rpc();
536
+ return signature;
537
+ }
538
+ /**
539
+ * Close token and reclaim all funds to owner (owner only)
540
+ * @param owner - Owner signer (wallet adapter or wrapped Keypair)
541
+ * @returns Transaction signature
542
+ */
543
+ async close(owner) {
544
+ const provider = new anchor_1.AnchorProvider(this.connection, owner, { commitment: "confirmed" });
545
+ const program = new anchor_1.Program(idl_json_1.default, provider);
546
+ const tokenStatePda = this.tokenStatePda;
547
+ const vaultPda = this.vaultPda;
548
+ const signature = await program.methods
549
+ .closeToken()
550
+ .accounts({
551
+ tokenState: tokenStatePda,
552
+ vault: vaultPda,
553
+ owner: owner.publicKey,
554
+ systemProgram: web3_js_1.SystemProgram.programId,
555
+ })
556
+ .rpc();
557
+ return signature;
558
+ }
559
+ /**
560
+ * Withdraw from vault to any destination (owner only, no constraints)
561
+ * Works even if token is frozen or expired - owner has full control
562
+ * Preserves privacy by allowing withdrawal to any wallet
563
+ * @param owner - Owner signer (wallet adapter or wrapped Keypair)
564
+ * @param amount - Amount in lamports to withdraw
565
+ * @param destination - Destination wallet (any PublicKey)
566
+ * @returns Transaction signature
567
+ */
568
+ async withdraw(owner, amount, destination) {
569
+ const provider = new anchor_1.AnchorProvider(this.connection, owner, { commitment: "confirmed" });
570
+ const program = new anchor_1.Program(idl_json_1.default, provider);
571
+ const tokenStatePda = this.tokenStatePda;
572
+ const vaultPda = this.vaultPda;
573
+ const signature = await program.methods
574
+ .withdraw(new anchor_1.BN(amount))
575
+ .accounts({
576
+ tokenState: tokenStatePda,
577
+ vault: vaultPda,
578
+ owner: owner.publicKey,
579
+ destination: destination,
580
+ systemProgram: web3_js_1.SystemProgram.programId,
581
+ })
582
+ .rpc();
583
+ return signature;
584
+ }
585
+ // ============================================
586
+ // Private Mode Methods (ZK Proof-Based)
587
+ // ============================================
588
+ /**
589
+ * Create a new Cloak token in private mode (no wallet linked on-chain)
590
+ *
591
+ * @param connection - Solana connection
592
+ * @param payer - Payer for transaction fees (can be any signer)
593
+ * @param masterSecret - Master secret derived from wallet signature
594
+ * @param nonce - Agent index (0, 1, 2, ...)
595
+ * @param options - Token creation options
596
+ * @returns New CloakToken instance (with private mode) and token code
597
+ */
598
+ static async createPrivate(connection, payer, masterSecret, nonce, options) {
599
+ const rpcUrl = connection.rpcEndpoint;
600
+ // Derive commitment from master secret and nonce
601
+ const { agentSecret, commitment } = await (0, zk_1.deriveAgentSecrets)(masterSecret, nonce);
602
+ const commitmentBytes = (0, zk_1.commitmentToBytes)(commitment);
603
+ // Generate new delegate keypair
604
+ const delegate = web3_js_1.Keypair.generate();
605
+ const tokenCode = bs58_1.default.encode(delegate.secretKey);
606
+ // Create program instance
607
+ const provider = new anchor_1.AnchorProvider(connection, payer, { commitment: "confirmed" });
608
+ const program = new anchor_1.Program(idl_json_1.default, provider);
609
+ // Convert expiration to unix timestamp (0 = never)
610
+ const expiresAt = options.expiresAt
611
+ ? Math.floor(options.expiresAt.getTime() / 1000)
612
+ : 0;
613
+ // Derive PDAs
614
+ const tokenStatePda = CloakToken.deriveTokenStatePda(delegate.publicKey);
615
+ const vaultPda = CloakToken.deriveVaultPda(tokenStatePda);
616
+ // Call create_token_private instruction
617
+ const signature = await program.methods
618
+ .createTokenPrivate(Array.from(commitmentBytes), new anchor_1.BN(options.maxPerTx ?? 0), new anchor_1.BN(options.dailyLimit ?? 0), new anchor_1.BN(options.totalLimit ?? 0), new anchor_1.BN(expiresAt))
619
+ .accounts({
620
+ tokenState: tokenStatePda,
621
+ vault: vaultPda,
622
+ delegate: delegate.publicKey,
623
+ payer: payer.publicKey,
624
+ systemProgram: web3_js_1.SystemProgram.programId,
625
+ })
626
+ .rpc();
627
+ // If initial deposit requested, deposit funds
628
+ if (options.initialDeposit && options.initialDeposit > 0) {
629
+ await program.methods
630
+ .deposit(new anchor_1.BN(options.initialDeposit))
631
+ .accounts({
632
+ tokenState: tokenStatePda,
633
+ vault: vaultPda,
634
+ depositor: payer.publicKey,
635
+ systemProgram: web3_js_1.SystemProgram.programId,
636
+ })
637
+ .rpc();
638
+ }
639
+ // Create token instance with private mode
640
+ const token = new CloakToken(tokenCode, rpcUrl);
641
+ token._ownerCommitment = commitmentBytes;
642
+ token._agentSecret = agentSecret;
643
+ token._nonce = nonce;
644
+ return { token, tokenCode, signature };
645
+ }
646
+ /**
647
+ * Create a new Cloak token via relayer (truly private - user wallet never signs on-chain)
648
+ *
649
+ * This is the most private mode of operation:
650
+ * 1. User signs message to derive master secret (client-side only)
651
+ * 2. User sends total (fee + funding) to relayer via Privacy Cash
652
+ * 3. Relayer keeps 0.01 SOL fee, forwards rest to vault
653
+ * 4. Relayer creates the agent on-chain (generates delegate)
654
+ * 5. TokenCode encrypted for user (only user can decrypt)
655
+ * 6. User's wallet NEVER appears in any on-chain transaction
656
+ *
657
+ * @param masterSecret - Master secret derived from wallet signature
658
+ * @param nonce - Agent index (0, 1, 2, ...)
659
+ * @param options - Token creation options
660
+ * @param depositSignature - Privacy Cash tx signature to relayer
661
+ * @param depositAmount - Total lamports sent (fee + vault funding)
662
+ * @param rpcUrl - Solana RPC endpoint URL
663
+ * @param apiUrl - Optional backend API URL
664
+ * @returns New CloakToken instance (with private mode) and token code
665
+ */
666
+ static async createPrivateViaRelayer(masterSecret, nonce, options, depositSignature, depositAmount, rpcUrl, apiUrl) {
667
+ // Call relayer API
668
+ const result = await (0, relayer_1.createPrivateAgentViaRelayer)(masterSecret, nonce, {
669
+ maxPerTx: options.maxPerTx,
670
+ dailyLimit: options.dailyLimit,
671
+ totalLimit: options.totalLimit,
672
+ expiresAt: options.expiresAt,
673
+ }, depositSignature, depositAmount, apiUrl);
674
+ // Derive agent secret for private mode operations
675
+ const { agentSecret, commitment } = await (0, zk_1.deriveAgentSecrets)(masterSecret, nonce);
676
+ const commitmentBytes = (0, zk_1.commitmentToBytes)(commitment);
677
+ // Create token instance with private mode
678
+ const token = new CloakToken(result.tokenCode, rpcUrl);
679
+ token._ownerCommitment = commitmentBytes;
680
+ token._agentSecret = agentSecret;
681
+ token._nonce = nonce;
682
+ return {
683
+ token,
684
+ tokenCode: result.tokenCode,
685
+ signature: result.signature,
686
+ vaultPda: result.vaultPda,
687
+ };
688
+ }
689
+ /**
690
+ * Create a new Cloak token using your own relayer keypair (no backend needed, no fees)
691
+ *
692
+ * This is for technical users who want to:
693
+ * - Use their own funded keypair as a relayer
694
+ * - Pay their own rent (no 0.01 SOL fee to our relayer)
695
+ * - Have full control over the creation process
696
+ * - Fund vault separately via Privacy Cash for anonymity
697
+ *
698
+ * Flow:
699
+ * 1. User signs message to derive master secret (client-side only)
700
+ * 2. User provides a funded Keypair to pay rent
701
+ * 3. Creates agent directly on-chain (relayerKeypair signs and pays)
702
+ * 4. User's main wallet NEVER appears in any on-chain transaction
703
+ * 5. Fund vault separately via Privacy Cash for complete anonymity
704
+ *
705
+ * @param masterSecret - Master secret derived from wallet signature
706
+ * @param nonce - Agent index (0, 1, 2, ...)
707
+ * @param options - Token creation options
708
+ * @param relayerKeypair - User's own funded keypair (pays rent ~0.00138 SOL)
709
+ * @param rpcUrl - Solana RPC endpoint URL
710
+ * @returns New CloakToken instance (with private mode) and token code
711
+ */
712
+ static async createPrivateWithRelayer(masterSecret, nonce, options, relayerKeypair, rpcUrl) {
713
+ const connection = new web3_js_1.Connection(rpcUrl, "confirmed");
714
+ // Derive commitment from master secret and nonce
715
+ const { agentSecret, commitment } = await (0, zk_1.deriveAgentSecrets)(masterSecret, nonce);
716
+ const commitmentBytes = (0, zk_1.commitmentToBytes)(commitment);
717
+ // Generate new delegate keypair
718
+ const delegate = web3_js_1.Keypair.generate();
719
+ const tokenCode = bs58_1.default.encode(delegate.secretKey);
720
+ // Create program instance with relayer as payer
721
+ const provider = new anchor_1.AnchorProvider(connection, new anchor_1.Wallet(relayerKeypair), { commitment: "confirmed" });
722
+ const program = new anchor_1.Program(idl_json_1.default, provider);
723
+ // Convert expiration to unix timestamp (0 = never)
724
+ const expiresAt = options.expiresAt
725
+ ? Math.floor(options.expiresAt.getTime() / 1000)
726
+ : 0;
727
+ // Derive PDAs
728
+ const tokenStatePda = CloakToken.deriveTokenStatePda(delegate.publicKey);
729
+ const vaultPda = CloakToken.deriveVaultPda(tokenStatePda);
730
+ // Call create_token_private instruction
731
+ const signature = await program.methods
732
+ .createTokenPrivate(Array.from(commitmentBytes), new anchor_1.BN(options.maxPerTx ?? 0), new anchor_1.BN(options.dailyLimit ?? 0), new anchor_1.BN(options.totalLimit ?? 0), new anchor_1.BN(expiresAt))
733
+ .accounts({
734
+ tokenState: tokenStatePda,
735
+ vault: vaultPda,
736
+ delegate: delegate.publicKey,
737
+ payer: relayerKeypair.publicKey,
738
+ systemProgram: web3_js_1.SystemProgram.programId,
739
+ })
740
+ .rpc();
741
+ // Create token instance with private mode
742
+ const token = new CloakToken(tokenCode, rpcUrl);
743
+ token._ownerCommitment = commitmentBytes;
744
+ token._agentSecret = agentSecret;
745
+ token._nonce = nonce;
746
+ return {
747
+ token,
748
+ tokenCode,
749
+ signature,
750
+ vaultPda,
751
+ };
752
+ }
753
+ /**
754
+ * Helper to get a dummy wallet for read-only operations
755
+ */
756
+ getDummyWallet() {
757
+ if (this.keypair) {
758
+ return new anchor_1.Wallet(this.keypair);
759
+ }
760
+ return {
761
+ publicKey: this.delegatePubkey,
762
+ signTransaction: async (tx) => tx,
763
+ signAllTransactions: async (txs) => txs,
764
+ };
765
+ }
766
+ /**
767
+ * Freeze token using ZK proof (private mode only)
768
+ * Uses relayer to submit transaction - vault pays for fees
769
+ * @param apiUrl - Optional API URL for relayer
770
+ * @returns Transaction signature
771
+ */
772
+ async freezePrivate(apiUrl) {
773
+ if (!this._agentSecret || !this._ownerCommitment) {
774
+ throw new Error("Not in private mode - use freeze() with owner signer instead");
775
+ }
776
+ if (!(0, zk_1.isProverReady)()) {
777
+ throw new Error("ZK prover not initialized. Call initProver() first.");
778
+ }
779
+ const commitment = (0, zk_1.bytesToCommitment)(this._ownerCommitment);
780
+ const proof = await (0, zk_1.generateOwnershipProof)(this._agentSecret, commitment);
781
+ const proofArgs = (0, zk_1.proofToInstructionArgs)(proof);
782
+ const signature = await (0, relayer_1.freezePrivateViaRelayer)({
783
+ tokenStatePda: this.tokenStatePda.toBase58(),
784
+ proofBytes: Array.from(proofArgs.proofBytes),
785
+ witnessBytes: Array.from(proofArgs.witnessBytes),
786
+ }, apiUrl);
787
+ return signature;
788
+ }
789
+ /**
790
+ * Unfreeze token using ZK proof (private mode only)
791
+ * Uses relayer to submit transaction - vault pays for fees
792
+ * @param apiUrl - Optional API URL for relayer
793
+ * @returns Transaction signature
794
+ */
795
+ async unfreezePrivate(apiUrl) {
796
+ if (!this._agentSecret || !this._ownerCommitment) {
797
+ throw new Error("Not in private mode - use unfreeze() with owner signer instead");
798
+ }
799
+ if (!(0, zk_1.isProverReady)()) {
800
+ throw new Error("ZK prover not initialized. Call initProver() first.");
801
+ }
802
+ const commitment = (0, zk_1.bytesToCommitment)(this._ownerCommitment);
803
+ const proof = await (0, zk_1.generateOwnershipProof)(this._agentSecret, commitment);
804
+ const proofArgs = (0, zk_1.proofToInstructionArgs)(proof);
805
+ const signature = await (0, relayer_1.unfreezePrivateViaRelayer)({
806
+ tokenStatePda: this.tokenStatePda.toBase58(),
807
+ proofBytes: Array.from(proofArgs.proofBytes),
808
+ witnessBytes: Array.from(proofArgs.witnessBytes),
809
+ }, apiUrl);
810
+ return signature;
811
+ }
812
+ /**
813
+ * Update constraints using ZK proof (private mode only)
814
+ * Uses relayer to submit transaction - vault pays for fees
815
+ * @param options - New constraint values (undefined = no change)
816
+ * @param apiUrl - Optional API URL for relayer
817
+ * @returns Transaction signature
818
+ */
819
+ async updateConstraintsPrivate(options, apiUrl) {
820
+ if (!this._agentSecret || !this._ownerCommitment) {
821
+ throw new Error("Not in private mode - use updateConstraints() with owner signer instead");
822
+ }
823
+ if (!(0, zk_1.isProverReady)()) {
824
+ throw new Error("ZK prover not initialized. Call initProver() first.");
825
+ }
826
+ const commitment = (0, zk_1.bytesToCommitment)(this._ownerCommitment);
827
+ const proof = await (0, zk_1.generateOwnershipProof)(this._agentSecret, commitment);
828
+ const proofArgs = (0, zk_1.proofToInstructionArgs)(proof);
829
+ const signature = await (0, relayer_1.updateConstraintsPrivateViaRelayer)({
830
+ tokenStatePda: this.tokenStatePda.toBase58(),
831
+ proofBytes: Array.from(proofArgs.proofBytes),
832
+ witnessBytes: Array.from(proofArgs.witnessBytes),
833
+ maxPerTx: options.maxPerTx !== undefined ? options.maxPerTx : null,
834
+ dailyLimit: options.dailyLimit !== undefined ? options.dailyLimit : null,
835
+ totalLimit: options.totalLimit !== undefined ? options.totalLimit : null,
836
+ expiresAt: options.expiresAt !== undefined
837
+ ? (options.expiresAt ? Math.floor(options.expiresAt.getTime() / 1000) : 0)
838
+ : null,
839
+ }, apiUrl);
840
+ return signature;
841
+ }
842
+ /**
843
+ * Close token and reclaim funds using ZK proof (private mode only)
844
+ * Uses relayer to submit transaction - vault pays for fees
845
+ * @param destination - Destination for remaining funds
846
+ * @param apiUrl - Optional API URL for relayer
847
+ * @returns Transaction signature
848
+ */
849
+ async closePrivate(destination, apiUrl) {
850
+ if (!this._agentSecret || !this._ownerCommitment) {
851
+ throw new Error("Not in private mode - use close() with owner signer instead");
852
+ }
853
+ if (!(0, zk_1.isProverReady)()) {
854
+ throw new Error("ZK prover not initialized. Call initProver() first.");
855
+ }
856
+ const commitment = (0, zk_1.bytesToCommitment)(this._ownerCommitment);
857
+ const proof = await (0, zk_1.generateOwnershipProof)(this._agentSecret, commitment);
858
+ const proofArgs = (0, zk_1.proofToInstructionArgs)(proof);
859
+ const signature = await (0, relayer_1.closePrivateViaRelayer)({
860
+ tokenStatePda: this.tokenStatePda.toBase58(),
861
+ proofBytes: Array.from(proofArgs.proofBytes),
862
+ witnessBytes: Array.from(proofArgs.witnessBytes),
863
+ destination: destination.toBase58(),
864
+ }, apiUrl);
865
+ return signature;
866
+ }
867
+ /**
868
+ * Withdraw using ZK proof (private mode only)
869
+ * Uses relayer to submit transaction - vault pays for fees
870
+ * @param amount - Amount in lamports to withdraw
871
+ * @param destination - Destination for funds
872
+ * @param apiUrl - Optional API URL for relayer
873
+ * @returns Transaction signature
874
+ */
875
+ async withdrawPrivate(amount, destination, apiUrl) {
876
+ if (!this._agentSecret || !this._ownerCommitment) {
877
+ throw new Error("Not in private mode - use withdraw() with owner signer instead");
878
+ }
879
+ if (!(0, zk_1.isProverReady)()) {
880
+ throw new Error("ZK prover not initialized. Call initProver() first.");
881
+ }
882
+ const commitment = (0, zk_1.bytesToCommitment)(this._ownerCommitment);
883
+ const proof = await (0, zk_1.generateOwnershipProof)(this._agentSecret, commitment);
884
+ const proofArgs = (0, zk_1.proofToInstructionArgs)(proof);
885
+ const signature = await (0, relayer_1.withdrawPrivateViaRelayer)({
886
+ tokenStatePda: this.tokenStatePda.toBase58(),
887
+ proofBytes: Array.from(proofArgs.proofBytes),
888
+ witnessBytes: Array.from(proofArgs.witnessBytes),
889
+ amount,
890
+ destination: destination.toBase58(),
891
+ }, apiUrl);
892
+ return signature;
893
+ }
894
+ }
895
+ exports.CloakToken = CloakToken;
896
+ //# sourceMappingURL=token.js.map