@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.
- package/README.md +90 -0
- package/dist/agent.d.ts +321 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +877 -0
- package/dist/agent.js.map +1 -0
- package/dist/config.d.ts +33 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +64 -0
- package/dist/config.js.map +1 -0
- package/dist/constants.d.ts +4 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +9 -0
- package/dist/constants.js.map +1 -0
- package/dist/idl.json +1347 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +66 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/index.d.ts +7 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +374 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/tools.d.ts +26 -0
- package/dist/mcp/tools.d.ts.map +1 -0
- package/dist/mcp/tools.js +320 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/mcp/types.d.ts +61 -0
- package/dist/mcp/types.d.ts.map +1 -0
- package/dist/mcp/types.js +4 -0
- package/dist/mcp/types.js.map +1 -0
- package/dist/relayer.d.ts +130 -0
- package/dist/relayer.d.ts.map +1 -0
- package/dist/relayer.js +225 -0
- package/dist/relayer.js.map +1 -0
- package/dist/signer.d.ts +18 -0
- package/dist/signer.d.ts.map +1 -0
- package/dist/signer.js +34 -0
- package/dist/signer.js.map +1 -0
- package/dist/token.d.ts +320 -0
- package/dist/token.d.ts.map +1 -0
- package/dist/token.js +896 -0
- package/dist/token.js.map +1 -0
- package/dist/types.d.ts +66 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/zk/browser-prover.d.ts +85 -0
- package/dist/zk/browser-prover.d.ts.map +1 -0
- package/dist/zk/browser-prover.js +260 -0
- package/dist/zk/browser-prover.js.map +1 -0
- package/dist/zk/discovery.d.ts +65 -0
- package/dist/zk/discovery.d.ts.map +1 -0
- package/dist/zk/discovery.js +143 -0
- package/dist/zk/discovery.js.map +1 -0
- package/dist/zk/index.d.ts +14 -0
- package/dist/zk/index.d.ts.map +1 -0
- package/dist/zk/index.js +47 -0
- package/dist/zk/index.js.map +1 -0
- package/dist/zk/ownership_proof.json +1 -0
- package/dist/zk/poseidon.d.ts +31 -0
- package/dist/zk/poseidon.d.ts.map +1 -0
- package/dist/zk/poseidon.js +103 -0
- package/dist/zk/poseidon.js.map +1 -0
- package/dist/zk/prover.d.ts +49 -0
- package/dist/zk/prover.d.ts.map +1 -0
- package/dist/zk/prover.js +120 -0
- package/dist/zk/prover.js.map +1 -0
- package/dist/zk/secrets.d.ts +62 -0
- package/dist/zk/secrets.d.ts.map +1 -0
- package/dist/zk/secrets.js +98 -0
- package/dist/zk/secrets.js.map +1 -0
- 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
|