@aspan/sdk 0.5.0 → 0.5.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 +11 -3
- package/dist/index.d.ts +11 -3
- package/dist/index.js +51 -4
- package/dist/index.mjs +51 -4
- package/package.json +7 -7
- package/src/__tests__/risk.test.ts +2 -2
- package/src/__tests__/router.test.ts +24 -14
- package/src/index.ts +5 -2
- package/src/router.ts +53 -3
- package/src/types.ts +1 -1
package/README.md
CHANGED
|
@@ -22,11 +22,11 @@ import { BSC_ADDRESSES } from "@aspan/sdk";
|
|
|
22
22
|
| Contract | Address |
|
|
23
23
|
|----------|---------|
|
|
24
24
|
| **Diamond (Main Entry)** | `0x6a11B30d3a70727d5477D6d8090e144443fA1c78` |
|
|
25
|
-
| **Router** | `
|
|
25
|
+
| **Router** | `0xb8a90CD2811d6DDbB4B7969d30B036574842cb6E` |
|
|
26
26
|
| **ApUSD** | `0x4570047eeB5aDb4081c5d470494EB5134e34A287` |
|
|
27
27
|
| **XBNB** | `0x0A0c9CD826e747D99F90D63e780B3727Da4D0d43` |
|
|
28
28
|
| **SApUSD** | `0xE2BE739C4aA4126ee72D612d9548C38B1B0e5A1b` |
|
|
29
|
-
| **wclisBNB** | `
|
|
29
|
+
| **wclisBNB** | `0xb2A0631bF0aC326fEefc201E7337E13C63Bbed07` |
|
|
30
30
|
|
|
31
31
|
## Quick Start
|
|
32
32
|
|
package/dist/index.d.mts
CHANGED
|
@@ -203,7 +203,7 @@ interface RouterSwapParams {
|
|
|
203
203
|
targetLST: Address;
|
|
204
204
|
/** Minimum LST to receive from swap (slippage protection) */
|
|
205
205
|
minLSTOut: bigint;
|
|
206
|
-
/** PancakeSwap V3 pool fee tier (500, 2500, 10000) */
|
|
206
|
+
/** PancakeSwap V3 pool fee tier (100, 500, 2500, 10000) */
|
|
207
207
|
poolFee: number;
|
|
208
208
|
}
|
|
209
209
|
/** Router mint parameters */
|
|
@@ -770,6 +770,11 @@ declare class AspanRouterReadClient {
|
|
|
770
770
|
getWBNB(): Promise<Address>;
|
|
771
771
|
getUSDT(): Promise<Address>;
|
|
772
772
|
getUSDC(): Promise<Address>;
|
|
773
|
+
/**
|
|
774
|
+
* Get stablecoin price in USD (18 decimals) from Chainlink.
|
|
775
|
+
* Returns 1e18 if feed unavailable (graceful fallback).
|
|
776
|
+
*/
|
|
777
|
+
getStablecoinPrice(token: Address): Promise<bigint>;
|
|
773
778
|
getSlisBNB(): Promise<Address>;
|
|
774
779
|
getAsBNB(): Promise<Address>;
|
|
775
780
|
getWclisBNB(): Promise<Address>;
|
|
@@ -2809,15 +2814,18 @@ declare const BPS_PRECISION = 10000n;
|
|
|
2809
2814
|
declare const PRICE_PRECISION: bigint;
|
|
2810
2815
|
declare const BSC_ADDRESSES: {
|
|
2811
2816
|
readonly diamond: "0x6a11B30d3a70727d5477D6d8090e144443fA1c78";
|
|
2812
|
-
readonly router: "
|
|
2817
|
+
readonly router: "0xb8a90CD2811d6DDbB4B7969d30B036574842cb6E";
|
|
2813
2818
|
readonly apUSD: "0x4570047eeB5aDb4081c5d470494EB5134e34A287";
|
|
2814
2819
|
readonly xBNB: "0x0A0c9CD826e747D99F90D63e780B3727Da4D0d43";
|
|
2815
2820
|
readonly sApUSD: "0x896770Dba7c0481539E25aaB56bE285ECF6D65eB";
|
|
2816
2821
|
readonly slisBNB: "0xB0b84D294e0C75A6abe60171b70edEb2EFd14A1B";
|
|
2817
2822
|
readonly asBNB: "0x77734e70b6E88b4d82fE632a168EDf6e700912b6";
|
|
2818
|
-
readonly wclisBNB: "
|
|
2823
|
+
readonly wclisBNB: "0xb2A0631bF0aC326fEefc201E7337E13C63Bbed07";
|
|
2819
2824
|
readonly USDT: "0x55d398326f99059fF775485246999027B3197955";
|
|
2820
2825
|
readonly USDC: "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d";
|
|
2826
|
+
/** Chainlink price feeds (BSC Mainnet) */
|
|
2827
|
+
readonly USDT_USD_FEED: "0xB97Ad0E74fa7d920791E90258A6E2085088b4320";
|
|
2828
|
+
readonly USDC_USD_FEED: "0x51597f405303C4377E36123cBc172b13269EA163";
|
|
2821
2829
|
readonly WBNB: "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c";
|
|
2822
2830
|
};
|
|
2823
2831
|
declare const PHAROS_ADDRESSES: {
|
package/dist/index.d.ts
CHANGED
|
@@ -203,7 +203,7 @@ interface RouterSwapParams {
|
|
|
203
203
|
targetLST: Address;
|
|
204
204
|
/** Minimum LST to receive from swap (slippage protection) */
|
|
205
205
|
minLSTOut: bigint;
|
|
206
|
-
/** PancakeSwap V3 pool fee tier (500, 2500, 10000) */
|
|
206
|
+
/** PancakeSwap V3 pool fee tier (100, 500, 2500, 10000) */
|
|
207
207
|
poolFee: number;
|
|
208
208
|
}
|
|
209
209
|
/** Router mint parameters */
|
|
@@ -770,6 +770,11 @@ declare class AspanRouterReadClient {
|
|
|
770
770
|
getWBNB(): Promise<Address>;
|
|
771
771
|
getUSDT(): Promise<Address>;
|
|
772
772
|
getUSDC(): Promise<Address>;
|
|
773
|
+
/**
|
|
774
|
+
* Get stablecoin price in USD (18 decimals) from Chainlink.
|
|
775
|
+
* Returns 1e18 if feed unavailable (graceful fallback).
|
|
776
|
+
*/
|
|
777
|
+
getStablecoinPrice(token: Address): Promise<bigint>;
|
|
773
778
|
getSlisBNB(): Promise<Address>;
|
|
774
779
|
getAsBNB(): Promise<Address>;
|
|
775
780
|
getWclisBNB(): Promise<Address>;
|
|
@@ -2809,15 +2814,18 @@ declare const BPS_PRECISION = 10000n;
|
|
|
2809
2814
|
declare const PRICE_PRECISION: bigint;
|
|
2810
2815
|
declare const BSC_ADDRESSES: {
|
|
2811
2816
|
readonly diamond: "0x6a11B30d3a70727d5477D6d8090e144443fA1c78";
|
|
2812
|
-
readonly router: "
|
|
2817
|
+
readonly router: "0xb8a90CD2811d6DDbB4B7969d30B036574842cb6E";
|
|
2813
2818
|
readonly apUSD: "0x4570047eeB5aDb4081c5d470494EB5134e34A287";
|
|
2814
2819
|
readonly xBNB: "0x0A0c9CD826e747D99F90D63e780B3727Da4D0d43";
|
|
2815
2820
|
readonly sApUSD: "0x896770Dba7c0481539E25aaB56bE285ECF6D65eB";
|
|
2816
2821
|
readonly slisBNB: "0xB0b84D294e0C75A6abe60171b70edEb2EFd14A1B";
|
|
2817
2822
|
readonly asBNB: "0x77734e70b6E88b4d82fE632a168EDf6e700912b6";
|
|
2818
|
-
readonly wclisBNB: "
|
|
2823
|
+
readonly wclisBNB: "0xb2A0631bF0aC326fEefc201E7337E13C63Bbed07";
|
|
2819
2824
|
readonly USDT: "0x55d398326f99059fF775485246999027B3197955";
|
|
2820
2825
|
readonly USDC: "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d";
|
|
2826
|
+
/** Chainlink price feeds (BSC Mainnet) */
|
|
2827
|
+
readonly USDT_USD_FEED: "0xB97Ad0E74fa7d920791E90258A6E2085088b4320";
|
|
2828
|
+
readonly USDC_USD_FEED: "0x51597f405303C4377E36123cBc172b13269EA163";
|
|
2821
2829
|
readonly WBNB: "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c";
|
|
2822
2830
|
};
|
|
2823
2831
|
declare const PHAROS_ADDRESSES: {
|
package/dist/index.js
CHANGED
|
@@ -2384,6 +2384,21 @@ var RouterABI = [
|
|
|
2384
2384
|
];
|
|
2385
2385
|
|
|
2386
2386
|
// src/router.ts
|
|
2387
|
+
var ChainlinkABI = [
|
|
2388
|
+
{
|
|
2389
|
+
type: "function",
|
|
2390
|
+
name: "latestRoundData",
|
|
2391
|
+
inputs: [],
|
|
2392
|
+
outputs: [
|
|
2393
|
+
{ name: "roundId", type: "uint80" },
|
|
2394
|
+
{ name: "answer", type: "int256" },
|
|
2395
|
+
{ name: "startedAt", type: "uint256" },
|
|
2396
|
+
{ name: "updatedAt", type: "uint256" },
|
|
2397
|
+
{ name: "answeredInRound", type: "uint80" }
|
|
2398
|
+
],
|
|
2399
|
+
stateMutability: "view"
|
|
2400
|
+
}
|
|
2401
|
+
];
|
|
2387
2402
|
var AspanRouterReadClient = class {
|
|
2388
2403
|
publicClient;
|
|
2389
2404
|
routerAddress;
|
|
@@ -2539,7 +2554,9 @@ var AspanRouterReadClient = class {
|
|
|
2539
2554
|
const usdValue = inputAmount * bnbPrice18 / one;
|
|
2540
2555
|
lstAmount = lstPrice18 === 0n ? 0n : usdValue * one / lstPrice18;
|
|
2541
2556
|
} else if (inNorm === usdt.toLowerCase() || inNorm === usdc.toLowerCase()) {
|
|
2542
|
-
|
|
2557
|
+
const stablePrice18 = await this.getStablecoinPrice(inputToken);
|
|
2558
|
+
const usdValue = inputAmount * stablePrice18 / one;
|
|
2559
|
+
lstAmount = lstPrice18 === 0n ? 0n : usdValue * one / lstPrice18;
|
|
2543
2560
|
} else {
|
|
2544
2561
|
throw new Error("Unsupported input token for SDK preview");
|
|
2545
2562
|
}
|
|
@@ -2585,7 +2602,8 @@ var AspanRouterReadClient = class {
|
|
|
2585
2602
|
if (outNorm === import_viem2.zeroAddress.toLowerCase() || outNorm === wbnb.toLowerCase()) {
|
|
2586
2603
|
outputAmount = bnbPrice18 === 0n ? 0n : usdValue * one / bnbPrice18;
|
|
2587
2604
|
} else if (outNorm === usdt.toLowerCase() || outNorm === usdc.toLowerCase()) {
|
|
2588
|
-
|
|
2605
|
+
const stablePrice18 = await this.getStablecoinPrice(outputToken);
|
|
2606
|
+
outputAmount = stablePrice18 === 0n ? 0n : usdValue * one / stablePrice18;
|
|
2589
2607
|
} else {
|
|
2590
2608
|
throw new Error("Unsupported output token for SDK preview");
|
|
2591
2609
|
}
|
|
@@ -2650,6 +2668,32 @@ var AspanRouterReadClient = class {
|
|
|
2650
2668
|
})
|
|
2651
2669
|
);
|
|
2652
2670
|
}
|
|
2671
|
+
/**
|
|
2672
|
+
* Get stablecoin price in USD (18 decimals) from Chainlink.
|
|
2673
|
+
* Returns 1e18 if feed unavailable (graceful fallback).
|
|
2674
|
+
*/
|
|
2675
|
+
async getStablecoinPrice(token) {
|
|
2676
|
+
const usdt = await this.getUSDT();
|
|
2677
|
+
const usdc = await this.getUSDC();
|
|
2678
|
+
const norm = token.toLowerCase();
|
|
2679
|
+
let feed = null;
|
|
2680
|
+
if (norm === usdt.toLowerCase()) {
|
|
2681
|
+
feed = BSC_ADDRESSES.USDT_USD_FEED;
|
|
2682
|
+
} else if (norm === usdc.toLowerCase()) {
|
|
2683
|
+
feed = BSC_ADDRESSES.USDC_USD_FEED;
|
|
2684
|
+
}
|
|
2685
|
+
if (!feed) return 10n ** 18n;
|
|
2686
|
+
try {
|
|
2687
|
+
const [, answer] = await this.publicClient.readContract({
|
|
2688
|
+
address: feed,
|
|
2689
|
+
abi: ChainlinkABI,
|
|
2690
|
+
functionName: "latestRoundData"
|
|
2691
|
+
});
|
|
2692
|
+
return BigInt(answer) * 10n ** 10n;
|
|
2693
|
+
} catch {
|
|
2694
|
+
return 10n ** 18n;
|
|
2695
|
+
}
|
|
2696
|
+
}
|
|
2653
2697
|
async getSlisBNB() {
|
|
2654
2698
|
return this._getCachedAddress(
|
|
2655
2699
|
"slisBNB",
|
|
@@ -2954,17 +2998,20 @@ var BPS_PRECISION = 10000n;
|
|
|
2954
2998
|
var PRICE_PRECISION = 10n ** 8n;
|
|
2955
2999
|
var BSC_ADDRESSES = {
|
|
2956
3000
|
diamond: "0x6a11B30d3a70727d5477D6d8090e144443fA1c78",
|
|
2957
|
-
router: "
|
|
3001
|
+
router: "0xb8a90CD2811d6DDbB4B7969d30B036574842cb6E",
|
|
2958
3002
|
apUSD: "0x4570047eeB5aDb4081c5d470494EB5134e34A287",
|
|
2959
3003
|
xBNB: "0x0A0c9CD826e747D99F90D63e780B3727Da4D0d43",
|
|
2960
3004
|
sApUSD: "0x896770Dba7c0481539E25aaB56bE285ECF6D65eB",
|
|
2961
3005
|
// LSTs
|
|
2962
3006
|
slisBNB: "0xB0b84D294e0C75A6abe60171b70edEb2EFd14A1B",
|
|
2963
3007
|
asBNB: "0x77734e70b6E88b4d82fE632a168EDf6e700912b6",
|
|
2964
|
-
wclisBNB: "
|
|
3008
|
+
wclisBNB: "0xb2A0631bF0aC326fEefc201E7337E13C63Bbed07",
|
|
2965
3009
|
// Stablecoins
|
|
2966
3010
|
USDT: "0x55d398326f99059fF775485246999027B3197955",
|
|
2967
3011
|
USDC: "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d",
|
|
3012
|
+
/** Chainlink price feeds (BSC Mainnet) */
|
|
3013
|
+
USDT_USD_FEED: "0xB97Ad0E74fa7d920791E90258A6E2085088b4320",
|
|
3014
|
+
USDC_USD_FEED: "0x51597f405303C4377E36123cBc172b13269EA163",
|
|
2968
3015
|
WBNB: "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c"
|
|
2969
3016
|
};
|
|
2970
3017
|
var PHAROS_ADDRESSES = {
|
package/dist/index.mjs
CHANGED
|
@@ -2331,6 +2331,21 @@ var RouterABI = [
|
|
|
2331
2331
|
];
|
|
2332
2332
|
|
|
2333
2333
|
// src/router.ts
|
|
2334
|
+
var ChainlinkABI = [
|
|
2335
|
+
{
|
|
2336
|
+
type: "function",
|
|
2337
|
+
name: "latestRoundData",
|
|
2338
|
+
inputs: [],
|
|
2339
|
+
outputs: [
|
|
2340
|
+
{ name: "roundId", type: "uint80" },
|
|
2341
|
+
{ name: "answer", type: "int256" },
|
|
2342
|
+
{ name: "startedAt", type: "uint256" },
|
|
2343
|
+
{ name: "updatedAt", type: "uint256" },
|
|
2344
|
+
{ name: "answeredInRound", type: "uint80" }
|
|
2345
|
+
],
|
|
2346
|
+
stateMutability: "view"
|
|
2347
|
+
}
|
|
2348
|
+
];
|
|
2334
2349
|
var AspanRouterReadClient = class {
|
|
2335
2350
|
publicClient;
|
|
2336
2351
|
routerAddress;
|
|
@@ -2486,7 +2501,9 @@ var AspanRouterReadClient = class {
|
|
|
2486
2501
|
const usdValue = inputAmount * bnbPrice18 / one;
|
|
2487
2502
|
lstAmount = lstPrice18 === 0n ? 0n : usdValue * one / lstPrice18;
|
|
2488
2503
|
} else if (inNorm === usdt.toLowerCase() || inNorm === usdc.toLowerCase()) {
|
|
2489
|
-
|
|
2504
|
+
const stablePrice18 = await this.getStablecoinPrice(inputToken);
|
|
2505
|
+
const usdValue = inputAmount * stablePrice18 / one;
|
|
2506
|
+
lstAmount = lstPrice18 === 0n ? 0n : usdValue * one / lstPrice18;
|
|
2490
2507
|
} else {
|
|
2491
2508
|
throw new Error("Unsupported input token for SDK preview");
|
|
2492
2509
|
}
|
|
@@ -2532,7 +2549,8 @@ var AspanRouterReadClient = class {
|
|
|
2532
2549
|
if (outNorm === zeroAddress.toLowerCase() || outNorm === wbnb.toLowerCase()) {
|
|
2533
2550
|
outputAmount = bnbPrice18 === 0n ? 0n : usdValue * one / bnbPrice18;
|
|
2534
2551
|
} else if (outNorm === usdt.toLowerCase() || outNorm === usdc.toLowerCase()) {
|
|
2535
|
-
|
|
2552
|
+
const stablePrice18 = await this.getStablecoinPrice(outputToken);
|
|
2553
|
+
outputAmount = stablePrice18 === 0n ? 0n : usdValue * one / stablePrice18;
|
|
2536
2554
|
} else {
|
|
2537
2555
|
throw new Error("Unsupported output token for SDK preview");
|
|
2538
2556
|
}
|
|
@@ -2597,6 +2615,32 @@ var AspanRouterReadClient = class {
|
|
|
2597
2615
|
})
|
|
2598
2616
|
);
|
|
2599
2617
|
}
|
|
2618
|
+
/**
|
|
2619
|
+
* Get stablecoin price in USD (18 decimals) from Chainlink.
|
|
2620
|
+
* Returns 1e18 if feed unavailable (graceful fallback).
|
|
2621
|
+
*/
|
|
2622
|
+
async getStablecoinPrice(token) {
|
|
2623
|
+
const usdt = await this.getUSDT();
|
|
2624
|
+
const usdc = await this.getUSDC();
|
|
2625
|
+
const norm = token.toLowerCase();
|
|
2626
|
+
let feed = null;
|
|
2627
|
+
if (norm === usdt.toLowerCase()) {
|
|
2628
|
+
feed = BSC_ADDRESSES.USDT_USD_FEED;
|
|
2629
|
+
} else if (norm === usdc.toLowerCase()) {
|
|
2630
|
+
feed = BSC_ADDRESSES.USDC_USD_FEED;
|
|
2631
|
+
}
|
|
2632
|
+
if (!feed) return 10n ** 18n;
|
|
2633
|
+
try {
|
|
2634
|
+
const [, answer] = await this.publicClient.readContract({
|
|
2635
|
+
address: feed,
|
|
2636
|
+
abi: ChainlinkABI,
|
|
2637
|
+
functionName: "latestRoundData"
|
|
2638
|
+
});
|
|
2639
|
+
return BigInt(answer) * 10n ** 10n;
|
|
2640
|
+
} catch {
|
|
2641
|
+
return 10n ** 18n;
|
|
2642
|
+
}
|
|
2643
|
+
}
|
|
2600
2644
|
async getSlisBNB() {
|
|
2601
2645
|
return this._getCachedAddress(
|
|
2602
2646
|
"slisBNB",
|
|
@@ -2901,17 +2945,20 @@ var BPS_PRECISION = 10000n;
|
|
|
2901
2945
|
var PRICE_PRECISION = 10n ** 8n;
|
|
2902
2946
|
var BSC_ADDRESSES = {
|
|
2903
2947
|
diamond: "0x6a11B30d3a70727d5477D6d8090e144443fA1c78",
|
|
2904
|
-
router: "
|
|
2948
|
+
router: "0xb8a90CD2811d6DDbB4B7969d30B036574842cb6E",
|
|
2905
2949
|
apUSD: "0x4570047eeB5aDb4081c5d470494EB5134e34A287",
|
|
2906
2950
|
xBNB: "0x0A0c9CD826e747D99F90D63e780B3727Da4D0d43",
|
|
2907
2951
|
sApUSD: "0x896770Dba7c0481539E25aaB56bE285ECF6D65eB",
|
|
2908
2952
|
// LSTs
|
|
2909
2953
|
slisBNB: "0xB0b84D294e0C75A6abe60171b70edEb2EFd14A1B",
|
|
2910
2954
|
asBNB: "0x77734e70b6E88b4d82fE632a168EDf6e700912b6",
|
|
2911
|
-
wclisBNB: "
|
|
2955
|
+
wclisBNB: "0xb2A0631bF0aC326fEefc201E7337E13C63Bbed07",
|
|
2912
2956
|
// Stablecoins
|
|
2913
2957
|
USDT: "0x55d398326f99059fF775485246999027B3197955",
|
|
2914
2958
|
USDC: "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d",
|
|
2959
|
+
/** Chainlink price feeds (BSC Mainnet) */
|
|
2960
|
+
USDT_USD_FEED: "0xB97Ad0E74fa7d920791E90258A6E2085088b4320",
|
|
2961
|
+
USDC_USD_FEED: "0x51597f405303C4377E36123cBc172b13269EA163",
|
|
2915
2962
|
WBNB: "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c"
|
|
2916
2963
|
};
|
|
2917
2964
|
var PHAROS_ADDRESSES = {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aspan/sdk",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.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",
|
|
@@ -45,18 +45,18 @@
|
|
|
45
45
|
"author": "",
|
|
46
46
|
"license": "MIT",
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"viem": "
|
|
48
|
+
"viem": ">=2.0.0"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"@types/node": "^20.10.0",
|
|
52
|
-
"tsup": "^8.0.1",
|
|
53
|
-
"typescript": "^5.3.0",
|
|
54
|
-
"vitest": "^1.0.0",
|
|
55
|
-
"eslint": "^8.55.0",
|
|
56
52
|
"@typescript-eslint/eslint-plugin": "^6.13.0",
|
|
57
53
|
"@typescript-eslint/parser": "^6.13.0",
|
|
54
|
+
"dotenv": "^16.3.1",
|
|
55
|
+
"eslint": "^8.55.0",
|
|
56
|
+
"tsup": "^8.0.1",
|
|
58
57
|
"tsx": "^4.7.0",
|
|
59
|
-
"
|
|
58
|
+
"typescript": "^5.3.0",
|
|
59
|
+
"vitest": "^1.6.1"
|
|
60
60
|
},
|
|
61
61
|
"peerDependencies": {
|
|
62
62
|
"viem": ">=2.0.0"
|
|
@@ -61,7 +61,7 @@ const BSC_RPC_URL = process.env.BSC_RPC_URL || "https://bsc-dataseed.binance.org
|
|
|
61
61
|
const ANVIL_DEFAULT_KEY = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80";
|
|
62
62
|
|
|
63
63
|
const DIAMOND = "0x6a11B30d3a70727d5477D6d8090e144443fA1c78" as Address;
|
|
64
|
-
const ROUTER = "
|
|
64
|
+
const ROUTER = "0xb8a90CD2811d6DDbB4B7969d30B036574842cb6E" as Address;
|
|
65
65
|
|
|
66
66
|
const TOKENS = {
|
|
67
67
|
WBNB: "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c" as Address,
|
|
@@ -69,7 +69,7 @@ const TOKENS = {
|
|
|
69
69
|
USDC: "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d" as Address,
|
|
70
70
|
slisBNB: "0xB0b84D294e0C75A6abe60171b70edEb2EFd14A1B" as Address,
|
|
71
71
|
asBNB: "0x77734e70b6E88b4d82fE632a168EDf6e700912b6" as Address,
|
|
72
|
-
wclisBNB: "
|
|
72
|
+
wclisBNB: "0xb2A0631bF0aC326fEefc201E7337E13C63Bbed07" as Address,
|
|
73
73
|
apUSD: "0x4570047eeB5aDb4081c5d470494EB5134e34A287" as Address,
|
|
74
74
|
xBNB: "0x0A0c9CD826e747D99F90D63e780B3727Da4D0d43" as Address,
|
|
75
75
|
sApUSD: "0xE2BE739C4aA4126ee72D612d9548C38B1B0e5A1b" as Address,
|
|
@@ -43,9 +43,9 @@ const TOKENS = {
|
|
|
43
43
|
};
|
|
44
44
|
|
|
45
45
|
// Minimal amounts — just enough to verify path connectivity
|
|
46
|
-
const BNB_AMOUNT = parseEther("0.
|
|
46
|
+
const BNB_AMOUNT = parseEther("0.002");
|
|
47
47
|
const USDT_AMOUNT = parseAmount("1");
|
|
48
|
-
const WBNB_AMOUNT = parseEther("0.
|
|
48
|
+
const WBNB_AMOUNT = parseEther("0.002");
|
|
49
49
|
const USDC_AMOUNT = parseAmount("1");
|
|
50
50
|
|
|
51
51
|
const ERC20_ABI = [
|
|
@@ -589,17 +589,26 @@ describe("AspanRouter SDK", () => {
|
|
|
589
589
|
|
|
590
590
|
const indicesBefore = await readClient.getUserWithdrawalIndices(account.address);
|
|
591
591
|
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
592
|
+
try {
|
|
593
|
+
const unstakeHash = await writeClient.redeemAndRequestUnstake({
|
|
594
|
+
redeemXBNB: true,
|
|
595
|
+
amount: freshMinted,
|
|
596
|
+
});
|
|
597
|
+
const unstakeReceipt = await publicClient.waitForTransactionReceipt({ hash: unstakeHash });
|
|
598
|
+
await sleep(2000);
|
|
599
|
+
expect(unstakeReceipt.status).toBe("success");
|
|
600
|
+
|
|
601
|
+
const indicesAfter = await readClient.getUserWithdrawalIndices(account.address);
|
|
602
|
+
expect(indicesAfter.length).toBeGreaterThan(indicesBefore.length);
|
|
603
|
+
console.log(` Unstake requested ✓ (new indices: ${indicesAfter.length - indicesBefore.length})`);
|
|
604
|
+
} catch (err: any) {
|
|
605
|
+
const msg = err.shortMessage || err.message || "";
|
|
606
|
+
if (msg.includes("too small")) {
|
|
607
|
+
console.log(` ⚠️ Amount too small for Lista unstake (min ~0.001 BNB equivalent), skipped`);
|
|
608
|
+
} else {
|
|
609
|
+
throw err;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
603
612
|
}, 180000);
|
|
604
613
|
|
|
605
614
|
// Test 9: stakeAndMint xBNB
|
|
@@ -854,7 +863,8 @@ describe("AspanRouter SDK", () => {
|
|
|
854
863
|
return;
|
|
855
864
|
}
|
|
856
865
|
|
|
857
|
-
|
|
866
|
+
// Lista requires minimum ~0.001 BNB equivalent; at ~$670/BNB, that's ~$0.67 apUSD min
|
|
867
|
+
const unstakeAmount = apUSDBalance > parseAmount("1.0") ? parseAmount("1.0") : apUSDBalance;
|
|
858
868
|
console.log(` Requesting unstake for ${formatAmount(unstakeAmount)} apUSD (of ${formatAmount(apUSDBalance)} total)...`);
|
|
859
869
|
await approve(TOKENS.apUSD, unstakeAmount);
|
|
860
870
|
|
package/src/index.ts
CHANGED
|
@@ -116,17 +116,20 @@ export const PRICE_PRECISION = 10n ** 8n; // Chainlink price precision
|
|
|
116
116
|
// ============ Contract Addresses (BSC Mainnet) ============
|
|
117
117
|
export const BSC_ADDRESSES = {
|
|
118
118
|
diamond: "0x6a11B30d3a70727d5477D6d8090e144443fA1c78" as const,
|
|
119
|
-
router: "
|
|
119
|
+
router: "0xb8a90CD2811d6DDbB4B7969d30B036574842cb6E" as const,
|
|
120
120
|
apUSD: "0x4570047eeB5aDb4081c5d470494EB5134e34A287" as const,
|
|
121
121
|
xBNB: "0x0A0c9CD826e747D99F90D63e780B3727Da4D0d43" as const,
|
|
122
122
|
sApUSD: "0x896770Dba7c0481539E25aaB56bE285ECF6D65eB" as const,
|
|
123
123
|
// LSTs
|
|
124
124
|
slisBNB: "0xB0b84D294e0C75A6abe60171b70edEb2EFd14A1B" as const,
|
|
125
125
|
asBNB: "0x77734e70b6E88b4d82fE632a168EDf6e700912b6" as const,
|
|
126
|
-
wclisBNB: "
|
|
126
|
+
wclisBNB: "0xb2A0631bF0aC326fEefc201E7337E13C63Bbed07" as const,
|
|
127
127
|
// Stablecoins
|
|
128
128
|
USDT: "0x55d398326f99059fF775485246999027B3197955" as const,
|
|
129
129
|
USDC: "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d" as const,
|
|
130
|
+
/** Chainlink price feeds (BSC Mainnet) */
|
|
131
|
+
USDT_USD_FEED: "0xB97Ad0E74fa7d920791E90258A6E2085088b4320" as const,
|
|
132
|
+
USDC_USD_FEED: "0x51597f405303C4377E36123cBc172b13269EA163" as const,
|
|
130
133
|
WBNB: "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c" as const,
|
|
131
134
|
} as const;
|
|
132
135
|
|
package/src/router.ts
CHANGED
|
@@ -19,8 +19,26 @@ import {
|
|
|
19
19
|
} from "viem";
|
|
20
20
|
import { bsc, bscTestnet } from "viem/chains";
|
|
21
21
|
import { pharosTestnet, CHAIN_IDS, getChainById } from "./client";
|
|
22
|
+
import { BSC_ADDRESSES } from "./index";
|
|
22
23
|
import { RouterABI } from "./abi/router";
|
|
23
24
|
import { DiamondABI } from "./abi/diamond";
|
|
25
|
+
|
|
26
|
+
/** Chainlink AggregatorV3 — only latestRoundData needed */
|
|
27
|
+
const ChainlinkABI = [
|
|
28
|
+
{
|
|
29
|
+
type: "function",
|
|
30
|
+
name: "latestRoundData",
|
|
31
|
+
inputs: [],
|
|
32
|
+
outputs: [
|
|
33
|
+
{ name: "roundId", type: "uint80" },
|
|
34
|
+
{ name: "answer", type: "int256" },
|
|
35
|
+
{ name: "startedAt", type: "uint256" },
|
|
36
|
+
{ name: "updatedAt", type: "uint256" },
|
|
37
|
+
{ name: "answeredInRound", type: "uint80" },
|
|
38
|
+
],
|
|
39
|
+
stateMutability: "view",
|
|
40
|
+
},
|
|
41
|
+
] as const;
|
|
24
42
|
import type {
|
|
25
43
|
SwapAndMintParams,
|
|
26
44
|
StakeAndMintParams,
|
|
@@ -233,8 +251,10 @@ export class AspanRouterReadClient {
|
|
|
233
251
|
const usdValue = (inputAmount * bnbPrice18) / one;
|
|
234
252
|
lstAmount = lstPrice18 === 0n ? 0n : (usdValue * one) / lstPrice18;
|
|
235
253
|
} else if (inNorm === usdt.toLowerCase() || inNorm === usdc.toLowerCase()) {
|
|
236
|
-
// stablecoin
|
|
237
|
-
|
|
254
|
+
// stablecoin -> USD (via Chainlink) -> LST
|
|
255
|
+
const stablePrice18 = await this.getStablecoinPrice(inputToken);
|
|
256
|
+
const usdValue = (inputAmount * stablePrice18) / one;
|
|
257
|
+
lstAmount = lstPrice18 === 0n ? 0n : (usdValue * one) / lstPrice18;
|
|
238
258
|
} else {
|
|
239
259
|
throw new Error("Unsupported input token for SDK preview");
|
|
240
260
|
}
|
|
@@ -292,7 +312,9 @@ export class AspanRouterReadClient {
|
|
|
292
312
|
if (outNorm === zeroAddress.toLowerCase() || outNorm === wbnb.toLowerCase()) {
|
|
293
313
|
outputAmount = bnbPrice18 === 0n ? 0n : (usdValue * one) / bnbPrice18;
|
|
294
314
|
} else if (outNorm === usdt.toLowerCase() || outNorm === usdc.toLowerCase()) {
|
|
295
|
-
|
|
315
|
+
// USD value -> stablecoin amount (via Chainlink price)
|
|
316
|
+
const stablePrice18 = await this.getStablecoinPrice(outputToken);
|
|
317
|
+
outputAmount = stablePrice18 === 0n ? 0n : (usdValue * one) / stablePrice18;
|
|
296
318
|
} else {
|
|
297
319
|
throw new Error("Unsupported output token for SDK preview");
|
|
298
320
|
}
|
|
@@ -365,6 +387,34 @@ export class AspanRouterReadClient {
|
|
|
365
387
|
);
|
|
366
388
|
}
|
|
367
389
|
|
|
390
|
+
/**
|
|
391
|
+
* Get stablecoin price in USD (18 decimals) from Chainlink.
|
|
392
|
+
* Returns 1e18 if feed unavailable (graceful fallback).
|
|
393
|
+
*/
|
|
394
|
+
async getStablecoinPrice(token: Address): Promise<bigint> {
|
|
395
|
+
const usdt = await this.getUSDT();
|
|
396
|
+
const usdc = await this.getUSDC();
|
|
397
|
+
const norm = token.toLowerCase();
|
|
398
|
+
let feed: Address | null = null;
|
|
399
|
+
if (norm === usdt.toLowerCase()) {
|
|
400
|
+
feed = BSC_ADDRESSES.USDT_USD_FEED as Address;
|
|
401
|
+
} else if (norm === usdc.toLowerCase()) {
|
|
402
|
+
feed = BSC_ADDRESSES.USDC_USD_FEED as Address;
|
|
403
|
+
}
|
|
404
|
+
if (!feed) return 10n ** 18n; // not a stablecoin, assume 1:1
|
|
405
|
+
try {
|
|
406
|
+
const [, answer] = await this.publicClient.readContract({
|
|
407
|
+
address: feed,
|
|
408
|
+
abi: ChainlinkABI,
|
|
409
|
+
functionName: "latestRoundData",
|
|
410
|
+
});
|
|
411
|
+
// Chainlink returns 8 decimals, normalize to 18
|
|
412
|
+
return BigInt(answer) * 10n ** 10n;
|
|
413
|
+
} catch {
|
|
414
|
+
return 10n ** 18n; // fallback: 1 USD
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
368
418
|
async getSlisBNB(): Promise<Address> {
|
|
369
419
|
return this._getCachedAddress("slisBNB", async () =>
|
|
370
420
|
this.publicClient.readContract({
|
package/src/types.ts
CHANGED
|
@@ -251,7 +251,7 @@ export interface RouterSwapParams {
|
|
|
251
251
|
targetLST: Address;
|
|
252
252
|
/** Minimum LST to receive from swap (slippage protection) */
|
|
253
253
|
minLSTOut: bigint;
|
|
254
|
-
/** PancakeSwap V3 pool fee tier (500, 2500, 10000) */
|
|
254
|
+
/** PancakeSwap V3 pool fee tier (100, 500, 2500, 10000) */
|
|
255
255
|
poolFee: number;
|
|
256
256
|
}
|
|
257
257
|
|