@aspan/sdk 0.3.0 → 0.4.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 +267 -509
- package/dist/index.d.mts +148 -328
- package/dist/index.d.ts +148 -328
- package/dist/index.js +133 -381
- package/dist/index.mjs +132 -381
- package/package.json +3 -1
- package/src/__tests__/fork.test.ts +206 -0
- package/src/__tests__/router.test.ts +456 -27
- package/src/abi/router.ts +60 -191
- package/src/index.ts +22 -5
- package/src/router.ts +58 -216
- package/src/types.ts +60 -43
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
|
|
11
11
|
import { describe, it, expect, beforeAll } from "vitest";
|
|
12
12
|
import { privateKeyToAccount } from "viem/accounts";
|
|
13
|
-
import { createPublicClient, http, parseEther, zeroAddress, type Address } from "viem";
|
|
13
|
+
import { createPublicClient, http, parseEther, formatEther, zeroAddress, type Address } from "viem";
|
|
14
14
|
import { bsc } from "viem/chains";
|
|
15
15
|
import {
|
|
16
16
|
AspanRouterClient,
|
|
@@ -18,23 +18,24 @@ import {
|
|
|
18
18
|
encodeV3Path,
|
|
19
19
|
parseAmount,
|
|
20
20
|
formatAmount,
|
|
21
|
+
BSC_ADDRESSES,
|
|
21
22
|
} from "../index";
|
|
22
23
|
|
|
23
24
|
// ============ Configuration ============
|
|
24
25
|
|
|
25
|
-
const ROUTER =
|
|
26
|
+
const ROUTER = BSC_ADDRESSES.router;
|
|
26
27
|
|
|
27
28
|
const TOKENS = {
|
|
28
|
-
WBNB:
|
|
29
|
-
USDT:
|
|
30
|
-
slisBNB:
|
|
31
|
-
apUSD:
|
|
32
|
-
xBNB:
|
|
29
|
+
WBNB: BSC_ADDRESSES.WBNB,
|
|
30
|
+
USDT: BSC_ADDRESSES.USDT,
|
|
31
|
+
slisBNB: BSC_ADDRESSES.slisBNB,
|
|
32
|
+
apUSD: BSC_ADDRESSES.apUSD,
|
|
33
|
+
xBNB: BSC_ADDRESSES.xBNB,
|
|
33
34
|
};
|
|
34
35
|
|
|
35
|
-
//
|
|
36
|
-
const BNB_AMOUNT = parseEther("0.
|
|
37
|
-
const USDT_AMOUNT = parseAmount("
|
|
36
|
+
// Test amounts - must be above Lista minimum unstake (~0.001 BNB worth)
|
|
37
|
+
const BNB_AMOUNT = parseEther("0.002"); // ~$1.20
|
|
38
|
+
const USDT_AMOUNT = parseAmount("2"); // $2.00
|
|
38
39
|
|
|
39
40
|
const ERC20_ABI = [
|
|
40
41
|
{ name: "balanceOf", type: "function", inputs: [{ type: "address" }], outputs: [{ type: "uint256" }], stateMutability: "view" },
|
|
@@ -121,10 +122,12 @@ describe("AspanRouter SDK", () => {
|
|
|
121
122
|
|
|
122
123
|
it("should preview mint/redeem", async () => {
|
|
123
124
|
const lstAmount = parseAmount("1");
|
|
124
|
-
|
|
125
|
+
// Use new unified previewMint(lst, amount, mintXBNB)
|
|
126
|
+
const apUSDOut = await readClient.previewMint(TOKENS.slisBNB, lstAmount, false);
|
|
125
127
|
expect(apUSDOut).toBeGreaterThan(parseAmount("400"));
|
|
126
128
|
|
|
127
|
-
|
|
129
|
+
// Use new unified previewRedeem(lst, redeemXBNB, amount)
|
|
130
|
+
const lstBack = await readClient.previewRedeem(TOKENS.slisBNB, false, apUSDOut);
|
|
128
131
|
const ratio = Number(lstBack) / Number(lstAmount);
|
|
129
132
|
expect(ratio).toBeGreaterThan(0.95);
|
|
130
133
|
console.log(`Preview: 1 slisBNB → ${formatAmount(apUSDOut)} apUSD (${(ratio * 100).toFixed(1)}% round-trip)`);
|
|
@@ -149,11 +152,17 @@ describe("AspanRouter SDK", () => {
|
|
|
149
152
|
console.log(` Staking ${formatAmount(BNB_AMOUNT)} BNB...`);
|
|
150
153
|
const apUSDBefore = await getBalance(TOKENS.apUSD);
|
|
151
154
|
|
|
152
|
-
const mintHash = await writeClient.
|
|
155
|
+
const mintHash = await writeClient.stakeAndMint({
|
|
156
|
+
targetLST: TOKENS.slisBNB,
|
|
157
|
+
isXBNB: false,
|
|
158
|
+
minMintOut: 0n,
|
|
159
|
+
deadline: BigInt(Math.floor(Date.now() / 1000) + 3600),
|
|
160
|
+
value: BNB_AMOUNT,
|
|
161
|
+
});
|
|
153
162
|
const mintReceipt = await publicClient.waitForTransactionReceipt({ hash: mintHash });
|
|
154
163
|
await sleep(2000);
|
|
155
164
|
expect(mintReceipt.status).toBe("success");
|
|
156
|
-
await sleep(2000);
|
|
165
|
+
await sleep(2000);
|
|
157
166
|
|
|
158
167
|
const apUSDAfter = await getBalance(TOKENS.apUSD);
|
|
159
168
|
const apUSDMinted = apUSDAfter - apUSDBefore;
|
|
@@ -166,9 +175,10 @@ describe("AspanRouter SDK", () => {
|
|
|
166
175
|
|
|
167
176
|
const slisBNBBefore = await getBalance(TOKENS.slisBNB);
|
|
168
177
|
|
|
169
|
-
const redeemHash = await writeClient.
|
|
178
|
+
const redeemHash = await writeClient.redeem({
|
|
170
179
|
lst: TOKENS.slisBNB,
|
|
171
|
-
|
|
180
|
+
redeemXBNB: false,
|
|
181
|
+
amount: apUSDMinted,
|
|
172
182
|
minOut: 0n,
|
|
173
183
|
});
|
|
174
184
|
const redeemReceipt = await publicClient.waitForTransactionReceipt({ hash: redeemHash });
|
|
@@ -185,9 +195,10 @@ describe("AspanRouter SDK", () => {
|
|
|
185
195
|
await approve(TOKENS.slisBNB, slisBNBReceived);
|
|
186
196
|
|
|
187
197
|
const apUSDBefore2 = await getBalance(TOKENS.apUSD);
|
|
188
|
-
const directMintHash = await writeClient.
|
|
198
|
+
const directMintHash = await writeClient.mint({
|
|
189
199
|
lst: TOKENS.slisBNB,
|
|
190
200
|
lstAmount: slisBNBReceived,
|
|
201
|
+
mintXBNB: false,
|
|
191
202
|
minOut: 0n,
|
|
192
203
|
});
|
|
193
204
|
await publicClient.waitForTransactionReceipt({ hash: directMintHash });
|
|
@@ -203,7 +214,10 @@ describe("AspanRouter SDK", () => {
|
|
|
203
214
|
await approve(TOKENS.apUSD, directMinted);
|
|
204
215
|
|
|
205
216
|
const indicesBefore = await readClient.getUserWithdrawalIndices(account.address);
|
|
206
|
-
const unstakeHash = await writeClient.
|
|
217
|
+
const unstakeHash = await writeClient.redeemAndRequestUnstake({
|
|
218
|
+
redeemXBNB: false,
|
|
219
|
+
amount: directMinted,
|
|
220
|
+
});
|
|
207
221
|
await publicClient.waitForTransactionReceipt({ hash: unstakeHash });
|
|
208
222
|
await sleep(2000);
|
|
209
223
|
|
|
@@ -220,7 +234,13 @@ describe("AspanRouter SDK", () => {
|
|
|
220
234
|
console.log(` Staking ${formatAmount(BNB_AMOUNT)} BNB...`);
|
|
221
235
|
const xBNBBefore = await getBalance(TOKENS.xBNB);
|
|
222
236
|
|
|
223
|
-
const mintHash = await writeClient.
|
|
237
|
+
const mintHash = await writeClient.stakeAndMint({
|
|
238
|
+
targetLST: TOKENS.slisBNB,
|
|
239
|
+
isXBNB: true,
|
|
240
|
+
minMintOut: 0n,
|
|
241
|
+
deadline: BigInt(Math.floor(Date.now() / 1000) + 3600),
|
|
242
|
+
value: BNB_AMOUNT,
|
|
243
|
+
});
|
|
224
244
|
const mintReceipt = await publicClient.waitForTransactionReceipt({ hash: mintHash });
|
|
225
245
|
await sleep(2000);
|
|
226
246
|
expect(mintReceipt.status).toBe("success");
|
|
@@ -236,9 +256,10 @@ describe("AspanRouter SDK", () => {
|
|
|
236
256
|
|
|
237
257
|
const slisBNBBefore = await getBalance(TOKENS.slisBNB);
|
|
238
258
|
|
|
239
|
-
const redeemHash = await writeClient.
|
|
259
|
+
const redeemHash = await writeClient.redeem({
|
|
240
260
|
lst: TOKENS.slisBNB,
|
|
241
|
-
|
|
261
|
+
redeemXBNB: true,
|
|
262
|
+
amount: xBNBMinted,
|
|
242
263
|
minOut: 0n,
|
|
243
264
|
});
|
|
244
265
|
const redeemReceipt = await publicClient.waitForTransactionReceipt({ hash: redeemHash });
|
|
@@ -255,9 +276,10 @@ describe("AspanRouter SDK", () => {
|
|
|
255
276
|
await approve(TOKENS.slisBNB, slisBNBReceived2);
|
|
256
277
|
|
|
257
278
|
const xBNBBefore2 = await getBalance(TOKENS.xBNB);
|
|
258
|
-
const directMintHash = await writeClient.
|
|
279
|
+
const directMintHash = await writeClient.mint({
|
|
259
280
|
lst: TOKENS.slisBNB,
|
|
260
281
|
lstAmount: slisBNBReceived2,
|
|
282
|
+
mintXBNB: true,
|
|
261
283
|
minOut: 0n,
|
|
262
284
|
});
|
|
263
285
|
await publicClient.waitForTransactionReceipt({ hash: directMintHash });
|
|
@@ -285,7 +307,7 @@ describe("AspanRouter SDK", () => {
|
|
|
285
307
|
|
|
286
308
|
const apUSDBefore = await getBalance(TOKENS.apUSD);
|
|
287
309
|
|
|
288
|
-
const mintHash = await writeClient.
|
|
310
|
+
const mintHash = await writeClient.swapAndMint({
|
|
289
311
|
swapParams: {
|
|
290
312
|
inputToken: TOKENS.USDT,
|
|
291
313
|
inputAmount: USDT_AMOUNT,
|
|
@@ -294,6 +316,7 @@ describe("AspanRouter SDK", () => {
|
|
|
294
316
|
poolFee: 2500,
|
|
295
317
|
},
|
|
296
318
|
mintParams: {
|
|
319
|
+
mintXBNB: false,
|
|
297
320
|
minMintOut: 0n,
|
|
298
321
|
recipient: zeroAddress,
|
|
299
322
|
deadline: BigInt(Math.floor(Date.now() / 1000) + 3600),
|
|
@@ -315,12 +338,14 @@ describe("AspanRouter SDK", () => {
|
|
|
315
338
|
const usdtBefore = await getBalance(TOKENS.USDT);
|
|
316
339
|
const path = encodeV3Path([TOKENS.slisBNB, TOKENS.WBNB, TOKENS.USDT], [500, 500]);
|
|
317
340
|
|
|
318
|
-
const redeemHash = await writeClient.
|
|
341
|
+
const redeemHash = await writeClient.redeemAndSwap({
|
|
319
342
|
lst: TOKENS.slisBNB,
|
|
343
|
+
redeemXBNB: false,
|
|
320
344
|
amount: apUSDMinted,
|
|
321
345
|
path,
|
|
322
346
|
minOut: 0n,
|
|
323
347
|
deadline: BigInt(Math.floor(Date.now() / 1000) + 3600),
|
|
348
|
+
unwrapBNB: false,
|
|
324
349
|
});
|
|
325
350
|
const redeemReceipt = await publicClient.waitForTransactionReceipt({ hash: redeemHash });
|
|
326
351
|
await sleep(2000);
|
|
@@ -348,7 +373,7 @@ describe("AspanRouter SDK", () => {
|
|
|
348
373
|
|
|
349
374
|
const xBNBBefore = await getBalance(TOKENS.xBNB);
|
|
350
375
|
|
|
351
|
-
const mintHash = await writeClient.
|
|
376
|
+
const mintHash = await writeClient.swapAndMint({
|
|
352
377
|
swapParams: {
|
|
353
378
|
inputToken: TOKENS.USDT,
|
|
354
379
|
inputAmount: USDT_AMOUNT,
|
|
@@ -357,6 +382,7 @@ describe("AspanRouter SDK", () => {
|
|
|
357
382
|
poolFee: 2500,
|
|
358
383
|
},
|
|
359
384
|
mintParams: {
|
|
385
|
+
mintXBNB: true,
|
|
360
386
|
minMintOut: 0n,
|
|
361
387
|
recipient: zeroAddress,
|
|
362
388
|
deadline: BigInt(Math.floor(Date.now() / 1000) + 3600),
|
|
@@ -377,9 +403,10 @@ describe("AspanRouter SDK", () => {
|
|
|
377
403
|
|
|
378
404
|
const slisBNBBefore = await getBalance(TOKENS.slisBNB);
|
|
379
405
|
|
|
380
|
-
const redeemHash = await writeClient.
|
|
406
|
+
const redeemHash = await writeClient.redeem({
|
|
381
407
|
lst: TOKENS.slisBNB,
|
|
382
|
-
|
|
408
|
+
redeemXBNB: true,
|
|
409
|
+
amount: xBNBMinted,
|
|
383
410
|
minOut: 0n,
|
|
384
411
|
});
|
|
385
412
|
const redeemReceipt = await publicClient.waitForTransactionReceipt({ hash: redeemHash });
|
|
@@ -390,5 +417,407 @@ describe("AspanRouter SDK", () => {
|
|
|
390
417
|
console.log(` Received: ${formatAmount(slisBNBAfter - slisBNBBefore)} slisBNB ✓`);
|
|
391
418
|
expect(slisBNBAfter).toBeGreaterThan(slisBNBBefore);
|
|
392
419
|
}, 180000);
|
|
420
|
+
|
|
421
|
+
// Test 5: swapAndMintDefault apUSD (USDT → apUSD with default LST)
|
|
422
|
+
it.skipIf(!HAS_PRIVATE_KEY)("swapAndMintDefault: USDT → apUSD", async () => {
|
|
423
|
+
console.log(`\n[E2E 5] swapAndMintDefault: USDT → apUSD`);
|
|
424
|
+
|
|
425
|
+
const usdtBalance = await getBalance(TOKENS.USDT);
|
|
426
|
+
if (usdtBalance < USDT_AMOUNT) {
|
|
427
|
+
console.log(` ⚠️ Insufficient USDT (${formatAmount(usdtBalance)}), skipping`);
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
console.log(` Swapping ${formatAmount(USDT_AMOUNT)} USDT → apUSD (default LST)...`);
|
|
432
|
+
await approve(TOKENS.USDT, USDT_AMOUNT);
|
|
433
|
+
|
|
434
|
+
const apUSDBefore = await getBalance(TOKENS.apUSD);
|
|
435
|
+
|
|
436
|
+
const deadline = BigInt(Math.floor(Date.now() / 1000) + 600);
|
|
437
|
+
const mintHash = await writeClient.swapAndMintDefault({
|
|
438
|
+
inputToken: TOKENS.USDT,
|
|
439
|
+
inputAmount: USDT_AMOUNT,
|
|
440
|
+
mintXBNB: false,
|
|
441
|
+
minMintOut: 0n,
|
|
442
|
+
deadline,
|
|
443
|
+
});
|
|
444
|
+
const mintReceipt = await publicClient.waitForTransactionReceipt({ hash: mintHash });
|
|
445
|
+
await sleep(2000);
|
|
446
|
+
expect(mintReceipt.status).toBe("success");
|
|
447
|
+
|
|
448
|
+
const apUSDAfter = await getBalance(TOKENS.apUSD);
|
|
449
|
+
const apUSDMinted = apUSDAfter - apUSDBefore;
|
|
450
|
+
console.log(` Minted: ${formatAmount(apUSDMinted)} apUSD ✓`);
|
|
451
|
+
expect(apUSDMinted).toBeGreaterThan(0n);
|
|
452
|
+
}, 180000);
|
|
453
|
+
|
|
454
|
+
// Test 6: swapAndMintDefault xBNB (USDT → xBNB with default LST)
|
|
455
|
+
it.skipIf(!HAS_PRIVATE_KEY)("swapAndMintDefault: USDT → xBNB", async () => {
|
|
456
|
+
console.log(`\n[E2E 6] swapAndMintDefault: USDT → xBNB`);
|
|
457
|
+
|
|
458
|
+
const usdtBalance = await getBalance(TOKENS.USDT);
|
|
459
|
+
if (usdtBalance < USDT_AMOUNT) {
|
|
460
|
+
console.log(` ⚠️ Insufficient USDT (${formatAmount(usdtBalance)}), skipping`);
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
console.log(` Swapping ${formatAmount(USDT_AMOUNT)} USDT → xBNB (default LST)...`);
|
|
465
|
+
await approve(TOKENS.USDT, USDT_AMOUNT);
|
|
466
|
+
|
|
467
|
+
const xBNBBefore = await getBalance(TOKENS.xBNB);
|
|
468
|
+
|
|
469
|
+
const deadline = BigInt(Math.floor(Date.now() / 1000) + 600);
|
|
470
|
+
const mintHash = await writeClient.swapAndMintDefault({
|
|
471
|
+
inputToken: TOKENS.USDT,
|
|
472
|
+
inputAmount: USDT_AMOUNT,
|
|
473
|
+
mintXBNB: true,
|
|
474
|
+
minMintOut: 0n,
|
|
475
|
+
deadline,
|
|
476
|
+
});
|
|
477
|
+
const mintReceipt = await publicClient.waitForTransactionReceipt({ hash: mintHash });
|
|
478
|
+
await sleep(2000);
|
|
479
|
+
expect(mintReceipt.status).toBe("success");
|
|
480
|
+
|
|
481
|
+
const xBNBAfter = await getBalance(TOKENS.xBNB);
|
|
482
|
+
const xBNBMinted = xBNBAfter - xBNBBefore;
|
|
483
|
+
console.log(` Minted: ${formatAmount(xBNBMinted, 8)} xBNB ✓`);
|
|
484
|
+
expect(xBNBMinted).toBeGreaterThan(0n);
|
|
485
|
+
}, 180000);
|
|
486
|
+
|
|
487
|
+
// Test 7: redeemAndSwap xBNB → USDT via V3
|
|
488
|
+
it.skipIf(!HAS_PRIVATE_KEY)("redeemAndSwap: xBNB → USDT", async () => {
|
|
489
|
+
console.log(`\n[E2E 7] redeemAndSwap: xBNB → USDT`);
|
|
490
|
+
|
|
491
|
+
const xBNBBalance = await getBalance(TOKENS.xBNB);
|
|
492
|
+
if (xBNBBalance === 0n) {
|
|
493
|
+
console.log(` ⚠️ No xBNB balance, skipping`);
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
const redeemAmount = xBNBBalance > parseEther("0.0001") ? parseEther("0.0001") : xBNBBalance;
|
|
498
|
+
console.log(` Redeeming ${formatAmount(redeemAmount, 8)} xBNB → USDT...`);
|
|
499
|
+
await approve(TOKENS.xBNB, redeemAmount);
|
|
500
|
+
|
|
501
|
+
const usdtBefore = await getBalance(TOKENS.USDT);
|
|
502
|
+
const path = encodeV3Path([TOKENS.slisBNB, TOKENS.WBNB, TOKENS.USDT], [500, 500]);
|
|
503
|
+
|
|
504
|
+
const redeemHash = await writeClient.redeemAndSwap({
|
|
505
|
+
lst: TOKENS.slisBNB,
|
|
506
|
+
redeemXBNB: true,
|
|
507
|
+
amount: redeemAmount,
|
|
508
|
+
path,
|
|
509
|
+
minOut: 0n,
|
|
510
|
+
deadline: BigInt(Math.floor(Date.now() / 1000) + 3600),
|
|
511
|
+
unwrapBNB: false,
|
|
512
|
+
});
|
|
513
|
+
const redeemReceipt = await publicClient.waitForTransactionReceipt({ hash: redeemHash });
|
|
514
|
+
await sleep(2000);
|
|
515
|
+
expect(redeemReceipt.status).toBe("success");
|
|
516
|
+
|
|
517
|
+
const usdtAfter = await getBalance(TOKENS.USDT);
|
|
518
|
+
console.log(` Received: ${formatAmount(usdtAfter - usdtBefore)} USDT ✓`);
|
|
519
|
+
expect(usdtAfter).toBeGreaterThan(usdtBefore);
|
|
520
|
+
}, 180000);
|
|
521
|
+
|
|
522
|
+
// Test 8: redeemAndRequestUnstake xBNB → Lista unstake
|
|
523
|
+
it.skipIf(!HAS_PRIVATE_KEY)("redeemAndRequestUnstake: xBNB → Lista unstake", async () => {
|
|
524
|
+
console.log(`\n[E2E 8] redeemAndRequestUnstake: xBNB → Lista unstake`);
|
|
525
|
+
|
|
526
|
+
// First stake fresh BNB → xBNB to ensure enough for Lista minimum
|
|
527
|
+
console.log(` Staking 0.002 BNB → xBNB first...`);
|
|
528
|
+
const stakeHash = await writeClient.stakeAndMint({
|
|
529
|
+
targetLST: TOKENS.slisBNB,
|
|
530
|
+
isXBNB: true,
|
|
531
|
+
minMintOut: 0n,
|
|
532
|
+
deadline: BigInt(Math.floor(Date.now() / 1000) + 3600),
|
|
533
|
+
value: parseEther("0.002"),
|
|
534
|
+
});
|
|
535
|
+
await publicClient.waitForTransactionReceipt({ hash: stakeHash });
|
|
536
|
+
await sleep(2000);
|
|
537
|
+
|
|
538
|
+
const xBNBBalance = await getBalance(TOKENS.xBNB);
|
|
539
|
+
console.log(` Requesting unstake for ${formatAmount(xBNBBalance, 8)} xBNB...`);
|
|
540
|
+
await approve(TOKENS.xBNB, xBNBBalance);
|
|
541
|
+
|
|
542
|
+
const indicesBefore = await readClient.getUserWithdrawalIndices(account.address);
|
|
543
|
+
|
|
544
|
+
const unstakeHash = await writeClient.redeemAndRequestUnstake({
|
|
545
|
+
redeemXBNB: true,
|
|
546
|
+
amount: xBNBBalance,
|
|
547
|
+
});
|
|
548
|
+
const unstakeReceipt = await publicClient.waitForTransactionReceipt({ hash: unstakeHash });
|
|
549
|
+
await sleep(2000);
|
|
550
|
+
expect(unstakeReceipt.status).toBe("success");
|
|
551
|
+
|
|
552
|
+
const indicesAfter = await readClient.getUserWithdrawalIndices(account.address);
|
|
553
|
+
expect(indicesAfter.length).toBeGreaterThan(indicesBefore.length);
|
|
554
|
+
console.log(` Unstake requested ✓ (new indices: ${indicesAfter.length - indicesBefore.length})`);
|
|
555
|
+
}, 180000);
|
|
556
|
+
|
|
557
|
+
// Test 9: stakeAndMint xBNB
|
|
558
|
+
it.skipIf(!HAS_PRIVATE_KEY)("stakeAndMint: BNB → xBNB", async () => {
|
|
559
|
+
console.log(`\n[E2E 9] stakeAndMint: BNB → xBNB`);
|
|
560
|
+
|
|
561
|
+
const amount = parseEther("0.001");
|
|
562
|
+
console.log(` Staking ${formatEther(amount)} BNB → xBNB...`);
|
|
563
|
+
|
|
564
|
+
const xBNBBefore = await getBalance(TOKENS.xBNB);
|
|
565
|
+
|
|
566
|
+
const hash = await writeClient.stakeAndMint({
|
|
567
|
+
targetLST: TOKENS.slisBNB,
|
|
568
|
+
isXBNB: true,
|
|
569
|
+
minMintOut: 0n,
|
|
570
|
+
deadline: BigInt(Math.floor(Date.now() / 1000) + 3600),
|
|
571
|
+
value: amount,
|
|
572
|
+
});
|
|
573
|
+
const receipt = await publicClient.waitForTransactionReceipt({ hash });
|
|
574
|
+
await sleep(2000);
|
|
575
|
+
expect(receipt.status).toBe("success");
|
|
576
|
+
|
|
577
|
+
const xBNBAfter = await getBalance(TOKENS.xBNB);
|
|
578
|
+
const minted = xBNBAfter - xBNBBefore;
|
|
579
|
+
console.log(` Minted: ${formatAmount(minted, 8)} xBNB ✓`);
|
|
580
|
+
expect(minted).toBeGreaterThan(0n);
|
|
581
|
+
}, 180000);
|
|
582
|
+
|
|
583
|
+
// Test 10: mint xBNB (direct LST → xBNB)
|
|
584
|
+
it.skipIf(!HAS_PRIVATE_KEY)("mint: slisBNB → xBNB", async () => {
|
|
585
|
+
console.log(`\n[E2E 10] mint: slisBNB → xBNB`);
|
|
586
|
+
|
|
587
|
+
const slisBNBBalance = await getBalance(TOKENS.slisBNB);
|
|
588
|
+
if (slisBNBBalance === 0n) {
|
|
589
|
+
console.log(` ⚠️ No slisBNB balance, skipping`);
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
const amount = slisBNBBalance > parseEther("0.001") ? parseEther("0.001") : slisBNBBalance;
|
|
594
|
+
console.log(` Minting xBNB from ${formatEther(amount)} slisBNB...`);
|
|
595
|
+
await approve(TOKENS.slisBNB, amount);
|
|
596
|
+
|
|
597
|
+
const xBNBBefore = await getBalance(TOKENS.xBNB);
|
|
598
|
+
|
|
599
|
+
const hash = await writeClient.mint({
|
|
600
|
+
lst: TOKENS.slisBNB,
|
|
601
|
+
lstAmount: amount,
|
|
602
|
+
mintXBNB: true,
|
|
603
|
+
minOut: 0n,
|
|
604
|
+
});
|
|
605
|
+
const receipt = await publicClient.waitForTransactionReceipt({ hash });
|
|
606
|
+
await sleep(2000);
|
|
607
|
+
expect(receipt.status).toBe("success");
|
|
608
|
+
|
|
609
|
+
const xBNBAfter = await getBalance(TOKENS.xBNB);
|
|
610
|
+
const minted = xBNBAfter - xBNBBefore;
|
|
611
|
+
console.log(` Minted: ${formatAmount(minted, 8)} xBNB ✓`);
|
|
612
|
+
expect(minted).toBeGreaterThan(0n);
|
|
613
|
+
}, 180000);
|
|
614
|
+
|
|
615
|
+
// Test 11: redeem xBNB → slisBNB
|
|
616
|
+
it.skipIf(!HAS_PRIVATE_KEY)("redeem: xBNB → slisBNB", async () => {
|
|
617
|
+
console.log(`\n[E2E 11] redeem: xBNB → slisBNB`);
|
|
618
|
+
|
|
619
|
+
const xBNBBalance = await getBalance(TOKENS.xBNB);
|
|
620
|
+
if (xBNBBalance === 0n) {
|
|
621
|
+
console.log(` ⚠️ No xBNB balance, skipping`);
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
const amount = xBNBBalance > parseEther("0.0001") ? parseEther("0.0001") : xBNBBalance;
|
|
626
|
+
console.log(` Redeeming ${formatAmount(amount, 8)} xBNB → slisBNB...`);
|
|
627
|
+
await approve(TOKENS.xBNB, amount);
|
|
628
|
+
|
|
629
|
+
const slisBNBBefore = await getBalance(TOKENS.slisBNB);
|
|
630
|
+
|
|
631
|
+
const hash = await writeClient.redeem({
|
|
632
|
+
lst: TOKENS.slisBNB,
|
|
633
|
+
redeemXBNB: true,
|
|
634
|
+
amount,
|
|
635
|
+
minOut: 0n,
|
|
636
|
+
});
|
|
637
|
+
const receipt = await publicClient.waitForTransactionReceipt({ hash });
|
|
638
|
+
await sleep(2000);
|
|
639
|
+
expect(receipt.status).toBe("success");
|
|
640
|
+
|
|
641
|
+
const slisBNBAfter = await getBalance(TOKENS.slisBNB);
|
|
642
|
+
const received = slisBNBAfter - slisBNBBefore;
|
|
643
|
+
console.log(` Received: ${formatEther(received)} slisBNB ✓`);
|
|
644
|
+
expect(received).toBeGreaterThan(0n);
|
|
645
|
+
}, 180000);
|
|
646
|
+
|
|
647
|
+
// Test 12: stakeAndMint apUSD
|
|
648
|
+
it.skipIf(!HAS_PRIVATE_KEY)("stakeAndMint: BNB → apUSD (may skip if disabled)", async () => {
|
|
649
|
+
console.log(`\n[E2E 12] stakeAndMint: BNB → apUSD`);
|
|
650
|
+
|
|
651
|
+
const amount = parseEther("0.001");
|
|
652
|
+
console.log(` Staking ${formatEther(amount)} BNB → apUSD...`);
|
|
653
|
+
|
|
654
|
+
const apUSDBefore = await getBalance(TOKENS.apUSD);
|
|
655
|
+
|
|
656
|
+
try {
|
|
657
|
+
const hash = await writeClient.stakeAndMint({
|
|
658
|
+
targetLST: TOKENS.slisBNB,
|
|
659
|
+
isXBNB: false,
|
|
660
|
+
minMintOut: 0n,
|
|
661
|
+
deadline: BigInt(Math.floor(Date.now() / 1000) + 3600),
|
|
662
|
+
value: amount,
|
|
663
|
+
});
|
|
664
|
+
const receipt = await publicClient.waitForTransactionReceipt({ hash });
|
|
665
|
+
await sleep(2000);
|
|
666
|
+
|
|
667
|
+
if (receipt.status === "success") {
|
|
668
|
+
const apUSDAfter = await getBalance(TOKENS.apUSD);
|
|
669
|
+
const minted = apUSDAfter - apUSDBefore;
|
|
670
|
+
console.log(` Minted: ${formatEther(minted)} apUSD ✓`);
|
|
671
|
+
expect(minted).toBeGreaterThan(0n);
|
|
672
|
+
} else {
|
|
673
|
+
console.log(` ⚠️ Transaction reverted (apUSD minting may be disabled)`);
|
|
674
|
+
}
|
|
675
|
+
} catch (e: any) {
|
|
676
|
+
if (e.message?.includes("MintingDisabled") || e.message?.includes("revert")) {
|
|
677
|
+
console.log(` ⚠️ apUSD minting is disabled, skipping`);
|
|
678
|
+
} else {
|
|
679
|
+
throw e;
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
}, 180000);
|
|
683
|
+
|
|
684
|
+
// Test 13: mint apUSD (direct LST → apUSD)
|
|
685
|
+
it.skipIf(!HAS_PRIVATE_KEY)("mint: slisBNB → apUSD (may skip if disabled)", async () => {
|
|
686
|
+
console.log(`\n[E2E 13] mint: slisBNB → apUSD`);
|
|
687
|
+
|
|
688
|
+
const slisBNBBalance = await getBalance(TOKENS.slisBNB);
|
|
689
|
+
if (slisBNBBalance === 0n) {
|
|
690
|
+
console.log(` ⚠️ No slisBNB balance, skipping`);
|
|
691
|
+
return;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
const amount = slisBNBBalance > parseEther("0.001") ? parseEther("0.001") : slisBNBBalance;
|
|
695
|
+
console.log(` Minting apUSD from ${formatEther(amount)} slisBNB...`);
|
|
696
|
+
await approve(TOKENS.slisBNB, amount);
|
|
697
|
+
|
|
698
|
+
const apUSDBefore = await getBalance(TOKENS.apUSD);
|
|
699
|
+
|
|
700
|
+
try {
|
|
701
|
+
const hash = await writeClient.mint({
|
|
702
|
+
lst: TOKENS.slisBNB,
|
|
703
|
+
lstAmount: amount,
|
|
704
|
+
mintXBNB: false,
|
|
705
|
+
minOut: 0n,
|
|
706
|
+
});
|
|
707
|
+
const receipt = await publicClient.waitForTransactionReceipt({ hash });
|
|
708
|
+
await sleep(2000);
|
|
709
|
+
|
|
710
|
+
if (receipt.status === "success") {
|
|
711
|
+
const apUSDAfter = await getBalance(TOKENS.apUSD);
|
|
712
|
+
const minted = apUSDAfter - apUSDBefore;
|
|
713
|
+
console.log(` Minted: ${formatEther(minted)} apUSD ✓`);
|
|
714
|
+
expect(minted).toBeGreaterThan(0n);
|
|
715
|
+
} else {
|
|
716
|
+
console.log(` ⚠️ Transaction reverted (apUSD minting may be disabled)`);
|
|
717
|
+
}
|
|
718
|
+
} catch (e: any) {
|
|
719
|
+
if (e.message?.includes("MintingDisabled") || e.message?.includes("revert")) {
|
|
720
|
+
console.log(` ⚠️ apUSD minting is disabled, skipping`);
|
|
721
|
+
} else {
|
|
722
|
+
throw e;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
}, 180000);
|
|
726
|
+
|
|
727
|
+
// Test 14: redeem apUSD → slisBNB
|
|
728
|
+
it.skipIf(!HAS_PRIVATE_KEY)("redeem: apUSD → slisBNB", async () => {
|
|
729
|
+
console.log(`\n[E2E 14] redeem: apUSD → slisBNB`);
|
|
730
|
+
|
|
731
|
+
const apUSDBalance = await getBalance(TOKENS.apUSD);
|
|
732
|
+
if (apUSDBalance === 0n) {
|
|
733
|
+
console.log(` ⚠️ No apUSD balance, skipping`);
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
const amount = apUSDBalance > parseEther("0.1") ? parseEther("0.1") : apUSDBalance;
|
|
738
|
+
console.log(` Redeeming ${formatEther(amount)} apUSD → slisBNB...`);
|
|
739
|
+
await approve(TOKENS.apUSD, amount);
|
|
740
|
+
|
|
741
|
+
const slisBNBBefore = await getBalance(TOKENS.slisBNB);
|
|
742
|
+
|
|
743
|
+
const hash = await writeClient.redeem({
|
|
744
|
+
lst: TOKENS.slisBNB,
|
|
745
|
+
redeemXBNB: false,
|
|
746
|
+
amount,
|
|
747
|
+
minOut: 0n,
|
|
748
|
+
});
|
|
749
|
+
const receipt = await publicClient.waitForTransactionReceipt({ hash });
|
|
750
|
+
await sleep(2000);
|
|
751
|
+
expect(receipt.status).toBe("success");
|
|
752
|
+
|
|
753
|
+
const slisBNBAfter = await getBalance(TOKENS.slisBNB);
|
|
754
|
+
const received = slisBNBAfter - slisBNBBefore;
|
|
755
|
+
console.log(` Received: ${formatEther(received)} slisBNB ✓`);
|
|
756
|
+
expect(received).toBeGreaterThan(0n);
|
|
757
|
+
}, 180000);
|
|
758
|
+
|
|
759
|
+
// Test 15: redeemAndSwap apUSD → USDT
|
|
760
|
+
it.skipIf(!HAS_PRIVATE_KEY)("redeemAndSwap: apUSD → USDT", async () => {
|
|
761
|
+
console.log(`\n[E2E 15] redeemAndSwap: apUSD → USDT`);
|
|
762
|
+
|
|
763
|
+
const apUSDBalance = await getBalance(TOKENS.apUSD);
|
|
764
|
+
if (apUSDBalance === 0n) {
|
|
765
|
+
console.log(` ⚠️ No apUSD balance, skipping`);
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
const amount = apUSDBalance > parseAmount("1") ? parseAmount("1") : apUSDBalance;
|
|
770
|
+
console.log(` Redeeming ${formatEther(amount)} apUSD → USDT...`);
|
|
771
|
+
await approve(TOKENS.apUSD, amount);
|
|
772
|
+
|
|
773
|
+
const usdtBefore = await getBalance(TOKENS.USDT);
|
|
774
|
+
const path = encodeV3Path([TOKENS.slisBNB, TOKENS.WBNB, TOKENS.USDT], [500, 500]);
|
|
775
|
+
|
|
776
|
+
const hash = await writeClient.redeemAndSwap({
|
|
777
|
+
lst: TOKENS.slisBNB,
|
|
778
|
+
redeemXBNB: false,
|
|
779
|
+
amount,
|
|
780
|
+
path,
|
|
781
|
+
minOut: 0n,
|
|
782
|
+
deadline: BigInt(Math.floor(Date.now() / 1000) + 3600),
|
|
783
|
+
unwrapBNB: false,
|
|
784
|
+
});
|
|
785
|
+
const receipt = await publicClient.waitForTransactionReceipt({ hash });
|
|
786
|
+
await sleep(2000);
|
|
787
|
+
expect(receipt.status).toBe("success");
|
|
788
|
+
|
|
789
|
+
const usdtAfter = await getBalance(TOKENS.USDT);
|
|
790
|
+
const received = usdtAfter - usdtBefore;
|
|
791
|
+
console.log(` Received: ${formatAmount(received)} USDT ✓`);
|
|
792
|
+
expect(received).toBeGreaterThan(0n);
|
|
793
|
+
}, 180000);
|
|
794
|
+
|
|
795
|
+
// Test 16: redeemAndRequestUnstake apUSD → Lista unstake
|
|
796
|
+
it.skipIf(!HAS_PRIVATE_KEY)("redeemAndRequestUnstake: apUSD → Lista unstake", async () => {
|
|
797
|
+
console.log(`\n[E2E 16] redeemAndRequestUnstake: apUSD → Lista unstake`);
|
|
798
|
+
|
|
799
|
+
const apUSDBalance = await getBalance(TOKENS.apUSD);
|
|
800
|
+
if (apUSDBalance === 0n) {
|
|
801
|
+
console.log(` ⚠️ No apUSD balance, skipping`);
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
console.log(` Requesting unstake for ${formatAmount(apUSDBalance)} apUSD...`);
|
|
806
|
+
await approve(TOKENS.apUSD, apUSDBalance);
|
|
807
|
+
|
|
808
|
+
const indicesBefore = await readClient.getUserWithdrawalIndices(account.address);
|
|
809
|
+
|
|
810
|
+
const hash = await writeClient.redeemAndRequestUnstake({
|
|
811
|
+
redeemXBNB: false,
|
|
812
|
+
amount: apUSDBalance,
|
|
813
|
+
});
|
|
814
|
+
const receipt = await publicClient.waitForTransactionReceipt({ hash });
|
|
815
|
+
await sleep(2000);
|
|
816
|
+
expect(receipt.status).toBe("success");
|
|
817
|
+
|
|
818
|
+
const indicesAfter = await readClient.getUserWithdrawalIndices(account.address);
|
|
819
|
+
expect(indicesAfter.length).toBeGreaterThan(indicesBefore.length);
|
|
820
|
+
console.log(` Unstake requested ✓ (new indices: ${indicesAfter.length - indicesBefore.length})`);
|
|
821
|
+
}, 180000);
|
|
393
822
|
});
|
|
394
823
|
});
|