@aspan/sdk 0.2.2 → 0.3.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/README.md +2 -2
- package/dist/index.d.mts +8 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +6 -0
- package/dist/index.mjs +6 -0
- package/package.json +3 -1
- package/src/__tests__/fork.test.ts +206 -0
- package/src/__tests__/router.test.ts +393 -4
- package/src/abi/router.ts +2 -0
- package/src/router.ts +2 -0
- package/src/types.ts +2 -0
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ pnpm add @aspan/sdk
|
|
|
16
16
|
| Contract | Address |
|
|
17
17
|
|----------|---------|
|
|
18
18
|
| **Diamond (Main Entry)** | `0x10d25Ae0690533e0BA9E64EC7ae77dbD4fE8A46f` |
|
|
19
|
-
| **Router** | `
|
|
19
|
+
| **Router** | `0x159B2990966B0E4f07cD58c9Def513EA1fF81c0C` |
|
|
20
20
|
| **wclisBNB** | `0x439faaC2229559121C4Ad4fd8B3FE13Dff038046` |
|
|
21
21
|
| **ApUSD** | `0x1977097E2E5697A6DD91b6732F368a14F50f6B3d` |
|
|
22
22
|
| **XBNB** | `0xB78eB4d5928bAb158Eb23c3154544084cD2661d5` |
|
|
@@ -98,7 +98,7 @@ import { createRouterClient, AspanRouterClient } from "@aspan/sdk";
|
|
|
98
98
|
import { privateKeyToAccount } from "viem/accounts";
|
|
99
99
|
import { zeroAddress } from "viem";
|
|
100
100
|
|
|
101
|
-
const ROUTER = "
|
|
101
|
+
const ROUTER = "0x159B2990966B0E4f07cD58c9Def513EA1fF81c0C";
|
|
102
102
|
|
|
103
103
|
// Node.js
|
|
104
104
|
const account = privateKeyToAccount("0x...");
|
package/dist/index.d.mts
CHANGED
|
@@ -212,6 +212,8 @@ interface RouterSwapParams {
|
|
|
212
212
|
}
|
|
213
213
|
/** Router mint parameters */
|
|
214
214
|
interface RouterMintParams {
|
|
215
|
+
/** true = mint xBNB, false = mint apUSD */
|
|
216
|
+
mintXBNB: boolean;
|
|
215
217
|
/** Minimum output to receive (slippage protection) */
|
|
216
218
|
minMintOut: bigint;
|
|
217
219
|
/** Recipient of minted tokens (address(0) = msg.sender) */
|
|
@@ -1672,6 +1674,9 @@ declare const RouterABI: readonly [{
|
|
|
1672
1674
|
readonly name: "mintParams";
|
|
1673
1675
|
readonly type: "tuple";
|
|
1674
1676
|
readonly components: readonly [{
|
|
1677
|
+
readonly name: "mintXBNB";
|
|
1678
|
+
readonly type: "bool";
|
|
1679
|
+
}, {
|
|
1675
1680
|
readonly name: "minMintOut";
|
|
1676
1681
|
readonly type: "uint256";
|
|
1677
1682
|
}, {
|
|
@@ -1716,6 +1721,9 @@ declare const RouterABI: readonly [{
|
|
|
1716
1721
|
readonly name: "mintParams";
|
|
1717
1722
|
readonly type: "tuple";
|
|
1718
1723
|
readonly components: readonly [{
|
|
1724
|
+
readonly name: "mintXBNB";
|
|
1725
|
+
readonly type: "bool";
|
|
1726
|
+
}, {
|
|
1719
1727
|
readonly name: "minMintOut";
|
|
1720
1728
|
readonly type: "uint256";
|
|
1721
1729
|
}, {
|
package/dist/index.d.ts
CHANGED
|
@@ -212,6 +212,8 @@ interface RouterSwapParams {
|
|
|
212
212
|
}
|
|
213
213
|
/** Router mint parameters */
|
|
214
214
|
interface RouterMintParams {
|
|
215
|
+
/** true = mint xBNB, false = mint apUSD */
|
|
216
|
+
mintXBNB: boolean;
|
|
215
217
|
/** Minimum output to receive (slippage protection) */
|
|
216
218
|
minMintOut: bigint;
|
|
217
219
|
/** Recipient of minted tokens (address(0) = msg.sender) */
|
|
@@ -1672,6 +1674,9 @@ declare const RouterABI: readonly [{
|
|
|
1672
1674
|
readonly name: "mintParams";
|
|
1673
1675
|
readonly type: "tuple";
|
|
1674
1676
|
readonly components: readonly [{
|
|
1677
|
+
readonly name: "mintXBNB";
|
|
1678
|
+
readonly type: "bool";
|
|
1679
|
+
}, {
|
|
1675
1680
|
readonly name: "minMintOut";
|
|
1676
1681
|
readonly type: "uint256";
|
|
1677
1682
|
}, {
|
|
@@ -1716,6 +1721,9 @@ declare const RouterABI: readonly [{
|
|
|
1716
1721
|
readonly name: "mintParams";
|
|
1717
1722
|
readonly type: "tuple";
|
|
1718
1723
|
readonly components: readonly [{
|
|
1724
|
+
readonly name: "mintXBNB";
|
|
1725
|
+
readonly type: "bool";
|
|
1726
|
+
}, {
|
|
1719
1727
|
readonly name: "minMintOut";
|
|
1720
1728
|
readonly type: "uint256";
|
|
1721
1729
|
}, {
|
package/dist/index.js
CHANGED
|
@@ -1483,6 +1483,7 @@ var RouterABI = [
|
|
|
1483
1483
|
name: "mintParams",
|
|
1484
1484
|
type: "tuple",
|
|
1485
1485
|
components: [
|
|
1486
|
+
{ name: "mintXBNB", type: "bool" },
|
|
1486
1487
|
{ name: "minMintOut", type: "uint256" },
|
|
1487
1488
|
{ name: "recipient", type: "address" },
|
|
1488
1489
|
{ name: "deadline", type: "uint256" }
|
|
@@ -1515,6 +1516,7 @@ var RouterABI = [
|
|
|
1515
1516
|
name: "mintParams",
|
|
1516
1517
|
type: "tuple",
|
|
1517
1518
|
components: [
|
|
1519
|
+
{ name: "mintXBNB", type: "bool" },
|
|
1518
1520
|
{ name: "minMintOut", type: "uint256" },
|
|
1519
1521
|
{ name: "recipient", type: "address" },
|
|
1520
1522
|
{ name: "deadline", type: "uint256" }
|
|
@@ -2184,6 +2186,8 @@ var AspanRouterClient = class extends AspanRouterReadClient {
|
|
|
2184
2186
|
poolFee: params.swapParams.poolFee
|
|
2185
2187
|
},
|
|
2186
2188
|
{
|
|
2189
|
+
mintXBNB: false,
|
|
2190
|
+
// swapAndMintApUSD always mints apUSD
|
|
2187
2191
|
minMintOut: params.mintParams.minMintOut,
|
|
2188
2192
|
recipient: params.mintParams.recipient,
|
|
2189
2193
|
deadline: params.mintParams.deadline
|
|
@@ -2212,6 +2216,8 @@ var AspanRouterClient = class extends AspanRouterReadClient {
|
|
|
2212
2216
|
poolFee: params.swapParams.poolFee
|
|
2213
2217
|
},
|
|
2214
2218
|
{
|
|
2219
|
+
mintXBNB: true,
|
|
2220
|
+
// swapAndMintXBNB always mints xBNB
|
|
2215
2221
|
minMintOut: params.mintParams.minMintOut,
|
|
2216
2222
|
recipient: params.mintParams.recipient,
|
|
2217
2223
|
deadline: params.mintParams.deadline
|
package/dist/index.mjs
CHANGED
|
@@ -1443,6 +1443,7 @@ var RouterABI = [
|
|
|
1443
1443
|
name: "mintParams",
|
|
1444
1444
|
type: "tuple",
|
|
1445
1445
|
components: [
|
|
1446
|
+
{ name: "mintXBNB", type: "bool" },
|
|
1446
1447
|
{ name: "minMintOut", type: "uint256" },
|
|
1447
1448
|
{ name: "recipient", type: "address" },
|
|
1448
1449
|
{ name: "deadline", type: "uint256" }
|
|
@@ -1475,6 +1476,7 @@ var RouterABI = [
|
|
|
1475
1476
|
name: "mintParams",
|
|
1476
1477
|
type: "tuple",
|
|
1477
1478
|
components: [
|
|
1479
|
+
{ name: "mintXBNB", type: "bool" },
|
|
1478
1480
|
{ name: "minMintOut", type: "uint256" },
|
|
1479
1481
|
{ name: "recipient", type: "address" },
|
|
1480
1482
|
{ name: "deadline", type: "uint256" }
|
|
@@ -2144,6 +2146,8 @@ var AspanRouterClient = class extends AspanRouterReadClient {
|
|
|
2144
2146
|
poolFee: params.swapParams.poolFee
|
|
2145
2147
|
},
|
|
2146
2148
|
{
|
|
2149
|
+
mintXBNB: false,
|
|
2150
|
+
// swapAndMintApUSD always mints apUSD
|
|
2147
2151
|
minMintOut: params.mintParams.minMintOut,
|
|
2148
2152
|
recipient: params.mintParams.recipient,
|
|
2149
2153
|
deadline: params.mintParams.deadline
|
|
@@ -2172,6 +2176,8 @@ var AspanRouterClient = class extends AspanRouterReadClient {
|
|
|
2172
2176
|
poolFee: params.swapParams.poolFee
|
|
2173
2177
|
},
|
|
2174
2178
|
{
|
|
2179
|
+
mintXBNB: true,
|
|
2180
|
+
// swapAndMintXBNB always mints xBNB
|
|
2175
2181
|
minMintOut: params.mintParams.minMintOut,
|
|
2176
2182
|
recipient: params.mintParams.recipient,
|
|
2177
2183
|
deadline: params.mintParams.deadline
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aspan/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "TypeScript SDK for Aspan Protocol - LST-backed stablecoin on BNB Chain",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -22,6 +22,8 @@
|
|
|
22
22
|
"lint": "eslint src --ext .ts",
|
|
23
23
|
"typecheck": "tsc --noEmit",
|
|
24
24
|
"test": "vitest run",
|
|
25
|
+
"test:fork": "ANVIL_RPC=http://127.0.0.1:8545 vitest run fork.test.ts",
|
|
26
|
+
"test:e2e": "vitest run router.test.ts",
|
|
25
27
|
"test:watch": "vitest",
|
|
26
28
|
"clean": "rm -rf dist",
|
|
27
29
|
"prepublishOnly": "npm run build",
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AspanRouter SDK Fork Tests
|
|
3
|
+
*
|
|
4
|
+
* Uses Anvil fork for time manipulation and state control.
|
|
5
|
+
* Run with: npm run test:fork
|
|
6
|
+
*
|
|
7
|
+
* These tests require a local Anvil fork:
|
|
8
|
+
* anvil --fork-url https://bsc-dataseed.binance.org/ --fork-block-number <recent>
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { describe, it, expect, beforeAll, afterAll } from "vitest";
|
|
12
|
+
import { privateKeyToAccount } from "viem/accounts";
|
|
13
|
+
import { createPublicClient, createTestClient, createWalletClient, http, parseEther, type Address } from "viem";
|
|
14
|
+
import { bsc } from "viem/chains";
|
|
15
|
+
import {
|
|
16
|
+
AspanRouterClient,
|
|
17
|
+
AspanRouterReadClient,
|
|
18
|
+
formatAmount,
|
|
19
|
+
} from "../index";
|
|
20
|
+
|
|
21
|
+
// ============ Configuration ============
|
|
22
|
+
|
|
23
|
+
const ROUTER = "0x159B2990966B0E4f07cD58c9Def513EA1fF81c0C" as Address;
|
|
24
|
+
const ANVIL_RPC = process.env.ANVIL_RPC || "http://127.0.0.1:8545";
|
|
25
|
+
|
|
26
|
+
const TOKENS = {
|
|
27
|
+
WBNB: "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c" as Address,
|
|
28
|
+
USDT: "0x55d398326f99059fF775485246999027B3197955" as Address,
|
|
29
|
+
slisBNB: "0xB0b84D294e0C75A6abe60171b70edEb2EFd14A1B" as Address,
|
|
30
|
+
apUSD: "0x1977097E2E5697A6DD91b6732F368a14F50f6B3d" as Address,
|
|
31
|
+
xBNB: "0xB78eB4d5928bAb158Eb23c3154544084cD2661d5" as Address,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const ERC20_ABI = [
|
|
35
|
+
{ name: "balanceOf", type: "function", inputs: [{ type: "address" }], outputs: [{ type: "uint256" }], stateMutability: "view" },
|
|
36
|
+
{ name: "approve", type: "function", inputs: [{ type: "address" }, { type: "uint256" }], outputs: [{ type: "bool" }], stateMutability: "nonpayable" },
|
|
37
|
+
] as const;
|
|
38
|
+
|
|
39
|
+
// Use Anvil's default test account
|
|
40
|
+
const TEST_PRIVATE_KEY = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80";
|
|
41
|
+
|
|
42
|
+
// Check if Anvil is available
|
|
43
|
+
const isAnvilAvailable = async (): Promise<boolean> => {
|
|
44
|
+
try {
|
|
45
|
+
const client = createPublicClient({ transport: http(ANVIL_RPC) });
|
|
46
|
+
await client.getChainId();
|
|
47
|
+
return true;
|
|
48
|
+
} catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// ============ Tests ============
|
|
54
|
+
|
|
55
|
+
describe.skipIf(!(await isAnvilAvailable()))("Fork Tests (Anvil)", () => {
|
|
56
|
+
let readClient: AspanRouterReadClient;
|
|
57
|
+
let writeClient: AspanRouterClient;
|
|
58
|
+
let publicClient: ReturnType<typeof createPublicClient>;
|
|
59
|
+
let testClient: ReturnType<typeof createTestClient>;
|
|
60
|
+
let account: ReturnType<typeof privateKeyToAccount>;
|
|
61
|
+
|
|
62
|
+
const getBalance = async (token: Address): Promise<bigint> => {
|
|
63
|
+
return publicClient.readContract({
|
|
64
|
+
address: token,
|
|
65
|
+
abi: ERC20_ABI,
|
|
66
|
+
functionName: "balanceOf",
|
|
67
|
+
args: [account.address],
|
|
68
|
+
}) as Promise<bigint>;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const approve = async (token: Address, amount: bigint) => {
|
|
72
|
+
const hash = await writeClient.walletClient.writeContract({
|
|
73
|
+
address: token,
|
|
74
|
+
abi: ERC20_ABI,
|
|
75
|
+
functionName: "approve",
|
|
76
|
+
args: [ROUTER, amount],
|
|
77
|
+
});
|
|
78
|
+
await publicClient.waitForTransactionReceipt({ hash });
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
beforeAll(async () => {
|
|
82
|
+
account = privateKeyToAccount(TEST_PRIVATE_KEY);
|
|
83
|
+
|
|
84
|
+
publicClient = createPublicClient({
|
|
85
|
+
chain: bsc,
|
|
86
|
+
transport: http(ANVIL_RPC),
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
testClient = createTestClient({
|
|
90
|
+
chain: bsc,
|
|
91
|
+
transport: http(ANVIL_RPC),
|
|
92
|
+
mode: "anvil",
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
readClient = new AspanRouterReadClient({
|
|
96
|
+
routerAddress: ROUTER,
|
|
97
|
+
chain: bsc,
|
|
98
|
+
rpcUrl: ANVIL_RPC,
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
writeClient = new AspanRouterClient({
|
|
102
|
+
routerAddress: ROUTER,
|
|
103
|
+
account,
|
|
104
|
+
chain: bsc,
|
|
105
|
+
rpcUrl: ANVIL_RPC,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Fund test account with BNB
|
|
109
|
+
await testClient.setBalance({
|
|
110
|
+
address: account.address,
|
|
111
|
+
value: parseEther("10"),
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
console.log(`📍 Fork Test Wallet: ${account.address}`);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe("claimUnstake (with time manipulation)", () => {
|
|
118
|
+
it("should claim unstake after waiting period", async () => {
|
|
119
|
+
console.log(`\n[Fork 1] claimUnstake after time manipulation`);
|
|
120
|
+
|
|
121
|
+
// Step 1: Create some apUSD via stake
|
|
122
|
+
console.log(` Staking BNB → apUSD...`);
|
|
123
|
+
const mintHash = await writeClient.stakeAndMintApUSD(0n, parseEther("0.01"));
|
|
124
|
+
await publicClient.waitForTransactionReceipt({ hash: mintHash });
|
|
125
|
+
|
|
126
|
+
const apUSDBalance = await getBalance(TOKENS.apUSD);
|
|
127
|
+
console.log(` Minted: ${formatAmount(apUSDBalance)} apUSD`);
|
|
128
|
+
expect(apUSDBalance).toBeGreaterThan(0n);
|
|
129
|
+
|
|
130
|
+
// Step 2: Request unstake
|
|
131
|
+
console.log(` Requesting Lista unstake...`);
|
|
132
|
+
await approve(TOKENS.apUSD, apUSDBalance);
|
|
133
|
+
|
|
134
|
+
const indicesBefore = await readClient.getUserWithdrawalIndices(account.address);
|
|
135
|
+
const unstakeHash = await writeClient.redeemApUSDAndRequestUnstake(apUSDBalance);
|
|
136
|
+
await publicClient.waitForTransactionReceipt({ hash: unstakeHash });
|
|
137
|
+
|
|
138
|
+
const indicesAfter = await readClient.getUserWithdrawalIndices(account.address);
|
|
139
|
+
expect(indicesAfter.length).toBeGreaterThan(indicesBefore.length);
|
|
140
|
+
|
|
141
|
+
const newIndex = indicesAfter[indicesAfter.length - 1];
|
|
142
|
+
console.log(` Unstake requested, index: ${newIndex}`);
|
|
143
|
+
|
|
144
|
+
// Step 3: Check withdrawal status (should not be claimable yet)
|
|
145
|
+
const statusBefore = await readClient.getWithdrawalStatus(newIndex);
|
|
146
|
+
console.log(` Status before: claimable=${statusBefore.isClaimable}`);
|
|
147
|
+
expect(statusBefore.isClaimable).toBe(false);
|
|
148
|
+
|
|
149
|
+
// Step 4: Fast forward time by 8 days (Lista unbonding period is ~7 days)
|
|
150
|
+
console.log(` Fast forwarding 8 days...`);
|
|
151
|
+
const EIGHT_DAYS = 8 * 24 * 60 * 60;
|
|
152
|
+
await testClient.increaseTime({ seconds: EIGHT_DAYS });
|
|
153
|
+
await testClient.mine({ blocks: 1 });
|
|
154
|
+
|
|
155
|
+
// Step 5: Check withdrawal status (should be claimable now)
|
|
156
|
+
const statusAfter = await readClient.getWithdrawalStatus(newIndex);
|
|
157
|
+
console.log(` Status after: claimable=${statusAfter.isClaimable}`);
|
|
158
|
+
|
|
159
|
+
if (!statusAfter.isClaimable) {
|
|
160
|
+
console.log(` ⚠️ Still not claimable, Lista might have longer unbonding. Skipping claim.`);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Step 6: Claim unstake
|
|
165
|
+
console.log(` Claiming unstake...`);
|
|
166
|
+
const bnbBefore = await publicClient.getBalance({ address: account.address });
|
|
167
|
+
|
|
168
|
+
const claimHash = await writeClient.claimUnstake(newIndex);
|
|
169
|
+
await publicClient.waitForTransactionReceipt({ hash: claimHash });
|
|
170
|
+
|
|
171
|
+
const bnbAfter = await publicClient.getBalance({ address: account.address });
|
|
172
|
+
const bnbReceived = bnbAfter - bnbBefore;
|
|
173
|
+
console.log(` Received: ${formatAmount(bnbReceived)} BNB ✓`);
|
|
174
|
+
|
|
175
|
+
// Note: bnbReceived might be negative due to gas, but the claim should succeed
|
|
176
|
+
expect(statusAfter.isClaimable).toBe(true);
|
|
177
|
+
}, 300000);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
describe("Edge Cases", () => {
|
|
181
|
+
it("should handle minimum amounts", async () => {
|
|
182
|
+
console.log(`\n[Fork 2] Minimum amount handling`);
|
|
183
|
+
|
|
184
|
+
// Try very small stake
|
|
185
|
+
const minAmount = parseEther("0.0001");
|
|
186
|
+
console.log(` Staking ${formatAmount(minAmount)} BNB...`);
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
const mintHash = await writeClient.stakeAndMintApUSD(0n, minAmount);
|
|
190
|
+
await publicClient.waitForTransactionReceipt({ hash: mintHash });
|
|
191
|
+
console.log(` Success ✓`);
|
|
192
|
+
} catch (e: any) {
|
|
193
|
+
console.log(` Failed (expected for very small amounts): ${e.message?.slice(0, 50)}`);
|
|
194
|
+
}
|
|
195
|
+
}, 60000);
|
|
196
|
+
|
|
197
|
+
it("should revert on zero amounts", async () => {
|
|
198
|
+
console.log(`\n[Fork 3] Zero amount revert`);
|
|
199
|
+
|
|
200
|
+
await expect(
|
|
201
|
+
writeClient.stakeAndMintApUSD(0n, 0n)
|
|
202
|
+
).rejects.toThrow();
|
|
203
|
+
console.log(` Reverted as expected ✓`);
|
|
204
|
+
}, 30000);
|
|
205
|
+
});
|
|
206
|
+
});
|
|
@@ -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,
|
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
|
|
23
23
|
// ============ Configuration ============
|
|
24
24
|
|
|
25
|
-
const ROUTER = "
|
|
25
|
+
const ROUTER = "0x159B2990966B0E4f07cD58c9Def513EA1fF81c0C" as Address;
|
|
26
26
|
|
|
27
27
|
const TOKENS = {
|
|
28
28
|
WBNB: "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c" as Address,
|
|
@@ -33,7 +33,7 @@ const TOKENS = {
|
|
|
33
33
|
};
|
|
34
34
|
|
|
35
35
|
// Minimal amounts
|
|
36
|
-
const BNB_AMOUNT = parseEther("0.
|
|
36
|
+
const BNB_AMOUNT = parseEther("0.0005"); // ~$0.30
|
|
37
37
|
const USDT_AMOUNT = parseAmount("0.5"); // $0.50
|
|
38
38
|
|
|
39
39
|
const ERC20_ABI = [
|
|
@@ -44,6 +44,9 @@ const ERC20_ABI = [
|
|
|
44
44
|
// Check env at module level
|
|
45
45
|
const HAS_PRIVATE_KEY = !!process.env.PRIVATE_KEY;
|
|
46
46
|
|
|
47
|
+
// Helper to wait for RPC state sync
|
|
48
|
+
const sleep = (ms: number) => new Promise(r => setTimeout(r, ms));
|
|
49
|
+
|
|
47
50
|
// ============ Tests ============
|
|
48
51
|
|
|
49
52
|
describe("AspanRouter SDK", () => {
|
|
@@ -69,6 +72,7 @@ describe("AspanRouter SDK", () => {
|
|
|
69
72
|
args: [ROUTER, amount],
|
|
70
73
|
});
|
|
71
74
|
await publicClient.waitForTransactionReceipt({ hash });
|
|
75
|
+
await sleep(1000);
|
|
72
76
|
};
|
|
73
77
|
|
|
74
78
|
beforeAll(() => {
|
|
@@ -136,7 +140,7 @@ describe("AspanRouter SDK", () => {
|
|
|
136
140
|
|
|
137
141
|
// ============ E2E Tests (Sequential) ============
|
|
138
142
|
|
|
139
|
-
describe("E2E Flows", () => {
|
|
143
|
+
describe.sequential("E2E Flows", () => {
|
|
140
144
|
// Test 1: BNB → apUSD → slisBNB
|
|
141
145
|
it.skipIf(!HAS_PRIVATE_KEY)("BNB → apUSD → slisBNB", async () => {
|
|
142
146
|
console.log(`\n[E2E 1] BNB → apUSD → slisBNB`);
|
|
@@ -147,7 +151,9 @@ describe("AspanRouter SDK", () => {
|
|
|
147
151
|
|
|
148
152
|
const mintHash = await writeClient.stakeAndMintApUSD(0n, BNB_AMOUNT);
|
|
149
153
|
const mintReceipt = await publicClient.waitForTransactionReceipt({ hash: mintHash });
|
|
154
|
+
await sleep(2000);
|
|
150
155
|
expect(mintReceipt.status).toBe("success");
|
|
156
|
+
await sleep(2000); // Wait for RPC state sync
|
|
151
157
|
|
|
152
158
|
const apUSDAfter = await getBalance(TOKENS.apUSD);
|
|
153
159
|
const apUSDMinted = apUSDAfter - apUSDBefore;
|
|
@@ -166,6 +172,7 @@ describe("AspanRouter SDK", () => {
|
|
|
166
172
|
minOut: 0n,
|
|
167
173
|
});
|
|
168
174
|
const redeemReceipt = await publicClient.waitForTransactionReceipt({ hash: redeemHash });
|
|
175
|
+
await sleep(2000);
|
|
169
176
|
expect(redeemReceipt.status).toBe("success");
|
|
170
177
|
|
|
171
178
|
const slisBNBAfter = await getBalance(TOKENS.slisBNB);
|
|
@@ -184,6 +191,7 @@ describe("AspanRouter SDK", () => {
|
|
|
184
191
|
minOut: 0n,
|
|
185
192
|
});
|
|
186
193
|
await publicClient.waitForTransactionReceipt({ hash: directMintHash });
|
|
194
|
+
await sleep(2000);
|
|
187
195
|
|
|
188
196
|
const apUSDAfter2 = await getBalance(TOKENS.apUSD);
|
|
189
197
|
const directMinted = apUSDAfter2 - apUSDBefore2;
|
|
@@ -197,6 +205,7 @@ describe("AspanRouter SDK", () => {
|
|
|
197
205
|
const indicesBefore = await readClient.getUserWithdrawalIndices(account.address);
|
|
198
206
|
const unstakeHash = await writeClient.redeemApUSDAndRequestUnstake(directMinted);
|
|
199
207
|
await publicClient.waitForTransactionReceipt({ hash: unstakeHash });
|
|
208
|
+
await sleep(2000);
|
|
200
209
|
|
|
201
210
|
const indicesAfter = await readClient.getUserWithdrawalIndices(account.address);
|
|
202
211
|
expect(indicesAfter.length).toBeGreaterThan(indicesBefore.length);
|
|
@@ -213,6 +222,7 @@ describe("AspanRouter SDK", () => {
|
|
|
213
222
|
|
|
214
223
|
const mintHash = await writeClient.stakeAndMintXBNB(0n, BNB_AMOUNT);
|
|
215
224
|
const mintReceipt = await publicClient.waitForTransactionReceipt({ hash: mintHash });
|
|
225
|
+
await sleep(2000);
|
|
216
226
|
expect(mintReceipt.status).toBe("success");
|
|
217
227
|
|
|
218
228
|
const xBNBAfter = await getBalance(TOKENS.xBNB);
|
|
@@ -232,6 +242,7 @@ describe("AspanRouter SDK", () => {
|
|
|
232
242
|
minOut: 0n,
|
|
233
243
|
});
|
|
234
244
|
const redeemReceipt = await publicClient.waitForTransactionReceipt({ hash: redeemHash });
|
|
245
|
+
await sleep(2000);
|
|
235
246
|
expect(redeemReceipt.status).toBe("success");
|
|
236
247
|
|
|
237
248
|
const slisBNBAfter = await getBalance(TOKENS.slisBNB);
|
|
@@ -250,6 +261,7 @@ describe("AspanRouter SDK", () => {
|
|
|
250
261
|
minOut: 0n,
|
|
251
262
|
});
|
|
252
263
|
await publicClient.waitForTransactionReceipt({ hash: directMintHash });
|
|
264
|
+
await sleep(2000);
|
|
253
265
|
|
|
254
266
|
const xBNBAfter2 = await getBalance(TOKENS.xBNB);
|
|
255
267
|
console.log(` Minted: ${formatAmount(xBNBAfter2 - xBNBBefore2, 8)} xBNB ✓`);
|
|
@@ -288,6 +300,7 @@ describe("AspanRouter SDK", () => {
|
|
|
288
300
|
},
|
|
289
301
|
});
|
|
290
302
|
const mintReceipt = await publicClient.waitForTransactionReceipt({ hash: mintHash });
|
|
303
|
+
await sleep(2000);
|
|
291
304
|
expect(mintReceipt.status).toBe("success");
|
|
292
305
|
|
|
293
306
|
const apUSDAfter = await getBalance(TOKENS.apUSD);
|
|
@@ -310,6 +323,7 @@ describe("AspanRouter SDK", () => {
|
|
|
310
323
|
deadline: BigInt(Math.floor(Date.now() / 1000) + 3600),
|
|
311
324
|
});
|
|
312
325
|
const redeemReceipt = await publicClient.waitForTransactionReceipt({ hash: redeemHash });
|
|
326
|
+
await sleep(2000);
|
|
313
327
|
expect(redeemReceipt.status).toBe("success");
|
|
314
328
|
|
|
315
329
|
const usdtAfter = await getBalance(TOKENS.USDT);
|
|
@@ -349,6 +363,7 @@ describe("AspanRouter SDK", () => {
|
|
|
349
363
|
},
|
|
350
364
|
});
|
|
351
365
|
const mintReceipt = await publicClient.waitForTransactionReceipt({ hash: mintHash });
|
|
366
|
+
await sleep(2000);
|
|
352
367
|
expect(mintReceipt.status).toBe("success");
|
|
353
368
|
|
|
354
369
|
const xBNBAfter = await getBalance(TOKENS.xBNB);
|
|
@@ -368,11 +383,385 @@ describe("AspanRouter SDK", () => {
|
|
|
368
383
|
minOut: 0n,
|
|
369
384
|
});
|
|
370
385
|
const redeemReceipt = await publicClient.waitForTransactionReceipt({ hash: redeemHash });
|
|
386
|
+
await sleep(2000);
|
|
371
387
|
expect(redeemReceipt.status).toBe("success");
|
|
372
388
|
|
|
373
389
|
const slisBNBAfter = await getBalance(TOKENS.slisBNB);
|
|
374
390
|
console.log(` Received: ${formatAmount(slisBNBAfter - slisBNBBefore)} slisBNB ✓`);
|
|
375
391
|
expect(slisBNBAfter).toBeGreaterThan(slisBNBBefore);
|
|
376
392
|
}, 180000);
|
|
393
|
+
|
|
394
|
+
// Test 5: swapAndMintApUSDDefault (USDT → apUSD with default LST)
|
|
395
|
+
it.skipIf(!HAS_PRIVATE_KEY)("swapAndMintApUSDDefault: USDT → apUSD", async () => {
|
|
396
|
+
console.log(`\n[E2E 5] swapAndMintApUSDDefault: USDT → apUSD`);
|
|
397
|
+
|
|
398
|
+
const usdtBalance = await getBalance(TOKENS.USDT);
|
|
399
|
+
if (usdtBalance < USDT_AMOUNT) {
|
|
400
|
+
console.log(` ⚠️ Insufficient USDT (${formatAmount(usdtBalance)}), skipping`);
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
console.log(` Swapping ${formatAmount(USDT_AMOUNT)} USDT → apUSD (default LST)...`);
|
|
405
|
+
await approve(TOKENS.USDT, USDT_AMOUNT);
|
|
406
|
+
|
|
407
|
+
const apUSDBefore = await getBalance(TOKENS.apUSD);
|
|
408
|
+
|
|
409
|
+
const deadline = BigInt(Math.floor(Date.now() / 1000) + 600);
|
|
410
|
+
const mintHash = await writeClient.swapAndMintApUSDDefault({
|
|
411
|
+
inputToken: TOKENS.USDT,
|
|
412
|
+
inputAmount: USDT_AMOUNT,
|
|
413
|
+
minMintOut: 0n,
|
|
414
|
+
deadline,
|
|
415
|
+
});
|
|
416
|
+
const mintReceipt = await publicClient.waitForTransactionReceipt({ hash: mintHash });
|
|
417
|
+
await sleep(2000);
|
|
418
|
+
expect(mintReceipt.status).toBe("success");
|
|
419
|
+
|
|
420
|
+
const apUSDAfter = await getBalance(TOKENS.apUSD);
|
|
421
|
+
const apUSDMinted = apUSDAfter - apUSDBefore;
|
|
422
|
+
console.log(` Minted: ${formatAmount(apUSDMinted)} apUSD ✓`);
|
|
423
|
+
expect(apUSDMinted).toBeGreaterThan(0n);
|
|
424
|
+
}, 180000);
|
|
425
|
+
|
|
426
|
+
// Test 6: swapAndMintXBNBDefault (USDT → xBNB with default LST)
|
|
427
|
+
it.skipIf(!HAS_PRIVATE_KEY)("swapAndMintXBNBDefault: USDT → xBNB", async () => {
|
|
428
|
+
console.log(`\n[E2E 6] swapAndMintXBNBDefault: USDT → xBNB`);
|
|
429
|
+
|
|
430
|
+
const usdtBalance = await getBalance(TOKENS.USDT);
|
|
431
|
+
if (usdtBalance < USDT_AMOUNT) {
|
|
432
|
+
console.log(` ⚠️ Insufficient USDT (${formatAmount(usdtBalance)}), skipping`);
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
console.log(` Swapping ${formatAmount(USDT_AMOUNT)} USDT → xBNB (default LST)...`);
|
|
437
|
+
await approve(TOKENS.USDT, USDT_AMOUNT);
|
|
438
|
+
|
|
439
|
+
const xBNBBefore = await getBalance(TOKENS.xBNB);
|
|
440
|
+
|
|
441
|
+
const deadline = BigInt(Math.floor(Date.now() / 1000) + 600);
|
|
442
|
+
const mintHash = await writeClient.swapAndMintXBNBDefault({
|
|
443
|
+
inputToken: TOKENS.USDT,
|
|
444
|
+
inputAmount: USDT_AMOUNT,
|
|
445
|
+
minMintOut: 0n,
|
|
446
|
+
deadline,
|
|
447
|
+
});
|
|
448
|
+
const mintReceipt = await publicClient.waitForTransactionReceipt({ hash: mintHash });
|
|
449
|
+
await sleep(2000);
|
|
450
|
+
expect(mintReceipt.status).toBe("success");
|
|
451
|
+
|
|
452
|
+
const xBNBAfter = await getBalance(TOKENS.xBNB);
|
|
453
|
+
const xBNBMinted = xBNBAfter - xBNBBefore;
|
|
454
|
+
console.log(` Minted: ${formatAmount(xBNBMinted, 8)} xBNB ✓`);
|
|
455
|
+
expect(xBNBMinted).toBeGreaterThan(0n);
|
|
456
|
+
}, 180000);
|
|
457
|
+
|
|
458
|
+
// Test 7: redeemXBNBAndSwap (xBNB → USDT via V3)
|
|
459
|
+
it.skipIf(!HAS_PRIVATE_KEY)("redeemXBNBAndSwap: xBNB → USDT", async () => {
|
|
460
|
+
console.log(`\n[E2E 7] redeemXBNBAndSwap: xBNB → USDT`);
|
|
461
|
+
|
|
462
|
+
const xBNBBalance = await getBalance(TOKENS.xBNB);
|
|
463
|
+
if (xBNBBalance === 0n) {
|
|
464
|
+
console.log(` ⚠️ No xBNB balance, skipping`);
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
const redeemAmount = xBNBBalance > parseEther("0.0001") ? parseEther("0.0001") : xBNBBalance;
|
|
469
|
+
console.log(` Redeeming ${formatAmount(redeemAmount, 8)} xBNB → USDT...`);
|
|
470
|
+
await approve(TOKENS.xBNB, redeemAmount);
|
|
471
|
+
|
|
472
|
+
const usdtBefore = await getBalance(TOKENS.USDT);
|
|
473
|
+
const path = encodeV3Path([TOKENS.slisBNB, TOKENS.WBNB, TOKENS.USDT], [500, 500]);
|
|
474
|
+
|
|
475
|
+
const redeemHash = await writeClient.redeemXBNBAndSwap({
|
|
476
|
+
lst: TOKENS.slisBNB,
|
|
477
|
+
amount: redeemAmount,
|
|
478
|
+
path,
|
|
479
|
+
minOut: 0n,
|
|
480
|
+
deadline: BigInt(Math.floor(Date.now() / 1000) + 3600),
|
|
481
|
+
});
|
|
482
|
+
const redeemReceipt = await publicClient.waitForTransactionReceipt({ hash: redeemHash });
|
|
483
|
+
await sleep(2000);
|
|
484
|
+
expect(redeemReceipt.status).toBe("success");
|
|
485
|
+
|
|
486
|
+
const usdtAfter = await getBalance(TOKENS.USDT);
|
|
487
|
+
console.log(` Received: ${formatAmount(usdtAfter - usdtBefore)} USDT ✓`);
|
|
488
|
+
expect(usdtAfter).toBeGreaterThan(usdtBefore);
|
|
489
|
+
}, 180000);
|
|
490
|
+
|
|
491
|
+
// Test 8: redeemXBNBAndRequestUnstake (xBNB → Lista unstake)
|
|
492
|
+
it.skipIf(!HAS_PRIVATE_KEY)("redeemXBNBAndRequestUnstake: xBNB → Lista unstake", async () => {
|
|
493
|
+
console.log(`\n[E2E 8] redeemXBNBAndRequestUnstake: xBNB → Lista unstake`);
|
|
494
|
+
|
|
495
|
+
const xBNBBalance = await getBalance(TOKENS.xBNB);
|
|
496
|
+
if (xBNBBalance === 0n) {
|
|
497
|
+
console.log(` ⚠️ No xBNB balance, skipping`);
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
console.log(` Requesting unstake for ${formatAmount(xBNBBalance, 8)} xBNB...`);
|
|
502
|
+
await approve(TOKENS.xBNB, xBNBBalance);
|
|
503
|
+
|
|
504
|
+
const indicesBefore = await readClient.getUserWithdrawalIndices(account.address);
|
|
505
|
+
|
|
506
|
+
const unstakeHash = await writeClient.redeemXBNBAndRequestUnstake(xBNBBalance);
|
|
507
|
+
const unstakeReceipt = await publicClient.waitForTransactionReceipt({ hash: unstakeHash });
|
|
508
|
+
await sleep(2000);
|
|
509
|
+
expect(unstakeReceipt.status).toBe("success");
|
|
510
|
+
|
|
511
|
+
const indicesAfter = await readClient.getUserWithdrawalIndices(account.address);
|
|
512
|
+
expect(indicesAfter.length).toBeGreaterThan(indicesBefore.length);
|
|
513
|
+
console.log(` Unstake requested ✓ (new indices: ${indicesAfter.length - indicesBefore.length})`);
|
|
514
|
+
}, 180000);
|
|
515
|
+
|
|
516
|
+
// Note: claimUnstake requires 7+ days waiting period, cannot be tested in e2e
|
|
517
|
+
// Would need a fork test with time manipulation
|
|
518
|
+
|
|
519
|
+
// Test 9: stakeAndMintXBNB (BNB → slisBNB → xBNB, simplified)
|
|
520
|
+
it.skipIf(!HAS_PRIVATE_KEY)("stakeAndMintXBNB: BNB → xBNB", async () => {
|
|
521
|
+
console.log(`\n[E2E 9] stakeAndMintXBNB: BNB → xBNB`);
|
|
522
|
+
|
|
523
|
+
const amount = parseEther("0.001");
|
|
524
|
+
console.log(` Staking ${formatEther(amount)} BNB → xBNB...`);
|
|
525
|
+
|
|
526
|
+
const xBNBBefore = await getBalance(TOKENS.xBNB);
|
|
527
|
+
|
|
528
|
+
const hash = await writeClient.stakeAndMintXBNB(0n, amount);
|
|
529
|
+
const receipt = await publicClient.waitForTransactionReceipt({ hash });
|
|
530
|
+
await sleep(2000);
|
|
531
|
+
expect(receipt.status).toBe("success");
|
|
532
|
+
|
|
533
|
+
const xBNBAfter = await getBalance(TOKENS.xBNB);
|
|
534
|
+
const minted = xBNBAfter - xBNBBefore;
|
|
535
|
+
console.log(` Minted: ${formatAmount(minted, 8)} xBNB ✓`);
|
|
536
|
+
expect(minted).toBeGreaterThan(0n);
|
|
537
|
+
}, 180000);
|
|
538
|
+
|
|
539
|
+
// Test 10: mintXBNB (direct LST → xBNB)
|
|
540
|
+
it.skipIf(!HAS_PRIVATE_KEY)("mintXBNB: slisBNB → xBNB", async () => {
|
|
541
|
+
console.log(`\n[E2E 10] mintXBNB: slisBNB → xBNB`);
|
|
542
|
+
|
|
543
|
+
const slisBNBBalance = await getBalance(TOKENS.slisBNB);
|
|
544
|
+
if (slisBNBBalance === 0n) {
|
|
545
|
+
console.log(` ⚠️ No slisBNB balance, skipping`);
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
const amount = slisBNBBalance > parseEther("0.001") ? parseEther("0.001") : slisBNBBalance;
|
|
550
|
+
console.log(` Minting xBNB from ${formatEther(amount)} slisBNB...`);
|
|
551
|
+
await approve(TOKENS.slisBNB, amount);
|
|
552
|
+
|
|
553
|
+
const xBNBBefore = await getBalance(TOKENS.xBNB);
|
|
554
|
+
|
|
555
|
+
const hash = await writeClient.mintXBNB({
|
|
556
|
+
lst: TOKENS.slisBNB,
|
|
557
|
+
lstAmount: amount,
|
|
558
|
+
minOut: 0n,
|
|
559
|
+
});
|
|
560
|
+
const receipt = await publicClient.waitForTransactionReceipt({ hash });
|
|
561
|
+
await sleep(2000);
|
|
562
|
+
expect(receipt.status).toBe("success");
|
|
563
|
+
|
|
564
|
+
const xBNBAfter = await getBalance(TOKENS.xBNB);
|
|
565
|
+
const minted = xBNBAfter - xBNBBefore;
|
|
566
|
+
console.log(` Minted: ${formatAmount(minted, 8)} xBNB ✓`);
|
|
567
|
+
expect(minted).toBeGreaterThan(0n);
|
|
568
|
+
}, 180000);
|
|
569
|
+
|
|
570
|
+
// Test 11: redeemXBNB (direct xBNB → slisBNB)
|
|
571
|
+
it.skipIf(!HAS_PRIVATE_KEY)("redeemXBNB: xBNB → slisBNB", async () => {
|
|
572
|
+
console.log(`\n[E2E 11] redeemXBNB: xBNB → slisBNB`);
|
|
573
|
+
|
|
574
|
+
const xBNBBalance = await getBalance(TOKENS.xBNB);
|
|
575
|
+
if (xBNBBalance === 0n) {
|
|
576
|
+
console.log(` ⚠️ No xBNB balance, skipping`);
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
const amount = xBNBBalance > parseEther("0.0001") ? parseEther("0.0001") : xBNBBalance;
|
|
581
|
+
console.log(` Redeeming ${formatAmount(amount, 8)} xBNB → slisBNB...`);
|
|
582
|
+
await approve(TOKENS.xBNB, amount);
|
|
583
|
+
|
|
584
|
+
const slisBNBBefore = await getBalance(TOKENS.slisBNB);
|
|
585
|
+
|
|
586
|
+
const hash = await writeClient.redeemXBNB({
|
|
587
|
+
lst: TOKENS.slisBNB,
|
|
588
|
+
xBNBAmount: amount,
|
|
589
|
+
minOut: 0n,
|
|
590
|
+
});
|
|
591
|
+
const receipt = await publicClient.waitForTransactionReceipt({ hash });
|
|
592
|
+
await sleep(2000);
|
|
593
|
+
expect(receipt.status).toBe("success");
|
|
594
|
+
|
|
595
|
+
const slisBNBAfter = await getBalance(TOKENS.slisBNB);
|
|
596
|
+
const received = slisBNBAfter - slisBNBBefore;
|
|
597
|
+
console.log(` Received: ${formatEther(received)} slisBNB ✓`);
|
|
598
|
+
expect(received).toBeGreaterThan(0n);
|
|
599
|
+
}, 180000);
|
|
600
|
+
|
|
601
|
+
// Test 12: stakeAndMintApUSD (BNB → slisBNB → apUSD, simplified)
|
|
602
|
+
// Note: May fail if apUSD minting is disabled on mainnet
|
|
603
|
+
it.skipIf(!HAS_PRIVATE_KEY)("stakeAndMintApUSD: BNB → apUSD (may skip if disabled)", async () => {
|
|
604
|
+
console.log(`\n[E2E 12] stakeAndMintApUSD: BNB → apUSD`);
|
|
605
|
+
|
|
606
|
+
const amount = parseEther("0.001");
|
|
607
|
+
console.log(` Staking ${formatEther(amount)} BNB → apUSD...`);
|
|
608
|
+
|
|
609
|
+
const apUSDBefore = await getBalance(TOKENS.apUSD);
|
|
610
|
+
|
|
611
|
+
try {
|
|
612
|
+
const hash = await writeClient.stakeAndMintApUSD(0n, amount);
|
|
613
|
+
const receipt = await publicClient.waitForTransactionReceipt({ hash });
|
|
614
|
+
await sleep(2000);
|
|
615
|
+
|
|
616
|
+
if (receipt.status === "success") {
|
|
617
|
+
const apUSDAfter = await getBalance(TOKENS.apUSD);
|
|
618
|
+
const minted = apUSDAfter - apUSDBefore;
|
|
619
|
+
console.log(` Minted: ${formatEther(minted)} apUSD ✓`);
|
|
620
|
+
expect(minted).toBeGreaterThan(0n);
|
|
621
|
+
} else {
|
|
622
|
+
console.log(` ⚠️ Transaction reverted (apUSD minting may be disabled)`);
|
|
623
|
+
}
|
|
624
|
+
} catch (e: any) {
|
|
625
|
+
if (e.message?.includes("MintingDisabled") || e.message?.includes("revert")) {
|
|
626
|
+
console.log(` ⚠️ apUSD minting is disabled, skipping`);
|
|
627
|
+
} else {
|
|
628
|
+
throw e;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
}, 180000);
|
|
632
|
+
|
|
633
|
+
// Test 13: mintApUSD (direct LST → apUSD)
|
|
634
|
+
// Note: May fail if apUSD minting is disabled on mainnet
|
|
635
|
+
it.skipIf(!HAS_PRIVATE_KEY)("mintApUSD: slisBNB → apUSD (may skip if disabled)", async () => {
|
|
636
|
+
console.log(`\n[E2E 13] mintApUSD: slisBNB → apUSD`);
|
|
637
|
+
|
|
638
|
+
const slisBNBBalance = await getBalance(TOKENS.slisBNB);
|
|
639
|
+
if (slisBNBBalance === 0n) {
|
|
640
|
+
console.log(` ⚠️ No slisBNB balance, skipping`);
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
const amount = slisBNBBalance > parseEther("0.001") ? parseEther("0.001") : slisBNBBalance;
|
|
645
|
+
console.log(` Minting apUSD from ${formatEther(amount)} slisBNB...`);
|
|
646
|
+
await approve(TOKENS.slisBNB, amount);
|
|
647
|
+
|
|
648
|
+
const apUSDBefore = await getBalance(TOKENS.apUSD);
|
|
649
|
+
|
|
650
|
+
try {
|
|
651
|
+
const hash = await writeClient.mintApUSD({
|
|
652
|
+
lst: TOKENS.slisBNB,
|
|
653
|
+
lstAmount: amount,
|
|
654
|
+
minOut: 0n,
|
|
655
|
+
});
|
|
656
|
+
const receipt = await publicClient.waitForTransactionReceipt({ hash });
|
|
657
|
+
await sleep(2000);
|
|
658
|
+
|
|
659
|
+
if (receipt.status === "success") {
|
|
660
|
+
const apUSDAfter = await getBalance(TOKENS.apUSD);
|
|
661
|
+
const minted = apUSDAfter - apUSDBefore;
|
|
662
|
+
console.log(` Minted: ${formatEther(minted)} apUSD ✓`);
|
|
663
|
+
expect(minted).toBeGreaterThan(0n);
|
|
664
|
+
} else {
|
|
665
|
+
console.log(` ⚠️ Transaction reverted (apUSD minting may be disabled)`);
|
|
666
|
+
}
|
|
667
|
+
} catch (e: any) {
|
|
668
|
+
if (e.message?.includes("MintingDisabled") || e.message?.includes("revert")) {
|
|
669
|
+
console.log(` ⚠️ apUSD minting is disabled, skipping`);
|
|
670
|
+
} else {
|
|
671
|
+
throw e;
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
}, 180000);
|
|
675
|
+
|
|
676
|
+
// Test 14: redeemApUSD (direct apUSD → slisBNB)
|
|
677
|
+
it.skipIf(!HAS_PRIVATE_KEY)("redeemApUSD: apUSD → slisBNB", async () => {
|
|
678
|
+
console.log(`\n[E2E 14] redeemApUSD: apUSD → slisBNB`);
|
|
679
|
+
|
|
680
|
+
const apUSDBalance = await getBalance(TOKENS.apUSD);
|
|
681
|
+
if (apUSDBalance === 0n) {
|
|
682
|
+
console.log(` ⚠️ No apUSD balance, skipping`);
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
const amount = apUSDBalance > parseEther("0.1") ? parseEther("0.1") : apUSDBalance;
|
|
687
|
+
console.log(` Redeeming ${formatEther(amount)} apUSD → slisBNB...`);
|
|
688
|
+
await approve(TOKENS.apUSD, amount);
|
|
689
|
+
|
|
690
|
+
const slisBNBBefore = await getBalance(TOKENS.slisBNB);
|
|
691
|
+
|
|
692
|
+
const hash = await writeClient.redeemApUSD({
|
|
693
|
+
lst: TOKENS.slisBNB,
|
|
694
|
+
apUSDAmount: amount,
|
|
695
|
+
minOut: 0n,
|
|
696
|
+
});
|
|
697
|
+
const receipt = await publicClient.waitForTransactionReceipt({ hash });
|
|
698
|
+
await sleep(2000);
|
|
699
|
+
expect(receipt.status).toBe("success");
|
|
700
|
+
|
|
701
|
+
const slisBNBAfter = await getBalance(TOKENS.slisBNB);
|
|
702
|
+
const received = slisBNBAfter - slisBNBBefore;
|
|
703
|
+
console.log(` Received: ${formatEther(received)} slisBNB ✓`);
|
|
704
|
+
expect(received).toBeGreaterThan(0n);
|
|
705
|
+
}, 180000);
|
|
706
|
+
|
|
707
|
+
// Test 15: redeemApUSDAndSwap (apUSD → slisBNB → USDT)
|
|
708
|
+
it.skipIf(!HAS_PRIVATE_KEY)("redeemApUSDAndSwap: apUSD → USDT", async () => {
|
|
709
|
+
console.log(`\n[E2E 15] redeemApUSDAndSwap: apUSD → USDT`);
|
|
710
|
+
|
|
711
|
+
const apUSDBalance = await getBalance(TOKENS.apUSD);
|
|
712
|
+
if (apUSDBalance === 0n) {
|
|
713
|
+
console.log(` ⚠️ No apUSD balance, skipping`);
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
const amount = apUSDBalance > parseEther("0.1") ? parseEther("0.1") : apUSDBalance;
|
|
718
|
+
console.log(` Redeeming ${formatEther(amount)} apUSD → USDT...`);
|
|
719
|
+
await approve(TOKENS.apUSD, amount);
|
|
720
|
+
|
|
721
|
+
const usdtBefore = await getBalance(TOKENS.USDT);
|
|
722
|
+
const path = encodeV3Path([TOKENS.slisBNB, TOKENS.WBNB, TOKENS.USDT], [500, 500]);
|
|
723
|
+
|
|
724
|
+
const hash = await writeClient.redeemApUSDAndSwap({
|
|
725
|
+
lst: TOKENS.slisBNB,
|
|
726
|
+
amount,
|
|
727
|
+
path,
|
|
728
|
+
minOut: 0n,
|
|
729
|
+
deadline: BigInt(Math.floor(Date.now() / 1000) + 3600),
|
|
730
|
+
});
|
|
731
|
+
const receipt = await publicClient.waitForTransactionReceipt({ hash });
|
|
732
|
+
await sleep(2000);
|
|
733
|
+
expect(receipt.status).toBe("success");
|
|
734
|
+
|
|
735
|
+
const usdtAfter = await getBalance(TOKENS.USDT);
|
|
736
|
+
const received = usdtAfter - usdtBefore;
|
|
737
|
+
console.log(` Received: ${formatAmount(received)} USDT ✓`);
|
|
738
|
+
expect(received).toBeGreaterThan(0n);
|
|
739
|
+
}, 180000);
|
|
740
|
+
|
|
741
|
+
// Test 16: redeemApUSDAndRequestUnstake (apUSD → Lista unstake)
|
|
742
|
+
it.skipIf(!HAS_PRIVATE_KEY)("redeemApUSDAndRequestUnstake: apUSD → Lista unstake", async () => {
|
|
743
|
+
console.log(`\n[E2E 16] redeemApUSDAndRequestUnstake: apUSD → Lista unstake`);
|
|
744
|
+
|
|
745
|
+
const apUSDBalance = await getBalance(TOKENS.apUSD);
|
|
746
|
+
if (apUSDBalance === 0n) {
|
|
747
|
+
console.log(` ⚠️ No apUSD balance, skipping`);
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// Use remaining apUSD balance
|
|
752
|
+
console.log(` Requesting unstake for ${formatEther(apUSDBalance)} apUSD...`);
|
|
753
|
+
await approve(TOKENS.apUSD, apUSDBalance);
|
|
754
|
+
|
|
755
|
+
const indicesBefore = await readClient.getUserWithdrawalIndices(account.address);
|
|
756
|
+
|
|
757
|
+
const hash = await writeClient.redeemApUSDAndRequestUnstake(apUSDBalance);
|
|
758
|
+
const receipt = await publicClient.waitForTransactionReceipt({ hash });
|
|
759
|
+
await sleep(2000);
|
|
760
|
+
expect(receipt.status).toBe("success");
|
|
761
|
+
|
|
762
|
+
const indicesAfter = await readClient.getUserWithdrawalIndices(account.address);
|
|
763
|
+
expect(indicesAfter.length).toBeGreaterThan(indicesBefore.length);
|
|
764
|
+
console.log(` Unstake requested ✓ (new indices: ${indicesAfter.length - indicesBefore.length})`);
|
|
765
|
+
}, 180000);
|
|
377
766
|
});
|
|
378
767
|
});
|
package/src/abi/router.ts
CHANGED
|
@@ -27,6 +27,7 @@ export const RouterABI = [
|
|
|
27
27
|
name: "mintParams",
|
|
28
28
|
type: "tuple",
|
|
29
29
|
components: [
|
|
30
|
+
{ name: "mintXBNB", type: "bool" },
|
|
30
31
|
{ name: "minMintOut", type: "uint256" },
|
|
31
32
|
{ name: "recipient", type: "address" },
|
|
32
33
|
{ name: "deadline", type: "uint256" },
|
|
@@ -60,6 +61,7 @@ export const RouterABI = [
|
|
|
60
61
|
name: "mintParams",
|
|
61
62
|
type: "tuple",
|
|
62
63
|
components: [
|
|
64
|
+
{ name: "mintXBNB", type: "bool" },
|
|
63
65
|
{ name: "minMintOut", type: "uint256" },
|
|
64
66
|
{ name: "recipient", type: "address" },
|
|
65
67
|
{ name: "deadline", type: "uint256" },
|
package/src/router.ts
CHANGED
|
@@ -313,6 +313,7 @@ export class AspanRouterClient extends AspanRouterReadClient {
|
|
|
313
313
|
poolFee: params.swapParams.poolFee,
|
|
314
314
|
},
|
|
315
315
|
{
|
|
316
|
+
mintXBNB: false, // swapAndMintApUSD always mints apUSD
|
|
316
317
|
minMintOut: params.mintParams.minMintOut,
|
|
317
318
|
recipient: params.mintParams.recipient,
|
|
318
319
|
deadline: params.mintParams.deadline,
|
|
@@ -346,6 +347,7 @@ export class AspanRouterClient extends AspanRouterReadClient {
|
|
|
346
347
|
poolFee: params.swapParams.poolFee,
|
|
347
348
|
},
|
|
348
349
|
{
|
|
350
|
+
mintXBNB: true, // swapAndMintXBNB always mints xBNB
|
|
349
351
|
minMintOut: params.mintParams.minMintOut,
|
|
350
352
|
recipient: params.mintParams.recipient,
|
|
351
353
|
deadline: params.mintParams.deadline,
|
package/src/types.ts
CHANGED
|
@@ -262,6 +262,8 @@ export interface RouterSwapParams {
|
|
|
262
262
|
|
|
263
263
|
/** Router mint parameters */
|
|
264
264
|
export interface RouterMintParams {
|
|
265
|
+
/** true = mint xBNB, false = mint apUSD */
|
|
266
|
+
mintXBNB: boolean;
|
|
265
267
|
/** Minimum output to receive (slippage protection) */
|
|
266
268
|
minMintOut: bigint;
|
|
267
269
|
/** Recipient of minted tokens (address(0) = msg.sender) */
|