@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.
- package/dist/evm/index.d.ts +7 -3
- package/dist/evm/index.js +38 -16
- package/dist/hooks/useAdmin.d.ts +0 -2
- package/dist/hooks/useAdmin.js +11 -51
- package/dist/hooks/useAintiVirus.js +1 -1
- package/dist/hooks/useClaim.js +1 -1
- package/dist/hooks/useDeploy.js +1 -1
- package/dist/hooks/useDeposit.js +1 -1
- package/dist/hooks/useStake.js +1 -1
- package/dist/hooks/useView.js +1 -1
- package/dist/hooks/useWithdraw.js +1 -1
- package/dist/solana/idl/aintivirus_factory.json +2951 -0
- package/dist/solana/idl/aintivirus_mixer.json +615 -0
- package/dist/solana/idl/aintivirus_staking.json +989 -0
- package/dist/solana/index.d.ts +60 -14
- package/dist/solana/index.js +310 -235
- package/dist/solana/types/aintivirus_factory.d.ts +2957 -0
- package/dist/solana/types/aintivirus_factory.js +2 -0
- package/dist/solana/types/aintivirus_mixer.d.ts +621 -0
- package/dist/solana/types/aintivirus_mixer.js +2 -0
- package/dist/solana/types/aintivirus_staking.d.ts +995 -0
- package/dist/solana/types/aintivirus_staking.js +2 -0
- package/dist/types/index.d.ts +43 -5
- package/dist/types/index.js +1 -0
- package/dist/utils/proof.js +35 -10
- package/package.json +9 -5
package/dist/solana/index.js
CHANGED
|
@@ -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(
|
|
51
|
+
constructor(wallet, connection, tokenMint) {
|
|
46
52
|
this.connection = connection;
|
|
47
53
|
this.wallet = wallet;
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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.
|
|
122
|
-
const [mixerConfigPda] = this.getMixerConfigPda(types_1.AssetMode.
|
|
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
|
-
|
|
125
|
-
const
|
|
126
|
-
commitmentBytes.
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
|
291
|
+
const vaultTokenAccount = await (0, spl_token_1.getAssociatedTokenAddress)(this.tokenMint, factoryPda, true // allowOwnerOffCurve
|
|
164
292
|
);
|
|
165
|
-
|
|
166
|
-
const
|
|
167
|
-
commitmentBytes.
|
|
168
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
nullifierHashBytes.
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
-
|
|
221
|
-
|
|
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()),
|
|
229
|
-
|
|
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 [
|
|
365
|
+
const [stakerRecordPda] = this.getStakerRecordPda(this.wallet.publicKey);
|
|
245
366
|
const tx = await this.factoryProgram.methods
|
|
246
|
-
.
|
|
367
|
+
.stakeSol(new anchor.BN(amount.toString()))
|
|
247
368
|
.accounts({
|
|
248
|
-
|
|
249
|
-
|
|
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
|
|
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
|
|
277
|
-
|
|
278
|
-
|
|
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
|
-
|
|
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 [
|
|
416
|
+
const [seasonClaimedPda] = this.getSeasonClaimedPda(this.wallet.publicKey, seasonId);
|
|
300
417
|
const tx = await this.factoryProgram.methods
|
|
301
|
-
.
|
|
418
|
+
.claimSol(new anchor.BN(seasonId.toString()))
|
|
302
419
|
.accounts({
|
|
303
|
-
factory
|
|
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
|
|
327
|
-
const
|
|
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
|
|
449
|
+
// factory, staking, stake_season, staking_program, token_program, system_program are auto-populated
|
|
450
|
+
seasonClaimed: seasonClaimedPda,
|
|
332
451
|
staker: this.wallet.publicKey,
|
|
333
|
-
|
|
334
|
-
|
|
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
|
-
.
|
|
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
|
|
382
|
-
const
|
|
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
|
-
|
|
389
|
-
|
|
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 [
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
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
|
|
447
|
-
|
|
448
|
-
|
|
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
|
-
|
|
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 [
|
|
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
|
-
.
|
|
637
|
+
.startStakeSeason(new anchor.BN(nextSeasonId.toString()))
|
|
545
638
|
.accounts({
|
|
546
|
-
|
|
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
|
-
|
|
586
|
-
|
|
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
|
-
|
|
596
|
-
|
|
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 =
|
|
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
|
-
|
|
712
|
+
totalStakedSolAmount: BigInt(seasonAccount.totalStakedSolAmount?.toString() || "0"),
|
|
615
713
|
totalStakedTokenAmount: BigInt(seasonAccount.totalStakedTokenAmount?.toString() || "0"),
|
|
616
|
-
|
|
714
|
+
totalRewardSolAmount: BigInt(seasonAccount.totalRewardSolAmount?.toString() || "0"),
|
|
617
715
|
totalRewardTokenAmount: BigInt(seasonAccount.totalRewardTokenAmount?.toString() || "0"),
|
|
618
|
-
|
|
716
|
+
totalSolWeightValue: BigInt(seasonAccount.totalSolWeightValue?.toString() || "0"),
|
|
619
717
|
totalTokenWeightValue: BigInt(seasonAccount.totalTokenWeightValue?.toString() || "0"),
|
|
620
718
|
};
|
|
621
719
|
}
|
|
622
|
-
catch {
|
|
623
|
-
|
|
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
|
-
|
|
734
|
+
solStakedSeasonId: BigInt(stakerRecord.solStakedSeasonId?.toString() || "0"),
|
|
636
735
|
tokenStakedSeasonId: BigInt(stakerRecord.tokenStakedSeasonId?.toString() || "0"),
|
|
637
|
-
|
|
736
|
+
solStakedTimestamp: BigInt(stakerRecord.solStakedTimestamp?.toString() || "0"),
|
|
638
737
|
tokenStakedTimestamp: BigInt(stakerRecord.tokenStakedTimestamp?.toString() || "0"),
|
|
639
|
-
|
|
738
|
+
stakedSolAmount: BigInt(stakerRecord.stakedSolAmount?.toString() || "0"),
|
|
640
739
|
stakedTokenAmount: BigInt(stakerRecord.stakedTokenAmount?.toString() || "0"),
|
|
641
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
766
|
+
return factoryAccount.stakingProgram;
|
|
692
767
|
}
|
|
693
768
|
}
|
|
694
769
|
exports.AintiVirusSolana = AintiVirusSolana;
|