@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 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** | `0x1Ca976d2043dfb785c7F3A17535B3A38c50113f3` |
25
+ | **Router** | `0xb8a90CD2811d6DDbB4B7969d30B036574842cb6E` |
26
26
  | **ApUSD** | `0x4570047eeB5aDb4081c5d470494EB5134e34A287` |
27
27
  | **XBNB** | `0x0A0c9CD826e747D99F90D63e780B3727Da4D0d43` |
28
28
  | **SApUSD** | `0xE2BE739C4aA4126ee72D612d9548C38B1B0e5A1b` |
29
- | **wclisBNB** | `0x448f7c2fa4e5135a4a5B50879602cf3CD428e108` |
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: "0x1Ca976d2043dfb785c7F3A17535B3A38c50113f3";
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: "0x448f7c2fa4e5135a4a5B50879602cf3CD428e108";
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: "0x1Ca976d2043dfb785c7F3A17535B3A38c50113f3";
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: "0x448f7c2fa4e5135a4a5B50879602cf3CD428e108";
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
- lstAmount = lstPrice18 === 0n ? 0n : inputAmount * one / lstPrice18;
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
- outputAmount = usdValue;
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: "0x1Ca976d2043dfb785c7F3A17535B3A38c50113f3",
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: "0x448f7c2fa4e5135a4a5B50879602cf3CD428e108",
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
- lstAmount = lstPrice18 === 0n ? 0n : inputAmount * one / lstPrice18;
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
- outputAmount = usdValue;
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: "0x1Ca976d2043dfb785c7F3A17535B3A38c50113f3",
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: "0x448f7c2fa4e5135a4a5B50879602cf3CD428e108",
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.0",
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": "^2.21.0"
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
- "dotenv": "^16.3.1"
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 = "0x1Ca976d2043dfb785c7F3A17535B3A38c50113f3" as Address;
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: "0x448f7c2fa4e5135a4a5B50879602cf3CD428e108" as Address,
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.001");
46
+ const BNB_AMOUNT = parseEther("0.002");
47
47
  const USDT_AMOUNT = parseAmount("1");
48
- const WBNB_AMOUNT = parseEther("0.001");
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
- const unstakeHash = await writeClient.redeemAndRequestUnstake({
593
- redeemXBNB: true,
594
- amount: freshMinted,
595
- });
596
- const unstakeReceipt = await publicClient.waitForTransactionReceipt({ hash: unstakeHash });
597
- await sleep(2000);
598
- expect(unstakeReceipt.status).toBe("success");
599
-
600
- const indicesAfter = await readClient.getUserWithdrawalIndices(account.address);
601
- expect(indicesAfter.length).toBeGreaterThan(indicesBefore.length);
602
- console.log(` Unstake requested ✓ (new indices: ${indicesAfter.length - indicesBefore.length})`);
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
- const unstakeAmount = apUSDBalance > parseAmount("0.5") ? parseAmount("0.5") : apUSDBalance;
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: "0x1Ca976d2043dfb785c7F3A17535B3A38c50113f3" as const,
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: "0x448f7c2fa4e5135a4a5B50879602cf3CD428e108" as const,
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 (18-decimal normalized in this SDK path) -> LST
237
- lstAmount = lstPrice18 === 0n ? 0n : (inputAmount * one) / lstPrice18;
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
- outputAmount = usdValue;
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