@aintivirus-ai/mixer-sdk 1.0.0 → 1.0.1

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.
@@ -32,28 +32,71 @@ var __importStar = (this && this.__importStar) || (function () {
32
32
  return result;
33
33
  };
34
34
  })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
35
38
  Object.defineProperty(exports, "__esModule", { value: true });
36
39
  exports.AintiVirusSolana = void 0;
37
40
  const anchor = __importStar(require("@coral-xyz/anchor"));
38
41
  const web3_js_1 = require("@solana/web3.js");
39
42
  const spl_token_1 = require("@solana/spl-token");
40
43
  const types_1 = require("../types");
44
+ const aintivirus_factory_json_1 = __importDefault(require("./idl/aintivirus_factory.json"));
45
+ const aintivirus_mixer_json_1 = __importDefault(require("./idl/aintivirus_mixer.json"));
46
+ const aintivirus_staking_json_1 = __importDefault(require("./idl/aintivirus_staking.json"));
41
47
  /**
42
48
  * Solana SDK for AintiVirus Mixer
43
49
  */
44
50
  class AintiVirusSolana {
45
- constructor(factoryProgramId, mixerProgramId, stakingProgramId, wallet, connection, tokenMint) {
51
+ constructor(wallet, connection, tokenMint) {
46
52
  this.connection = connection;
47
53
  this.wallet = wallet;
48
- // Initialize programs (using any type for IDL flexibility)
49
- this.factoryProgram = new anchor.Program({}, // IDL should be loaded separately
50
- new web3_js_1.PublicKey(factoryProgramId), new anchor.AnchorProvider(connection, wallet, {}));
51
- this.mixerProgram = new anchor.Program({}, new web3_js_1.PublicKey(mixerProgramId), new anchor.AnchorProvider(connection, wallet, {}));
52
- this.stakingProgram = new anchor.Program({}, new web3_js_1.PublicKey(stakingProgramId), new anchor.AnchorProvider(connection, wallet, {}));
54
+ const provider = new anchor.AnchorProvider(connection, wallet, {
55
+ commitment: "confirmed",
56
+ });
57
+ // Set the provider on Anchor so it's available globally
58
+ anchor.setProvider(provider);
59
+ // Import IDL files directly
60
+ // Ensure metadata.address is set for Anchor 0.32.1
61
+ const factoryIdl = this.ensureIdlAddress(aintivirus_factory_json_1.default);
62
+ const mixerIdl = this.ensureIdlAddress(aintivirus_mixer_json_1.default);
63
+ const stakingIdl = this.ensureIdlAddress(aintivirus_staking_json_1.default);
64
+ // Initialize programs with IDLs
65
+ this.factoryProgram = new anchor.Program(factoryIdl, provider);
66
+ this.mixerProgram = new anchor.Program(mixerIdl, provider);
67
+ this.stakingProgram = new anchor.Program(stakingIdl, provider);
53
68
  if (tokenMint) {
54
69
  this.tokenMint = new web3_js_1.PublicKey(tokenMint);
55
70
  }
56
71
  }
72
+ /**
73
+ * Ensure IDL has address in metadata.address for Anchor 0.32.1
74
+ */
75
+ ensureIdlAddress(idlJson) {
76
+ // If metadata.address already exists, return as-is
77
+ if (idlJson.metadata?.address) {
78
+ return idlJson;
79
+ }
80
+ // Get address from top level or metadata
81
+ const programAddress = idlJson.address || idlJson.metadata?.address;
82
+ if (!programAddress) {
83
+ throw new Error(`IDL missing program address. Expected 'address' field at top level or in metadata.`);
84
+ }
85
+ // Ensure metadata.address is set
86
+ return {
87
+ ...idlJson,
88
+ metadata: {
89
+ ...(idlJson.metadata || {}),
90
+ address: programAddress,
91
+ },
92
+ };
93
+ }
94
+ /**
95
+ * Set token mint address (can be called after initialization)
96
+ */
97
+ setTokenMint(tokenMint) {
98
+ this.tokenMint = tokenMint;
99
+ }
57
100
  /**
58
101
  * Get factory PDA
59
102
  */
@@ -82,6 +125,75 @@ class AintiVirusSolana {
82
125
  getMerkleTreePda(mixerConfigPda) {
83
126
  return web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("merkle_tree"), mixerConfigPda.toBuffer()], this.mixerProgram.programId);
84
127
  }
128
+ /**
129
+ * Get vault SOL PDA
130
+ */
131
+ getVaultSolPda() {
132
+ return web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("vault"), Buffer.from("sol")], this.factoryProgram.programId);
133
+ }
134
+ /**
135
+ * Get staking PDA
136
+ */
137
+ getStakingPda() {
138
+ return web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("staking")], this.stakingProgram.programId);
139
+ }
140
+ /**
141
+ * Get stake season PDA
142
+ */
143
+ getStakeSeasonPda(seasonId) {
144
+ const seasonIdBuffer = Buffer.allocUnsafe(8);
145
+ seasonIdBuffer.writeBigUInt64LE(seasonId, 0);
146
+ return web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("stake_season"), seasonIdBuffer], this.stakingProgram.programId);
147
+ }
148
+ /**
149
+ * Get staker record PDA
150
+ */
151
+ getStakerRecordPda(staker) {
152
+ return web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("staker_record"), staker.toBuffer()], this.stakingProgram.programId);
153
+ }
154
+ /**
155
+ * Get season claimed PDA
156
+ */
157
+ getSeasonClaimedPda(staker, seasonId, mode = types_1.AssetMode.SOL) {
158
+ const seasonIdBuffer = Buffer.allocUnsafe(8);
159
+ seasonIdBuffer.writeBigUInt64LE(seasonId, 0);
160
+ return web3_js_1.PublicKey.findProgramAddressSync([
161
+ Buffer.from("season_claimed"),
162
+ staker.toBuffer(),
163
+ seasonIdBuffer,
164
+ Buffer.from([mode]),
165
+ ], this.stakingProgram.programId);
166
+ }
167
+ /**
168
+ * Check if an address has claimed rewards for a season
169
+ * @param address - Staker's public key
170
+ * @param seasonId - Season ID to check
171
+ * @param mode - Asset mode (SOL or TOKEN)
172
+ * @returns true if the address has claimed rewards for the season
173
+ */
174
+ async hasClaimed(address, seasonId, mode) {
175
+ const stakerPubkey = new web3_js_1.PublicKey(address);
176
+ const [claimPda] = this.getSeasonClaimedPda(stakerPubkey, seasonId, mode);
177
+ try {
178
+ await this.stakingProgram.account.seasonClaimed.fetch(claimPda);
179
+ return true;
180
+ }
181
+ catch {
182
+ return false;
183
+ }
184
+ }
185
+ /**
186
+ * Get commitment checker PDA
187
+ */
188
+ getCommitmentCheckerPda(commitment) {
189
+ return web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("commitment"), commitment], this.mixerProgram.programId);
190
+ }
191
+ /**
192
+ * Get nullifier hash checker PDA
193
+ */
194
+ getNullifierHashCheckerPda(nullifierHash) {
195
+ return web3_js_1.PublicKey.findProgramAddressSync([Buffer.from("nullifier_hash"), nullifierHash], this.mixerProgram.programId);
196
+ }
85
197
  /**
86
198
  * Get mixer address for a specific mode and amount
87
199
  */
@@ -118,35 +230,51 @@ class AintiVirusSolana {
118
230
  */
119
231
  async depositSol(amount, commitment) {
120
232
  const [factoryPda] = this.getFactoryPda();
121
- const [mixerPoolPda] = this.getMixerPoolPda(types_1.AssetMode.ETH, amount);
122
- const [mixerConfigPda] = this.getMixerConfigPda(types_1.AssetMode.ETH, amount);
233
+ const [mixerPoolPda] = this.getMixerPoolPda(types_1.AssetMode.SOL, amount);
234
+ const [mixerConfigPda] = this.getMixerConfigPda(types_1.AssetMode.SOL, amount);
123
235
  const [merkleTreePda] = this.getMerkleTreePda(mixerConfigPda);
124
- const commitmentBytes = Buffer.allocUnsafe(32);
125
- const commitmentBigInt = commitment;
126
- commitmentBytes.writeBigUInt64LE(commitmentBigInt & BigInt("0xFFFFFFFFFFFFFFFF"), 0);
127
- commitmentBytes.writeBigUInt64LE((commitmentBigInt >> 64n) & BigInt("0xFFFFFFFFFFFFFFFF"), 8);
128
- commitmentBytes.writeBigUInt64LE((commitmentBigInt >> 128n) & BigInt("0xFFFFFFFFFFFFFFFF"), 16);
129
- commitmentBytes.writeBigUInt64LE((commitmentBigInt >> 192n) & BigInt("0xFFFFFFFFFFFFFFFF"), 24);
130
- const tx = await this.factoryProgram.methods
131
- .deposit(types_1.AssetMode.ETH, new anchor.BN(amount.toString()), Array.from(commitmentBytes))
132
- .accounts({
133
- factory: factoryPda,
134
- payer: this.wallet.publicKey,
135
- mixerProgram: this.mixerProgram.programId,
136
- mixerPool: mixerPoolPda,
137
- mixerConfig: mixerConfigPda,
138
- merkleTree: merkleTreePda,
139
- systemProgram: web3_js_1.SystemProgram.programId,
140
- })
141
- .rpc();
142
- const signature = tx;
143
- const txDetails = await this.connection.getTransaction(signature, {
144
- commitment: "confirmed",
145
- });
146
- return {
147
- txHash: signature,
148
- blockTime: txDetails?.blockTime ?? undefined,
149
- };
236
+ // Convert commitment bigint to 32-byte buffer (same as reference implementation)
237
+ const commitmentHex = commitment.toString(16).padStart(64, "0");
238
+ const commitmentBytes = Buffer.from(commitmentHex, "hex");
239
+ const [commitmentCheckerPda] = this.getCommitmentCheckerPda(commitmentBytes);
240
+ // Get token mint from factory account
241
+ const factoryAccount = await this.factoryProgram.account.factory.fetch(factoryPda);
242
+ const tokenMint = new web3_js_1.PublicKey(factoryAccount.mint);
243
+ const vaultTokenAccount = (0, spl_token_1.getAssociatedTokenAddressSync)(tokenMint, factoryPda, true);
244
+ try {
245
+ const commitmentArray = Array.from(commitmentBytes);
246
+ const tx = await this.factoryProgram.methods
247
+ .deposit(types_1.AssetMode.SOL, new anchor.BN(amount.toString()), commitmentArray)
248
+ .accounts({
249
+ commitmentChecker: commitmentCheckerPda,
250
+ merkleTree: merkleTreePda,
251
+ mixerConfig: mixerConfigPda,
252
+ mixerPool: mixerPoolPda,
253
+ userTokenAccount: null,
254
+ vaultTokenAccount: vaultTokenAccount,
255
+ user: this.wallet.publicKey,
256
+ })
257
+ .rpc();
258
+ const signature = tx;
259
+ const txDetails = await this.connection.getTransaction(signature, {
260
+ commitment: "confirmed",
261
+ });
262
+ return {
263
+ txHash: signature,
264
+ blockTime: txDetails?.blockTime ?? undefined,
265
+ };
266
+ }
267
+ catch (error) {
268
+ const errorMsg = error?.message || String(error);
269
+ console.error("[depositSol] ✗ Error preparing/sending transaction:", errorMsg);
270
+ if (error?.stack) {
271
+ console.error("[depositSol] Stack trace:", error.stack);
272
+ }
273
+ if (error?.logs) {
274
+ console.error("[depositSol] Error logs:", error.logs);
275
+ }
276
+ throw error;
277
+ }
150
278
  }
151
279
  /**
152
280
  * Deposit tokens into the mixer
@@ -160,28 +288,22 @@ class AintiVirusSolana {
160
288
  const [mixerConfigPda] = this.getMixerConfigPda(types_1.AssetMode.TOKEN, amount);
161
289
  const [merkleTreePda] = this.getMerkleTreePda(mixerConfigPda);
162
290
  const userTokenAccount = await (0, spl_token_1.getAssociatedTokenAddress)(this.tokenMint, this.wallet.publicKey);
163
- const factoryTokenAccount = await (0, spl_token_1.getAssociatedTokenAddress)(this.tokenMint, factoryPda, true // allowOwnerOffCurve
291
+ const vaultTokenAccount = await (0, spl_token_1.getAssociatedTokenAddress)(this.tokenMint, factoryPda, true // allowOwnerOffCurve
164
292
  );
165
- const commitmentBytes = Buffer.allocUnsafe(32);
166
- const commitmentBigInt = commitment;
167
- commitmentBytes.writeBigUInt64LE(commitmentBigInt & BigInt("0xFFFFFFFFFFFFFFFF"), 0);
168
- commitmentBytes.writeBigUInt64LE((commitmentBigInt >> 64n) & BigInt("0xFFFFFFFFFFFFFFFF"), 8);
169
- commitmentBytes.writeBigUInt64LE((commitmentBigInt >> 128n) & BigInt("0xFFFFFFFFFFFFFFFF"), 16);
170
- commitmentBytes.writeBigUInt64LE((commitmentBigInt >> 192n) & BigInt("0xFFFFFFFFFFFFFFFF"), 24);
293
+ // Convert commitment bigint to 32-byte buffer (same as reference implementation)
294
+ const commitmentHex = commitment.toString(16).padStart(64, "0");
295
+ const commitmentBytes = Buffer.from(commitmentHex, "hex");
296
+ const [commitmentCheckerPda] = this.getCommitmentCheckerPda(commitmentBytes);
171
297
  const tx = await this.factoryProgram.methods
172
298
  .deposit(types_1.AssetMode.TOKEN, new anchor.BN(amount.toString()), Array.from(commitmentBytes))
173
299
  .accounts({
174
- factory: factoryPda,
175
- payer: this.wallet.publicKey,
176
- mixerProgram: this.mixerProgram.programId,
300
+ vaultTokenAccount: vaultTokenAccount,
177
301
  mixerPool: mixerPoolPda,
178
302
  mixerConfig: mixerConfigPda,
179
303
  merkleTree: merkleTreePda,
180
- tokenMint: this.tokenMint,
304
+ commitmentChecker: commitmentCheckerPda,
305
+ user: this.wallet.publicKey,
181
306
  userTokenAccount: userTokenAccount,
182
- factoryTokenAccount: factoryTokenAccount,
183
- tokenProgram: spl_token_1.TOKEN_PROGRAM_ID,
184
- systemProgram: web3_js_1.SystemProgram.programId,
185
307
  })
186
308
  .rpc();
187
309
  const signature = tx;
@@ -196,37 +318,36 @@ class AintiVirusSolana {
196
318
  /**
197
319
  * Withdraw from the mixer
198
320
  */
199
- async withdraw(instructionData, nullifierHash, amount, mode) {
321
+ async withdraw(instructionData, nullifierHash, amount, mode, recipient) {
200
322
  const [factoryPda] = this.getFactoryPda();
323
+ const [vaultSolPda] = this.getVaultSolPda();
201
324
  const [mixerPoolPda] = this.getMixerPoolPda(mode, amount);
202
325
  const [mixerConfigPda] = this.getMixerConfigPda(mode, amount);
203
326
  const [merkleTreePda] = this.getMerkleTreePda(mixerConfigPda);
204
- const nullifierHashBytes = Buffer.allocUnsafe(32);
205
- const nullifierHashBigInt = nullifierHash;
206
- nullifierHashBytes.writeBigUInt64LE(nullifierHashBigInt & BigInt("0xFFFFFFFFFFFFFFFF"), 0);
207
- nullifierHashBytes.writeBigUInt64LE((nullifierHashBigInt >> 64n) & BigInt("0xFFFFFFFFFFFFFFFF"), 8);
208
- nullifierHashBytes.writeBigUInt64LE((nullifierHashBigInt >> 128n) & BigInt("0xFFFFFFFFFFFFFFFF"), 16);
209
- nullifierHashBytes.writeBigUInt64LE((nullifierHashBigInt >> 192n) & BigInt("0xFFFFFFFFFFFFFFFF"), 24);
210
- const accounts = {
211
- factory: factoryPda,
212
- payer: this.wallet.publicKey,
213
- mixerProgram: this.mixerProgram.programId,
214
- mixerPool: mixerPoolPda,
215
- mixerConfig: mixerConfigPda,
216
- merkleTree: merkleTreePda,
217
- systemProgram: web3_js_1.SystemProgram.programId,
218
- };
327
+ const recipientPubkey = recipient || this.wallet.publicKey;
328
+ // Convert nullifier hash bigint to 32-byte buffer (same as reference implementation)
329
+ const nullifierHashHex = nullifierHash.toString(16).padStart(64, "0");
330
+ const nullifierHashBytes = Buffer.from(nullifierHashHex, "hex");
331
+ const [nullifierHashCheckerPda] = this.getNullifierHashCheckerPda(nullifierHashBytes);
332
+ let recipientTokenAccount = null;
333
+ let vaultTokenAccount = null;
219
334
  if (mode === types_1.AssetMode.TOKEN && this.tokenMint) {
220
- const userTokenAccount = await (0, spl_token_1.getAssociatedTokenAddress)(this.tokenMint, this.wallet.publicKey);
221
- const factoryTokenAccount = await (0, spl_token_1.getAssociatedTokenAddress)(this.tokenMint, factoryPda, true);
222
- accounts.tokenMint = this.tokenMint;
223
- accounts.userTokenAccount = userTokenAccount;
224
- accounts.factoryTokenAccount = factoryTokenAccount;
225
- accounts.tokenProgram = spl_token_1.TOKEN_PROGRAM_ID;
335
+ recipientTokenAccount = await (0, spl_token_1.getAssociatedTokenAddress)(this.tokenMint, recipientPubkey);
336
+ vaultTokenAccount = await (0, spl_token_1.getAssociatedTokenAddress)(this.tokenMint, factoryPda, true);
226
337
  }
227
338
  const tx = await this.factoryProgram.methods
228
- .withdraw(mode, new anchor.BN(amount.toString()), Array.from(instructionData), Array.from(nullifierHashBytes))
229
- .accounts(accounts)
339
+ .withdraw(mode, new anchor.BN(amount.toString()), instructionData, // Buffer type
340
+ Array.from(nullifierHashBytes))
341
+ .accounts({
342
+ mixerPool: mixerPoolPda,
343
+ mixerConfig: mixerConfigPda,
344
+ merkleTree: merkleTreePda,
345
+ nullifierHashChecker: nullifierHashCheckerPda,
346
+ recipient: recipientPubkey,
347
+ payer: this.wallet.publicKey,
348
+ vaultTokenAccount: vaultTokenAccount,
349
+ recipientTokenAccount: recipientTokenAccount,
350
+ })
230
351
  .rpc();
231
352
  const signature = tx;
232
353
  const txDetails = await this.connection.getTransaction(signature, {
@@ -241,14 +362,12 @@ class AintiVirusSolana {
241
362
  * Stake SOL
242
363
  */
243
364
  async stakeSol(amount) {
244
- const [factoryPda] = this.getFactoryPda();
365
+ const [stakerRecordPda] = this.getStakerRecordPda(this.wallet.publicKey);
245
366
  const tx = await this.factoryProgram.methods
246
- .stakeEther(new anchor.BN(amount.toString()))
367
+ .stakeSol(new anchor.BN(amount.toString()))
247
368
  .accounts({
248
- factory: factoryPda,
249
- staker: this.wallet.publicKey,
250
- stakingProgram: this.stakingProgram.programId,
251
- systemProgram: web3_js_1.SystemProgram.programId,
369
+ stakerRecord: stakerRecordPda,
370
+ user: this.wallet.publicKey,
252
371
  })
253
372
  .rpc();
254
373
  const signature = tx;
@@ -268,19 +387,17 @@ class AintiVirusSolana {
268
387
  throw new Error("Token mint not configured");
269
388
  }
270
389
  const [factoryPda] = this.getFactoryPda();
390
+ const [stakerRecordPda] = this.getStakerRecordPda(this.wallet.publicKey);
271
391
  const userTokenAccount = await (0, spl_token_1.getAssociatedTokenAddress)(this.tokenMint, this.wallet.publicKey);
272
- const factoryTokenAccount = await (0, spl_token_1.getAssociatedTokenAddress)(this.tokenMint, factoryPda, true);
392
+ const vaultTokenAccount = await (0, spl_token_1.getAssociatedTokenAddress)(this.tokenMint, factoryPda, true);
273
393
  const tx = await this.factoryProgram.methods
274
394
  .stakeToken(new anchor.BN(amount.toString()))
275
395
  .accounts({
276
- factory: factoryPda,
277
- staker: this.wallet.publicKey,
278
- stakingProgram: this.stakingProgram.programId,
279
- tokenMint: this.tokenMint,
396
+ // factory, staking, stake_season, staking_program, token_program, system_program are auto-populated
397
+ user: this.wallet.publicKey,
398
+ stakerRecord: stakerRecordPda,
280
399
  userTokenAccount: userTokenAccount,
281
- factoryTokenAccount: factoryTokenAccount,
282
- tokenProgram: spl_token_1.TOKEN_PROGRAM_ID,
283
- systemProgram: web3_js_1.SystemProgram.programId,
400
+ vaultTokenAccount: vaultTokenAccount,
284
401
  })
285
402
  .rpc();
286
403
  const signature = tx;
@@ -296,14 +413,13 @@ class AintiVirusSolana {
296
413
  * Claim SOL rewards
297
414
  */
298
415
  async claimSol(seasonId) {
299
- const [factoryPda] = this.getFactoryPda();
416
+ const [seasonClaimedPda] = this.getSeasonClaimedPda(this.wallet.publicKey, seasonId);
300
417
  const tx = await this.factoryProgram.methods
301
- .claimEth(new anchor.BN(seasonId.toString()))
418
+ .claimSol(new anchor.BN(seasonId.toString()))
302
419
  .accounts({
303
- factory: factoryPda,
420
+ // factory, vault_sol, staking, stake_season, staking_program, system_program are auto-populated
421
+ seasonClaimed: seasonClaimedPda,
304
422
  staker: this.wallet.publicKey,
305
- stakingProgram: this.stakingProgram.programId,
306
- systemProgram: web3_js_1.SystemProgram.programId,
307
423
  })
308
424
  .rpc();
309
425
  const signature = tx;
@@ -323,19 +439,18 @@ class AintiVirusSolana {
323
439
  throw new Error("Token mint not configured");
324
440
  }
325
441
  const [factoryPda] = this.getFactoryPda();
326
- const userTokenAccount = await (0, spl_token_1.getAssociatedTokenAddress)(this.tokenMint, this.wallet.publicKey);
327
- const factoryTokenAccount = await (0, spl_token_1.getAssociatedTokenAddress)(this.tokenMint, factoryPda, true);
442
+ const [stakerRecordPda] = this.getStakerRecordPda(this.wallet.publicKey);
443
+ const [seasonClaimedPda] = this.getSeasonClaimedPda(this.wallet.publicKey, seasonId, types_1.AssetMode.TOKEN);
444
+ const stakerTokenAccount = await (0, spl_token_1.getAssociatedTokenAddress)(this.tokenMint, this.wallet.publicKey);
445
+ const vaultTokenAccount = await (0, spl_token_1.getAssociatedTokenAddress)(this.tokenMint, factoryPda, true);
328
446
  const tx = await this.factoryProgram.methods
329
447
  .claimToken(new anchor.BN(seasonId.toString()))
330
448
  .accounts({
331
- factory: factoryPda,
449
+ // factory, staking, stake_season, staking_program, token_program, system_program are auto-populated
450
+ seasonClaimed: seasonClaimedPda,
332
451
  staker: this.wallet.publicKey,
333
- stakingProgram: this.stakingProgram.programId,
334
- tokenMint: this.tokenMint,
335
- userTokenAccount: userTokenAccount,
336
- factoryTokenAccount: factoryTokenAccount,
337
- tokenProgram: spl_token_1.TOKEN_PROGRAM_ID,
338
- systemProgram: web3_js_1.SystemProgram.programId,
452
+ vaultTokenAccount: vaultTokenAccount,
453
+ stakerTokenAccount: stakerTokenAccount,
339
454
  })
340
455
  .rpc();
341
456
  const signature = tx;
@@ -351,14 +466,10 @@ class AintiVirusSolana {
351
466
  * Unstake SOL
352
467
  */
353
468
  async unstakeSol() {
354
- const [factoryPda] = this.getFactoryPda();
355
469
  const tx = await this.factoryProgram.methods
356
- .unstakeEth()
470
+ .unstakeSol()
357
471
  .accounts({
358
- factory: factoryPda,
359
472
  staker: this.wallet.publicKey,
360
- stakingProgram: this.stakingProgram.programId,
361
- systemProgram: web3_js_1.SystemProgram.programId,
362
473
  })
363
474
  .rpc();
364
475
  const signature = tx;
@@ -378,19 +489,14 @@ class AintiVirusSolana {
378
489
  throw new Error("Token mint not configured");
379
490
  }
380
491
  const [factoryPda] = this.getFactoryPda();
381
- const userTokenAccount = await (0, spl_token_1.getAssociatedTokenAddress)(this.tokenMint, this.wallet.publicKey);
382
- const factoryTokenAccount = await (0, spl_token_1.getAssociatedTokenAddress)(this.tokenMint, factoryPda, true);
492
+ const recipientTokenAccount = await (0, spl_token_1.getAssociatedTokenAddress)(this.tokenMint, this.wallet.publicKey);
493
+ const vaultTokenAccount = await (0, spl_token_1.getAssociatedTokenAddress)(this.tokenMint, factoryPda, true);
383
494
  const tx = await this.factoryProgram.methods
384
495
  .unstakeToken()
385
496
  .accounts({
386
- factory: factoryPda,
387
497
  staker: this.wallet.publicKey,
388
- stakingProgram: this.stakingProgram.programId,
389
- tokenMint: this.tokenMint,
390
- userTokenAccount: userTokenAccount,
391
- factoryTokenAccount: factoryTokenAccount,
392
- tokenProgram: spl_token_1.TOKEN_PROGRAM_ID,
393
- systemProgram: web3_js_1.SystemProgram.programId,
498
+ recipientTokenAccount: recipientTokenAccount,
499
+ vaultTokenAccount: vaultTokenAccount,
394
500
  })
395
501
  .rpc();
396
502
  const signature = tx;
@@ -406,11 +512,15 @@ class AintiVirusSolana {
406
512
  * Get current stake season
407
513
  */
408
514
  async getCurrentStakeSeason() {
409
- const [factoryPda] = this.getFactoryPda();
410
- const factoryAccount = await this.factoryProgram.account.factory.fetch(factoryPda);
411
- // Assuming factory account has currentStakeSeason field
412
- // This may need adjustment based on actual account structure
413
- return BigInt(factoryAccount.currentStakeSeason?.toString() || "0");
515
+ const [stakingPda] = this.getStakingPda();
516
+ try {
517
+ const stakingAccount = await this.stakingProgram.account.staking.fetch(stakingPda);
518
+ return BigInt(stakingAccount.currentStakeSeason.toString());
519
+ }
520
+ catch {
521
+ // If staking account doesn't exist, return 0
522
+ return 0n;
523
+ }
414
524
  }
415
525
  /**
416
526
  * Get SOL balance
@@ -435,32 +545,43 @@ class AintiVirusSolana {
435
545
  return 0n;
436
546
  }
437
547
  }
548
+ /**
549
+ * Initialize factory account
550
+ * @param feeRate Fee rate in basis points (e.g., 250 = 0.25%)
551
+ * @param tokenMint Token mint address (required for vault_token_account)
552
+ */
553
+ async initializeFactory(feeRate, tokenMint) {
554
+ const [factoryPda] = this.getFactoryPda();
555
+ // Derive vault token account (factory PDA's associated token account)
556
+ const vaultTokenAccount = await (0, spl_token_1.getAssociatedTokenAddress)(tokenMint, factoryPda, true);
557
+ const tx = await this.factoryProgram.methods
558
+ .initializeFactory(new anchor.BN(feeRate.toString()))
559
+ .accounts({
560
+ vaultTokenAccount: vaultTokenAccount,
561
+ mint: tokenMint,
562
+ })
563
+ .rpc();
564
+ const slot = await this.connection.getSlot();
565
+ const blockTime = await this.connection.getBlockTime(slot);
566
+ return {
567
+ txHash: tx,
568
+ blockTime: blockTime || undefined,
569
+ };
570
+ }
438
571
  /**
439
572
  * Deploy mixer instance
440
573
  */
441
574
  async deployMixer(mode, amount) {
442
- const [factoryPda] = this.getFactoryPda();
443
575
  const [mixerPoolPda] = this.getMixerPoolPda(mode, amount);
444
576
  const [mixerConfigPda] = this.getMixerConfigPda(mode, amount);
445
577
  const [merkleTreePda] = this.getMerkleTreePda(mixerConfigPda);
446
- const accounts = {
447
- factory: factoryPda,
448
- payer: this.wallet.publicKey,
449
- mixerProgram: this.mixerProgram.programId,
578
+ const tx = await this.factoryProgram.methods
579
+ .deployMixer(mode, new anchor.BN(amount.toString()))
580
+ .accounts({
450
581
  mixerPool: mixerPoolPda,
451
582
  mixerConfig: mixerConfigPda,
452
583
  merkleTree: merkleTreePda,
453
- systemProgram: web3_js_1.SystemProgram.programId,
454
- };
455
- if (mode === types_1.AssetMode.TOKEN && this.tokenMint) {
456
- const factoryTokenAccount = await (0, spl_token_1.getAssociatedTokenAddress)(this.tokenMint, factoryPda, true);
457
- accounts.tokenMint = this.tokenMint;
458
- accounts.factoryTokenAccount = factoryTokenAccount;
459
- accounts.tokenProgram = spl_token_1.TOKEN_PROGRAM_ID;
460
- }
461
- const tx = await this.factoryProgram.methods
462
- .deployMixer(mode, new anchor.BN(amount.toString()))
463
- .accounts(accounts)
584
+ })
464
585
  .rpc();
465
586
  const signature = tx;
466
587
  const txDetails = await this.connection.getTransaction(signature, {
@@ -475,11 +596,9 @@ class AintiVirusSolana {
475
596
  * Set fee rate (admin function)
476
597
  */
477
598
  async setFeeRate(feeRate) {
478
- const [factoryPda] = this.getFactoryPda();
479
599
  const tx = await this.factoryProgram.methods
480
600
  .setFeeRate(new anchor.BN(feeRate.toString()))
481
601
  .accounts({
482
- factory: factoryPda,
483
602
  authority: this.wallet.publicKey,
484
603
  })
485
604
  .rpc();
@@ -496,13 +615,9 @@ class AintiVirusSolana {
496
615
  * Set staking season period (admin function)
497
616
  */
498
617
  async setStakingSeasonPeriod(period) {
499
- const [factoryPda] = this.getFactoryPda();
500
618
  const tx = await this.factoryProgram.methods
501
619
  .setStakingSeasonPeriod(new anchor.BN(period.toString()))
502
- .accounts({
503
- factory: factoryPda,
504
- authority: this.wallet.publicKey,
505
- })
620
+ .accounts({})
506
621
  .rpc();
507
622
  const signature = tx;
508
623
  const txDetails = await this.connection.getTransaction(signature, {
@@ -516,56 +631,12 @@ class AintiVirusSolana {
516
631
  /**
517
632
  * Start stake season (admin function)
518
633
  */
519
- async startStakeSeason() {
520
- const [factoryPda] = this.getFactoryPda();
521
- const tx = await this.factoryProgram.methods
522
- .startStakeSeason()
523
- .accounts({
524
- factory: factoryPda,
525
- authority: this.wallet.publicKey,
526
- stakingProgram: this.stakingProgram.programId,
527
- })
528
- .rpc();
529
- const signature = tx;
530
- const txDetails = await this.connection.getTransaction(signature, {
531
- commitment: "confirmed",
532
- });
533
- return {
534
- txHash: signature,
535
- blockTime: txDetails?.blockTime ?? undefined,
536
- };
537
- }
538
- /**
539
- * Set verifier address (admin function)
540
- */
541
- async setVerifier(verifierAddress) {
542
- const [factoryPda] = this.getFactoryPda();
634
+ async startStakeSeason(nextSeasonId) {
635
+ const [nextStakeSeasonPda] = this.getStakeSeasonPda(nextSeasonId);
543
636
  const tx = await this.factoryProgram.methods
544
- .setVerifier(new web3_js_1.PublicKey(verifierAddress))
637
+ .startStakeSeason(new anchor.BN(nextSeasonId.toString()))
545
638
  .accounts({
546
- factory: factoryPda,
547
- authority: this.wallet.publicKey,
548
- })
549
- .rpc();
550
- const signature = tx;
551
- const txDetails = await this.connection.getTransaction(signature, {
552
- commitment: "confirmed",
553
- });
554
- return {
555
- txHash: signature,
556
- blockTime: txDetails?.blockTime ?? undefined,
557
- };
558
- }
559
- /**
560
- * Set hasher address (admin function)
561
- */
562
- async setHasher(hasherAddress) {
563
- const [factoryPda] = this.getFactoryPda();
564
- const tx = await this.factoryProgram.methods
565
- .setHasher(new web3_js_1.PublicKey(hasherAddress))
566
- .accounts({
567
- factory: factoryPda,
568
- authority: this.wallet.publicKey,
639
+ nextStakeSeason: nextStakeSeasonPda,
569
640
  })
570
641
  .rpc();
571
642
  const signature = tx;
@@ -582,8 +653,20 @@ class AintiVirusSolana {
582
653
  */
583
654
  async calculateDepositAmount(amount) {
584
655
  const [factoryPda] = this.getFactoryPda();
585
- const factoryAccount = await this.factoryProgram.account.factory.fetch(factoryPda);
586
- const feeRate = BigInt(factoryAccount.feeRate?.toString() || "0");
656
+ let factoryAccount;
657
+ try {
658
+ factoryAccount = await this.factoryProgram.account.factory.fetch(factoryPda);
659
+ }
660
+ catch (error) {
661
+ const errorMsg = error?.message || String(error);
662
+ if (errorMsg.includes("array")) {
663
+ throw new Error(`Failed to deserialize factory account. This may indicate an IDL type mismatch. ` +
664
+ `Original error: ${errorMsg}`);
665
+ }
666
+ throw error;
667
+ }
668
+ const feeRateValue = factoryAccount.feeRate;
669
+ const feeRate = feeRateValue ? BigInt(feeRateValue.toString()) : 0n;
587
670
  // Calculate: amount + (amount * feeRate / 10000)
588
671
  return amount + (amount * feeRate) / 10000n;
589
672
  }
@@ -592,35 +675,51 @@ class AintiVirusSolana {
592
675
  */
593
676
  async getFeeRate() {
594
677
  const [factoryPda] = this.getFactoryPda();
595
- const factoryAccount = await this.factoryProgram.account.factory.fetch(factoryPda);
596
- return BigInt(factoryAccount.feeRate?.toString() || "0");
678
+ try {
679
+ const factoryAccount = await this.factoryProgram.account.factory.fetch(factoryPda);
680
+ return factoryAccount?.feeRate
681
+ ? BigInt(factoryAccount.feeRate.toString())
682
+ : 0n;
683
+ }
684
+ catch (error) {
685
+ const errorMsg = error instanceof Error ? error.message : String(error);
686
+ if (errorMsg.includes("array")) {
687
+ throw new Error(`Failed to deserialize factory account. This may indicate an IDL type mismatch. ` +
688
+ `Original error: ${errorMsg}`);
689
+ }
690
+ throw error;
691
+ }
692
+ }
693
+ /**
694
+ * Get factory account (includes mint address)
695
+ */
696
+ async getFactoryAccount() {
697
+ const [factoryPda] = this.getFactoryPda();
698
+ return await this.factoryProgram.account.factory.fetch(factoryPda);
597
699
  }
598
700
  /**
599
701
  * Get stake season information
600
702
  */
601
703
  async getStakeSeason(seasonId) {
602
- const [factoryPda] = this.getFactoryPda();
603
704
  // Fetch stake season from staking program
604
- const seasonPda = web3_js_1.PublicKey.findProgramAddressSync([
605
- Buffer.from("stake_season"),
606
- new anchor.BN(seasonId.toString()).toArrayLike(Buffer, "be", 8),
607
- ], this.stakingProgram.programId)[0];
705
+ const [seasonPda] = this.getStakeSeasonPda(seasonId);
608
706
  try {
609
707
  const seasonAccount = await this.stakingProgram.account.stakeSeason.fetch(seasonPda);
610
708
  return {
611
709
  seasonId: BigInt(seasonAccount.seasonId?.toString() || "0"),
612
710
  startTimestamp: BigInt(seasonAccount.startTimestamp?.toString() || "0"),
613
711
  endTimestamp: BigInt(seasonAccount.endTimestamp?.toString() || "0"),
614
- totalStakedEthAmount: BigInt(seasonAccount.totalStakedEthAmount?.toString() || "0"),
712
+ totalStakedSolAmount: BigInt(seasonAccount.totalStakedSolAmount?.toString() || "0"),
615
713
  totalStakedTokenAmount: BigInt(seasonAccount.totalStakedTokenAmount?.toString() || "0"),
616
- totalRewardEthAmount: BigInt(seasonAccount.totalRewardEthAmount?.toString() || "0"),
714
+ totalRewardSolAmount: BigInt(seasonAccount.totalRewardSolAmount?.toString() || "0"),
617
715
  totalRewardTokenAmount: BigInt(seasonAccount.totalRewardTokenAmount?.toString() || "0"),
618
- totalEthWeightValue: BigInt(seasonAccount.totalEthWeightValue?.toString() || "0"),
716
+ totalSolWeightValue: BigInt(seasonAccount.totalSolWeightValue?.toString() || "0"),
619
717
  totalTokenWeightValue: BigInt(seasonAccount.totalTokenWeightValue?.toString() || "0"),
620
718
  };
621
719
  }
622
- catch {
623
- throw new Error(`Stake season ${seasonId} not found`);
720
+ catch (error) {
721
+ const errorMsg = error instanceof Error ? error.message : String(error);
722
+ throw new Error(`Stake season ${seasonId} not found. Original error: ${errorMsg}`);
624
723
  }
625
724
  }
626
725
  /**
@@ -632,13 +731,13 @@ class AintiVirusSolana {
632
731
  try {
633
732
  const stakerRecord = await this.stakingProgram.account.stakerRecord.fetch(stakerRecordPda);
634
733
  return {
635
- ethStakedSeasonId: BigInt(stakerRecord.ethStakedSeasonId?.toString() || "0"),
734
+ solStakedSeasonId: BigInt(stakerRecord.solStakedSeasonId?.toString() || "0"),
636
735
  tokenStakedSeasonId: BigInt(stakerRecord.tokenStakedSeasonId?.toString() || "0"),
637
- ethStakedTimestamp: BigInt(stakerRecord.ethStakedTimestamp?.toString() || "0"),
736
+ solStakedTimestamp: BigInt(stakerRecord.solStakedTimestamp?.toString() || "0"),
638
737
  tokenStakedTimestamp: BigInt(stakerRecord.tokenStakedTimestamp?.toString() || "0"),
639
- stakedEthAmount: BigInt(stakerRecord.stakedEthAmount?.toString() || "0"),
738
+ stakedSolAmount: BigInt(stakerRecord.stakedSolAmount?.toString() || "0"),
640
739
  stakedTokenAmount: BigInt(stakerRecord.stakedTokenAmount?.toString() || "0"),
641
- ethWeightValue: BigInt(stakerRecord.ethWeightValue?.toString() || "0"),
740
+ solWeightValue: BigInt(stakerRecord.solWeightValue?.toString() || "0"),
642
741
  tokenWeightValue: BigInt(stakerRecord.tokenWeightValue?.toString() || "0"),
643
742
  };
644
743
  }
@@ -650,37 +749,13 @@ class AintiVirusSolana {
650
749
  * Check if address has claimed SOL for a season
651
750
  */
652
751
  async hasClaimedSol(address, seasonId) {
653
- const stakerPubkey = new web3_js_1.PublicKey(address);
654
- const claimPda = web3_js_1.PublicKey.findProgramAddressSync([
655
- Buffer.from("season_claimed_eth"),
656
- stakerPubkey.toBuffer(),
657
- new anchor.BN(seasonId.toString()).toArrayLike(Buffer, "be", 8),
658
- ], this.stakingProgram.programId)[0];
659
- try {
660
- await this.stakingProgram.account.seasonClaimedEth.fetch(claimPda);
661
- return true;
662
- }
663
- catch {
664
- return false;
665
- }
752
+ return this.hasClaimed(address, seasonId, types_1.AssetMode.SOL);
666
753
  }
667
754
  /**
668
755
  * Check if address has claimed tokens for a season
669
756
  */
670
757
  async hasClaimedToken(address, seasonId) {
671
- const stakerPubkey = new web3_js_1.PublicKey(address);
672
- const claimPda = web3_js_1.PublicKey.findProgramAddressSync([
673
- Buffer.from("season_claimed_token"),
674
- stakerPubkey.toBuffer(),
675
- new anchor.BN(seasonId.toString()).toArrayLike(Buffer, "be", 8),
676
- ], this.stakingProgram.programId)[0];
677
- try {
678
- await this.stakingProgram.account.seasonClaimedToken.fetch(claimPda);
679
- return true;
680
- }
681
- catch {
682
- return false;
683
- }
758
+ return this.hasClaimed(address, seasonId, types_1.AssetMode.TOKEN);
684
759
  }
685
760
  /**
686
761
  * Get staking address (PDA)
@@ -688,7 +763,7 @@ class AintiVirusSolana {
688
763
  async getStakingAddress() {
689
764
  const [factoryPda] = this.getFactoryPda();
690
765
  const factoryAccount = await this.factoryProgram.account.factory.fetch(factoryPda);
691
- return factoryAccount.staking;
766
+ return factoryAccount.stakingProgram;
692
767
  }
693
768
  }
694
769
  exports.AintiVirusSolana = AintiVirusSolana;