@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.
@@ -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 = "0xf63f34f7e9608ae7d3a6f5b06ce423d9f9043648" as Address;
26
+ const ROUTER = BSC_ADDRESSES.router;
26
27
 
27
28
  const TOKENS = {
28
- WBNB: "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c" as Address,
29
- USDT: "0x55d398326f99059fF775485246999027B3197955" as Address,
30
- slisBNB: "0xB0b84D294e0C75A6abe60171b70edEb2EFd14A1B" as Address,
31
- apUSD: "0x1977097E2E5697A6DD91b6732F368a14F50f6B3d" as Address,
32
- xBNB: "0xB78eB4d5928bAb158Eb23c3154544084cD2661d5" as Address,
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
- // Minimal amounts
36
- const BNB_AMOUNT = parseEther("0.0005"); // ~$0.30
37
- const USDT_AMOUNT = parseAmount("0.5"); // $0.50
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
- const apUSDOut = await readClient.previewMintApUSD(TOKENS.slisBNB, lstAmount);
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
- const lstBack = await readClient.previewRedeemApUSD(TOKENS.slisBNB, apUSDOut);
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.stakeAndMintApUSD(0n, BNB_AMOUNT);
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); // Wait for RPC state sync
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.redeemApUSD({
178
+ const redeemHash = await writeClient.redeem({
170
179
  lst: TOKENS.slisBNB,
171
- apUSDAmount: apUSDMinted,
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.mintApUSD({
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.redeemApUSDAndRequestUnstake(directMinted);
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.stakeAndMintXBNB(0n, BNB_AMOUNT);
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.redeemXBNB({
259
+ const redeemHash = await writeClient.redeem({
240
260
  lst: TOKENS.slisBNB,
241
- xBNBAmount: xBNBMinted,
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.mintXBNB({
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.swapAndMintApUSD({
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.redeemApUSDAndSwap({
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.swapAndMintXBNB({
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.redeemXBNB({
406
+ const redeemHash = await writeClient.redeem({
381
407
  lst: TOKENS.slisBNB,
382
- xBNBAmount: xBNBMinted,
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
  });