@aspan/sdk 0.4.4 → 0.4.5

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/dist/index.d.mts CHANGED
@@ -457,11 +457,28 @@ declare class AspanReadClient {
457
457
  getOracleBounds(priceFeed: Address): Promise<OracleBounds>;
458
458
  getShares(user: Address): Promise<bigint>;
459
459
  getBalance(user: Address): Promise<bigint>;
460
+ /**
461
+ * Get user's underlying balances (apUSD + xBNB) from sApUSD vault
462
+ */
463
+ getBalanceMulti(user: Address): Promise<{
464
+ apUSDBalance: bigint;
465
+ xBNBBalance: bigint;
466
+ }>;
460
467
  getUserStabilityPoolPosition(user: Address): Promise<UserStabilityPoolPosition>;
461
468
  getExchangeRate(): Promise<bigint>;
462
469
  getTotalStaked(): Promise<bigint>;
463
470
  previewDeposit(assets: bigint): Promise<bigint>;
464
471
  previewRedeem(shares: bigint): Promise<bigint>;
472
+ /**
473
+ * Preview withdraw with multi-asset support (apUSD + xBNB)
474
+ * @param shares Amount of sApUSD shares to redeem
475
+ * @returns Object with apUSD and xBNB amounts, plus whether vault has xBNB
476
+ */
477
+ previewRedeemMulti(shares: bigint): Promise<{
478
+ apUSD: bigint;
479
+ xBNB: bigint;
480
+ hasXBNB: boolean;
481
+ }>;
465
482
  getPendingYield(): Promise<bigint>;
466
483
  getTotalYieldGenerated(): Promise<bigint>;
467
484
  getLSTYieldInfo(lstToken: Address): Promise<LSTYieldInfo>;
@@ -474,6 +491,39 @@ declare class AspanReadClient {
474
491
  getTreasury(): Promise<Address>;
475
492
  getFeeTierCount(): Promise<bigint>;
476
493
  getFeeTier(index: bigint): Promise<FeeTier>;
494
+ /**
495
+ * Get all fee tiers
496
+ * @returns Array of fee tiers sorted by minCR descending
497
+ */
498
+ getAllFeeTiers(): Promise<FeeTier[]>;
499
+ /**
500
+ * Get comprehensive vault (sApUSD) info for frontend display
501
+ * @returns Vault state including TVL, exchange rate, xBNB status
502
+ */
503
+ getVaultInfo(): Promise<{
504
+ totalSupply: bigint;
505
+ totalAssets: bigint;
506
+ exchangeRate: bigint;
507
+ hasXBNB: boolean;
508
+ xBNBAmount: bigint;
509
+ }>;
510
+ /**
511
+ * Get full protocol dashboard data in one call (for frontend overview page)
512
+ * Batches all key metrics into a single multicall
513
+ */
514
+ getProtocolOverview(): Promise<{
515
+ collateralRatio: bigint;
516
+ tvlBNB: bigint;
517
+ tvlUSD: bigint;
518
+ apUSDSupply: bigint;
519
+ xBNBSupply: bigint;
520
+ xBNBPriceUSD: bigint;
521
+ xBNBPriceBNB: bigint;
522
+ bnbPriceUSD: bigint;
523
+ currentFees: CurrentFeeTier;
524
+ stabilityMode: StabilityModeInfo;
525
+ totalStaked: bigint;
526
+ }>;
477
527
  getCurrentFeeTier(): Promise<CurrentFeeTier>;
478
528
  getMaxPriceAge(): Promise<bigint>;
479
529
  getMinDepositPeriod(): Promise<bigint>;
@@ -1529,6 +1579,16 @@ declare const DiamondABI: readonly [{
1529
1579
  readonly internalType: "address";
1530
1580
  }];
1531
1581
  readonly stateMutability: "view";
1582
+ }, {
1583
+ readonly type: "function";
1584
+ readonly name: "setSApUSD";
1585
+ readonly inputs: readonly [{
1586
+ readonly name: "_sApUSD";
1587
+ readonly type: "address";
1588
+ readonly internalType: "address";
1589
+ }];
1590
+ readonly outputs: readonly [];
1591
+ readonly stateMutability: "nonpayable";
1532
1592
  }, {
1533
1593
  readonly type: "function";
1534
1594
  readonly name: "getStabilityPool";
@@ -1559,6 +1619,41 @@ declare const DiamondABI: readonly [{
1559
1619
  readonly internalType: "uint256";
1560
1620
  }];
1561
1621
  readonly stateMutability: "view";
1622
+ }, {
1623
+ readonly type: "function";
1624
+ readonly name: "getAllFeeTiers";
1625
+ readonly inputs: readonly [];
1626
+ readonly outputs: readonly [{
1627
+ readonly name: "";
1628
+ readonly type: "tuple[]";
1629
+ readonly internalType: "struct LibAppStorage.FeeTier[]";
1630
+ readonly components: readonly [{
1631
+ readonly name: "minCR";
1632
+ readonly type: "uint256";
1633
+ readonly internalType: "uint256";
1634
+ }, {
1635
+ readonly name: "apUSDMintFee";
1636
+ readonly type: "uint16";
1637
+ readonly internalType: "uint16";
1638
+ }, {
1639
+ readonly name: "apUSDRedeemFee";
1640
+ readonly type: "uint16";
1641
+ readonly internalType: "uint16";
1642
+ }, {
1643
+ readonly name: "xBNBMintFee";
1644
+ readonly type: "uint16";
1645
+ readonly internalType: "uint16";
1646
+ }, {
1647
+ readonly name: "xBNBRedeemFee";
1648
+ readonly type: "uint16";
1649
+ readonly internalType: "uint16";
1650
+ }, {
1651
+ readonly name: "apUSDMintDisabled";
1652
+ readonly type: "bool";
1653
+ readonly internalType: "bool";
1654
+ }];
1655
+ }];
1656
+ readonly stateMutability: "view";
1562
1657
  }, {
1563
1658
  readonly type: "function";
1564
1659
  readonly name: "getFeeTier";
package/dist/index.d.ts CHANGED
@@ -457,11 +457,28 @@ declare class AspanReadClient {
457
457
  getOracleBounds(priceFeed: Address): Promise<OracleBounds>;
458
458
  getShares(user: Address): Promise<bigint>;
459
459
  getBalance(user: Address): Promise<bigint>;
460
+ /**
461
+ * Get user's underlying balances (apUSD + xBNB) from sApUSD vault
462
+ */
463
+ getBalanceMulti(user: Address): Promise<{
464
+ apUSDBalance: bigint;
465
+ xBNBBalance: bigint;
466
+ }>;
460
467
  getUserStabilityPoolPosition(user: Address): Promise<UserStabilityPoolPosition>;
461
468
  getExchangeRate(): Promise<bigint>;
462
469
  getTotalStaked(): Promise<bigint>;
463
470
  previewDeposit(assets: bigint): Promise<bigint>;
464
471
  previewRedeem(shares: bigint): Promise<bigint>;
472
+ /**
473
+ * Preview withdraw with multi-asset support (apUSD + xBNB)
474
+ * @param shares Amount of sApUSD shares to redeem
475
+ * @returns Object with apUSD and xBNB amounts, plus whether vault has xBNB
476
+ */
477
+ previewRedeemMulti(shares: bigint): Promise<{
478
+ apUSD: bigint;
479
+ xBNB: bigint;
480
+ hasXBNB: boolean;
481
+ }>;
465
482
  getPendingYield(): Promise<bigint>;
466
483
  getTotalYieldGenerated(): Promise<bigint>;
467
484
  getLSTYieldInfo(lstToken: Address): Promise<LSTYieldInfo>;
@@ -474,6 +491,39 @@ declare class AspanReadClient {
474
491
  getTreasury(): Promise<Address>;
475
492
  getFeeTierCount(): Promise<bigint>;
476
493
  getFeeTier(index: bigint): Promise<FeeTier>;
494
+ /**
495
+ * Get all fee tiers
496
+ * @returns Array of fee tiers sorted by minCR descending
497
+ */
498
+ getAllFeeTiers(): Promise<FeeTier[]>;
499
+ /**
500
+ * Get comprehensive vault (sApUSD) info for frontend display
501
+ * @returns Vault state including TVL, exchange rate, xBNB status
502
+ */
503
+ getVaultInfo(): Promise<{
504
+ totalSupply: bigint;
505
+ totalAssets: bigint;
506
+ exchangeRate: bigint;
507
+ hasXBNB: boolean;
508
+ xBNBAmount: bigint;
509
+ }>;
510
+ /**
511
+ * Get full protocol dashboard data in one call (for frontend overview page)
512
+ * Batches all key metrics into a single multicall
513
+ */
514
+ getProtocolOverview(): Promise<{
515
+ collateralRatio: bigint;
516
+ tvlBNB: bigint;
517
+ tvlUSD: bigint;
518
+ apUSDSupply: bigint;
519
+ xBNBSupply: bigint;
520
+ xBNBPriceUSD: bigint;
521
+ xBNBPriceBNB: bigint;
522
+ bnbPriceUSD: bigint;
523
+ currentFees: CurrentFeeTier;
524
+ stabilityMode: StabilityModeInfo;
525
+ totalStaked: bigint;
526
+ }>;
477
527
  getCurrentFeeTier(): Promise<CurrentFeeTier>;
478
528
  getMaxPriceAge(): Promise<bigint>;
479
529
  getMinDepositPeriod(): Promise<bigint>;
@@ -1529,6 +1579,16 @@ declare const DiamondABI: readonly [{
1529
1579
  readonly internalType: "address";
1530
1580
  }];
1531
1581
  readonly stateMutability: "view";
1582
+ }, {
1583
+ readonly type: "function";
1584
+ readonly name: "setSApUSD";
1585
+ readonly inputs: readonly [{
1586
+ readonly name: "_sApUSD";
1587
+ readonly type: "address";
1588
+ readonly internalType: "address";
1589
+ }];
1590
+ readonly outputs: readonly [];
1591
+ readonly stateMutability: "nonpayable";
1532
1592
  }, {
1533
1593
  readonly type: "function";
1534
1594
  readonly name: "getStabilityPool";
@@ -1559,6 +1619,41 @@ declare const DiamondABI: readonly [{
1559
1619
  readonly internalType: "uint256";
1560
1620
  }];
1561
1621
  readonly stateMutability: "view";
1622
+ }, {
1623
+ readonly type: "function";
1624
+ readonly name: "getAllFeeTiers";
1625
+ readonly inputs: readonly [];
1626
+ readonly outputs: readonly [{
1627
+ readonly name: "";
1628
+ readonly type: "tuple[]";
1629
+ readonly internalType: "struct LibAppStorage.FeeTier[]";
1630
+ readonly components: readonly [{
1631
+ readonly name: "minCR";
1632
+ readonly type: "uint256";
1633
+ readonly internalType: "uint256";
1634
+ }, {
1635
+ readonly name: "apUSDMintFee";
1636
+ readonly type: "uint16";
1637
+ readonly internalType: "uint16";
1638
+ }, {
1639
+ readonly name: "apUSDRedeemFee";
1640
+ readonly type: "uint16";
1641
+ readonly internalType: "uint16";
1642
+ }, {
1643
+ readonly name: "xBNBMintFee";
1644
+ readonly type: "uint16";
1645
+ readonly internalType: "uint16";
1646
+ }, {
1647
+ readonly name: "xBNBRedeemFee";
1648
+ readonly type: "uint16";
1649
+ readonly internalType: "uint16";
1650
+ }, {
1651
+ readonly name: "apUSDMintDisabled";
1652
+ readonly type: "bool";
1653
+ readonly internalType: "bool";
1654
+ }];
1655
+ }];
1656
+ readonly stateMutability: "view";
1562
1657
  }, {
1563
1658
  readonly type: "function";
1564
1659
  readonly name: "getFeeTier";
package/dist/index.js CHANGED
@@ -463,6 +463,13 @@ var DiamondABI = [
463
463
  outputs: [{ name: "", type: "address", internalType: "address" }],
464
464
  stateMutability: "view"
465
465
  },
466
+ {
467
+ type: "function",
468
+ name: "setSApUSD",
469
+ inputs: [{ name: "_sApUSD", type: "address", internalType: "address" }],
470
+ outputs: [],
471
+ stateMutability: "nonpayable"
472
+ },
466
473
  {
467
474
  type: "function",
468
475
  name: "getStabilityPool",
@@ -484,6 +491,25 @@ var DiamondABI = [
484
491
  outputs: [{ name: "", type: "uint256", internalType: "uint256" }],
485
492
  stateMutability: "view"
486
493
  },
494
+ {
495
+ type: "function",
496
+ name: "getAllFeeTiers",
497
+ inputs: [],
498
+ outputs: [{
499
+ name: "",
500
+ type: "tuple[]",
501
+ internalType: "struct LibAppStorage.FeeTier[]",
502
+ components: [
503
+ { name: "minCR", type: "uint256", internalType: "uint256" },
504
+ { name: "apUSDMintFee", type: "uint16", internalType: "uint16" },
505
+ { name: "apUSDRedeemFee", type: "uint16", internalType: "uint16" },
506
+ { name: "xBNBMintFee", type: "uint16", internalType: "uint16" },
507
+ { name: "xBNBRedeemFee", type: "uint16", internalType: "uint16" },
508
+ { name: "apUSDMintDisabled", type: "bool", internalType: "bool" }
509
+ ]
510
+ }],
511
+ stateMutability: "view"
512
+ },
487
513
  {
488
514
  type: "function",
489
515
  name: "getFeeTier",
@@ -677,6 +703,138 @@ var DiamondABI = [
677
703
  }
678
704
  ];
679
705
 
706
+ // src/abi/sApUSD.ts
707
+ var SApUSDABI = [
708
+ // ============ View Functions ============
709
+ {
710
+ type: "function",
711
+ name: "totalAssets",
712
+ inputs: [],
713
+ outputs: [{ name: "", type: "uint256", internalType: "uint256" }],
714
+ stateMutability: "view"
715
+ },
716
+ {
717
+ type: "function",
718
+ name: "totalSupply",
719
+ inputs: [],
720
+ outputs: [{ name: "", type: "uint256", internalType: "uint256" }],
721
+ stateMutability: "view"
722
+ },
723
+ {
724
+ type: "function",
725
+ name: "balanceOf",
726
+ inputs: [{ name: "account", type: "address", internalType: "address" }],
727
+ outputs: [{ name: "", type: "uint256", internalType: "uint256" }],
728
+ stateMutability: "view"
729
+ },
730
+ {
731
+ type: "function",
732
+ name: "previewRedeemMulti",
733
+ inputs: [
734
+ { name: "shares", type: "uint256", internalType: "uint256" }
735
+ ],
736
+ outputs: [
737
+ { name: "assets", type: "address[]", internalType: "address[]" },
738
+ { name: "amounts", type: "uint256[]", internalType: "uint256[]" }
739
+ ],
740
+ stateMutability: "view"
741
+ },
742
+ {
743
+ type: "function",
744
+ name: "hasStabilityConversion",
745
+ inputs: [],
746
+ outputs: [
747
+ { name: "hasXBNB", type: "bool", internalType: "bool" },
748
+ { name: "xBNBAmount", type: "uint256", internalType: "uint256" }
749
+ ],
750
+ stateMutability: "view"
751
+ },
752
+ {
753
+ type: "function",
754
+ name: "totalValue",
755
+ inputs: [],
756
+ outputs: [{ name: "", type: "uint256", internalType: "uint256" }],
757
+ stateMutability: "view"
758
+ },
759
+ {
760
+ type: "function",
761
+ name: "underlyingBalances",
762
+ inputs: [{ name: "_user", type: "address", internalType: "address" }],
763
+ outputs: [
764
+ { name: "apUSDBalance", type: "uint256", internalType: "uint256" },
765
+ { name: "xBNBBalance", type: "uint256", internalType: "uint256" }
766
+ ],
767
+ stateMutability: "view"
768
+ },
769
+ {
770
+ type: "function",
771
+ name: "xBNBToApUSDRate",
772
+ inputs: [],
773
+ outputs: [{ name: "", type: "uint256", internalType: "uint256" }],
774
+ stateMutability: "view"
775
+ },
776
+ {
777
+ type: "function",
778
+ name: "exchangeRate",
779
+ inputs: [],
780
+ outputs: [{ name: "", type: "uint256", internalType: "uint256" }],
781
+ stateMutability: "view"
782
+ },
783
+ {
784
+ type: "function",
785
+ name: "previewCleanXBNB",
786
+ inputs: [
787
+ { name: "_xBNBAmount", type: "uint256", internalType: "uint256" },
788
+ { name: "_router", type: "address", internalType: "address" },
789
+ { name: "_path", type: "address[]", internalType: "address[]" }
790
+ ],
791
+ outputs: [{ name: "expectedApUSD", type: "uint256", internalType: "uint256" }],
792
+ stateMutability: "view"
793
+ },
794
+ {
795
+ type: "function",
796
+ name: "KEEPER_ROLE",
797
+ inputs: [],
798
+ outputs: [{ name: "", type: "bytes32", internalType: "bytes32" }],
799
+ stateMutability: "view"
800
+ },
801
+ {
802
+ type: "function",
803
+ name: "hasRole",
804
+ inputs: [
805
+ { name: "role", type: "bytes32", internalType: "bytes32" },
806
+ { name: "account", type: "address", internalType: "address" }
807
+ ],
808
+ outputs: [{ name: "", type: "bool", internalType: "bool" }],
809
+ stateMutability: "view"
810
+ },
811
+ // ============ Keeper Functions ============
812
+ {
813
+ type: "function",
814
+ name: "cleanXBNB",
815
+ inputs: [
816
+ { name: "_xBNBAmount", type: "uint256", internalType: "uint256" },
817
+ { name: "_minApUSDOut", type: "uint256", internalType: "uint256" },
818
+ { name: "_router", type: "address", internalType: "address" },
819
+ { name: "_path", type: "address[]", internalType: "address[]" },
820
+ { name: "_deadline", type: "uint256", internalType: "uint256" }
821
+ ],
822
+ outputs: [{ name: "apUSDReceived", type: "uint256", internalType: "uint256" }],
823
+ stateMutability: "nonpayable"
824
+ },
825
+ // ============ Events ============
826
+ {
827
+ type: "event",
828
+ name: "VaultCleaned",
829
+ inputs: [
830
+ { name: "xBNBSold", type: "uint256", indexed: false, internalType: "uint256" },
831
+ { name: "apUSDReceived", type: "uint256", indexed: false, internalType: "uint256" },
832
+ { name: "keeper", type: "address", indexed: true, internalType: "address" }
833
+ ],
834
+ anonymous: false
835
+ }
836
+ ];
837
+
680
838
  // src/client.ts
681
839
  var pharosTestnet = {
682
840
  id: 688689,
@@ -1070,6 +1228,19 @@ var AspanReadClient = class _AspanReadClient {
1070
1228
  args: [user]
1071
1229
  });
1072
1230
  }
1231
+ /**
1232
+ * Get user's underlying balances (apUSD + xBNB) from sApUSD vault
1233
+ */
1234
+ async getBalanceMulti(user) {
1235
+ const sApUSDAddress = await this.getSApUSD();
1236
+ const [apUSDBalance, xBNBBalance] = await this.publicClient.readContract({
1237
+ address: sApUSDAddress,
1238
+ abi: SApUSDABI,
1239
+ functionName: "underlyingBalances",
1240
+ args: [user]
1241
+ });
1242
+ return { apUSDBalance, xBNBBalance };
1243
+ }
1073
1244
  async getUserStabilityPoolPosition(user) {
1074
1245
  const [shares, balance] = await Promise.all([
1075
1246
  this.getShares(user),
@@ -1135,6 +1306,32 @@ var AspanReadClient = class _AspanReadClient {
1135
1306
  throw error;
1136
1307
  }
1137
1308
  }
1309
+ /**
1310
+ * Preview withdraw with multi-asset support (apUSD + xBNB)
1311
+ * @param shares Amount of sApUSD shares to redeem
1312
+ * @returns Object with apUSD and xBNB amounts, plus whether vault has xBNB
1313
+ */
1314
+ async previewRedeemMulti(shares) {
1315
+ const sApUSDAddress = await this.getSApUSD();
1316
+ const [result, conversion] = await Promise.all([
1317
+ this.publicClient.readContract({
1318
+ address: sApUSDAddress,
1319
+ abi: SApUSDABI,
1320
+ functionName: "previewRedeemMulti",
1321
+ args: [shares]
1322
+ }),
1323
+ this.publicClient.readContract({
1324
+ address: sApUSDAddress,
1325
+ abi: SApUSDABI,
1326
+ functionName: "hasStabilityConversion"
1327
+ })
1328
+ ]);
1329
+ return {
1330
+ apUSD: result[1][0],
1331
+ xBNB: result[1][1],
1332
+ hasXBNB: conversion[0]
1333
+ };
1334
+ }
1138
1335
  async getPendingYield() {
1139
1336
  try {
1140
1337
  return await this.publicClient.readContract({
@@ -1288,6 +1485,93 @@ var AspanReadClient = class _AspanReadClient {
1288
1485
  apUSDMintDisabled: result[5]
1289
1486
  };
1290
1487
  }
1488
+ /**
1489
+ * Get all fee tiers
1490
+ * @returns Array of fee tiers sorted by minCR descending
1491
+ */
1492
+ async getAllFeeTiers() {
1493
+ const result = await this.publicClient.readContract({
1494
+ address: this.diamondAddress,
1495
+ abi: DiamondABI,
1496
+ functionName: "getAllFeeTiers"
1497
+ });
1498
+ return result.map((tier) => ({
1499
+ minCR: tier.minCR,
1500
+ apUSDMintFee: tier.apUSDMintFee,
1501
+ apUSDRedeemFee: tier.apUSDRedeemFee,
1502
+ xBNBMintFee: tier.xBNBMintFee,
1503
+ xBNBRedeemFee: tier.xBNBRedeemFee,
1504
+ apUSDMintDisabled: tier.apUSDMintDisabled
1505
+ }));
1506
+ }
1507
+ /**
1508
+ * Get comprehensive vault (sApUSD) info for frontend display
1509
+ * @returns Vault state including TVL, exchange rate, xBNB status
1510
+ */
1511
+ async getVaultInfo() {
1512
+ const sApUSDAddress = await this.getSApUSD();
1513
+ const [totalSupply, totalAssets, exchangeRate, conversion] = await Promise.all([
1514
+ this.publicClient.readContract({
1515
+ address: sApUSDAddress,
1516
+ abi: SApUSDABI,
1517
+ functionName: "totalSupply"
1518
+ }),
1519
+ this.publicClient.readContract({
1520
+ address: sApUSDAddress,
1521
+ abi: SApUSDABI,
1522
+ functionName: "totalAssets"
1523
+ }),
1524
+ this.publicClient.readContract({
1525
+ address: sApUSDAddress,
1526
+ abi: SApUSDABI,
1527
+ functionName: "exchangeRate"
1528
+ }),
1529
+ this.publicClient.readContract({
1530
+ address: sApUSDAddress,
1531
+ abi: SApUSDABI,
1532
+ functionName: "hasStabilityConversion"
1533
+ })
1534
+ ]);
1535
+ return {
1536
+ totalSupply,
1537
+ totalAssets,
1538
+ exchangeRate,
1539
+ hasXBNB: conversion[0],
1540
+ xBNBAmount: conversion[1]
1541
+ };
1542
+ }
1543
+ /**
1544
+ * Get full protocol dashboard data in one call (for frontend overview page)
1545
+ * Batches all key metrics into a single multicall
1546
+ */
1547
+ async getProtocolOverview() {
1548
+ const [cr, tvlBNB, tvlUSD, apUSD, xBNB, xPriceUSD, xPriceBNB, bnbPrice, fees, sm, staked] = await Promise.all([
1549
+ this.getCollateralRatio(),
1550
+ this.getTVLInBNB(),
1551
+ this.getTVLInUSD(),
1552
+ this.getApUSDSupply(),
1553
+ this.getXBNBSupply(),
1554
+ this.getXBNBPriceUSD(),
1555
+ this.getXBNBPriceBNB(),
1556
+ this.getBNBPriceUSD(),
1557
+ this.getCurrentFeeTier(),
1558
+ this.getStabilityMode(),
1559
+ this.getTotalStaked()
1560
+ ]);
1561
+ return {
1562
+ collateralRatio: cr,
1563
+ tvlBNB,
1564
+ tvlUSD,
1565
+ apUSDSupply: apUSD,
1566
+ xBNBSupply: xBNB,
1567
+ xBNBPriceUSD: xPriceUSD,
1568
+ xBNBPriceBNB: xPriceBNB,
1569
+ bnbPriceUSD: bnbPrice,
1570
+ currentFees: fees,
1571
+ stabilityMode: sm,
1572
+ totalStaked: staked
1573
+ };
1574
+ }
1291
1575
  async getCurrentFeeTier() {
1292
1576
  try {
1293
1577
  const result = await this.publicClient.readContract({
package/dist/index.mjs CHANGED
@@ -405,6 +405,13 @@ var DiamondABI = [
405
405
  outputs: [{ name: "", type: "address", internalType: "address" }],
406
406
  stateMutability: "view"
407
407
  },
408
+ {
409
+ type: "function",
410
+ name: "setSApUSD",
411
+ inputs: [{ name: "_sApUSD", type: "address", internalType: "address" }],
412
+ outputs: [],
413
+ stateMutability: "nonpayable"
414
+ },
408
415
  {
409
416
  type: "function",
410
417
  name: "getStabilityPool",
@@ -426,6 +433,25 @@ var DiamondABI = [
426
433
  outputs: [{ name: "", type: "uint256", internalType: "uint256" }],
427
434
  stateMutability: "view"
428
435
  },
436
+ {
437
+ type: "function",
438
+ name: "getAllFeeTiers",
439
+ inputs: [],
440
+ outputs: [{
441
+ name: "",
442
+ type: "tuple[]",
443
+ internalType: "struct LibAppStorage.FeeTier[]",
444
+ components: [
445
+ { name: "minCR", type: "uint256", internalType: "uint256" },
446
+ { name: "apUSDMintFee", type: "uint16", internalType: "uint16" },
447
+ { name: "apUSDRedeemFee", type: "uint16", internalType: "uint16" },
448
+ { name: "xBNBMintFee", type: "uint16", internalType: "uint16" },
449
+ { name: "xBNBRedeemFee", type: "uint16", internalType: "uint16" },
450
+ { name: "apUSDMintDisabled", type: "bool", internalType: "bool" }
451
+ ]
452
+ }],
453
+ stateMutability: "view"
454
+ },
429
455
  {
430
456
  type: "function",
431
457
  name: "getFeeTier",
@@ -619,6 +645,138 @@ var DiamondABI = [
619
645
  }
620
646
  ];
621
647
 
648
+ // src/abi/sApUSD.ts
649
+ var SApUSDABI = [
650
+ // ============ View Functions ============
651
+ {
652
+ type: "function",
653
+ name: "totalAssets",
654
+ inputs: [],
655
+ outputs: [{ name: "", type: "uint256", internalType: "uint256" }],
656
+ stateMutability: "view"
657
+ },
658
+ {
659
+ type: "function",
660
+ name: "totalSupply",
661
+ inputs: [],
662
+ outputs: [{ name: "", type: "uint256", internalType: "uint256" }],
663
+ stateMutability: "view"
664
+ },
665
+ {
666
+ type: "function",
667
+ name: "balanceOf",
668
+ inputs: [{ name: "account", type: "address", internalType: "address" }],
669
+ outputs: [{ name: "", type: "uint256", internalType: "uint256" }],
670
+ stateMutability: "view"
671
+ },
672
+ {
673
+ type: "function",
674
+ name: "previewRedeemMulti",
675
+ inputs: [
676
+ { name: "shares", type: "uint256", internalType: "uint256" }
677
+ ],
678
+ outputs: [
679
+ { name: "assets", type: "address[]", internalType: "address[]" },
680
+ { name: "amounts", type: "uint256[]", internalType: "uint256[]" }
681
+ ],
682
+ stateMutability: "view"
683
+ },
684
+ {
685
+ type: "function",
686
+ name: "hasStabilityConversion",
687
+ inputs: [],
688
+ outputs: [
689
+ { name: "hasXBNB", type: "bool", internalType: "bool" },
690
+ { name: "xBNBAmount", type: "uint256", internalType: "uint256" }
691
+ ],
692
+ stateMutability: "view"
693
+ },
694
+ {
695
+ type: "function",
696
+ name: "totalValue",
697
+ inputs: [],
698
+ outputs: [{ name: "", type: "uint256", internalType: "uint256" }],
699
+ stateMutability: "view"
700
+ },
701
+ {
702
+ type: "function",
703
+ name: "underlyingBalances",
704
+ inputs: [{ name: "_user", type: "address", internalType: "address" }],
705
+ outputs: [
706
+ { name: "apUSDBalance", type: "uint256", internalType: "uint256" },
707
+ { name: "xBNBBalance", type: "uint256", internalType: "uint256" }
708
+ ],
709
+ stateMutability: "view"
710
+ },
711
+ {
712
+ type: "function",
713
+ name: "xBNBToApUSDRate",
714
+ inputs: [],
715
+ outputs: [{ name: "", type: "uint256", internalType: "uint256" }],
716
+ stateMutability: "view"
717
+ },
718
+ {
719
+ type: "function",
720
+ name: "exchangeRate",
721
+ inputs: [],
722
+ outputs: [{ name: "", type: "uint256", internalType: "uint256" }],
723
+ stateMutability: "view"
724
+ },
725
+ {
726
+ type: "function",
727
+ name: "previewCleanXBNB",
728
+ inputs: [
729
+ { name: "_xBNBAmount", type: "uint256", internalType: "uint256" },
730
+ { name: "_router", type: "address", internalType: "address" },
731
+ { name: "_path", type: "address[]", internalType: "address[]" }
732
+ ],
733
+ outputs: [{ name: "expectedApUSD", type: "uint256", internalType: "uint256" }],
734
+ stateMutability: "view"
735
+ },
736
+ {
737
+ type: "function",
738
+ name: "KEEPER_ROLE",
739
+ inputs: [],
740
+ outputs: [{ name: "", type: "bytes32", internalType: "bytes32" }],
741
+ stateMutability: "view"
742
+ },
743
+ {
744
+ type: "function",
745
+ name: "hasRole",
746
+ inputs: [
747
+ { name: "role", type: "bytes32", internalType: "bytes32" },
748
+ { name: "account", type: "address", internalType: "address" }
749
+ ],
750
+ outputs: [{ name: "", type: "bool", internalType: "bool" }],
751
+ stateMutability: "view"
752
+ },
753
+ // ============ Keeper Functions ============
754
+ {
755
+ type: "function",
756
+ name: "cleanXBNB",
757
+ inputs: [
758
+ { name: "_xBNBAmount", type: "uint256", internalType: "uint256" },
759
+ { name: "_minApUSDOut", type: "uint256", internalType: "uint256" },
760
+ { name: "_router", type: "address", internalType: "address" },
761
+ { name: "_path", type: "address[]", internalType: "address[]" },
762
+ { name: "_deadline", type: "uint256", internalType: "uint256" }
763
+ ],
764
+ outputs: [{ name: "apUSDReceived", type: "uint256", internalType: "uint256" }],
765
+ stateMutability: "nonpayable"
766
+ },
767
+ // ============ Events ============
768
+ {
769
+ type: "event",
770
+ name: "VaultCleaned",
771
+ inputs: [
772
+ { name: "xBNBSold", type: "uint256", indexed: false, internalType: "uint256" },
773
+ { name: "apUSDReceived", type: "uint256", indexed: false, internalType: "uint256" },
774
+ { name: "keeper", type: "address", indexed: true, internalType: "address" }
775
+ ],
776
+ anonymous: false
777
+ }
778
+ ];
779
+
622
780
  // src/client.ts
623
781
  var pharosTestnet = {
624
782
  id: 688689,
@@ -1012,6 +1170,19 @@ var AspanReadClient = class _AspanReadClient {
1012
1170
  args: [user]
1013
1171
  });
1014
1172
  }
1173
+ /**
1174
+ * Get user's underlying balances (apUSD + xBNB) from sApUSD vault
1175
+ */
1176
+ async getBalanceMulti(user) {
1177
+ const sApUSDAddress = await this.getSApUSD();
1178
+ const [apUSDBalance, xBNBBalance] = await this.publicClient.readContract({
1179
+ address: sApUSDAddress,
1180
+ abi: SApUSDABI,
1181
+ functionName: "underlyingBalances",
1182
+ args: [user]
1183
+ });
1184
+ return { apUSDBalance, xBNBBalance };
1185
+ }
1015
1186
  async getUserStabilityPoolPosition(user) {
1016
1187
  const [shares, balance] = await Promise.all([
1017
1188
  this.getShares(user),
@@ -1077,6 +1248,32 @@ var AspanReadClient = class _AspanReadClient {
1077
1248
  throw error;
1078
1249
  }
1079
1250
  }
1251
+ /**
1252
+ * Preview withdraw with multi-asset support (apUSD + xBNB)
1253
+ * @param shares Amount of sApUSD shares to redeem
1254
+ * @returns Object with apUSD and xBNB amounts, plus whether vault has xBNB
1255
+ */
1256
+ async previewRedeemMulti(shares) {
1257
+ const sApUSDAddress = await this.getSApUSD();
1258
+ const [result, conversion] = await Promise.all([
1259
+ this.publicClient.readContract({
1260
+ address: sApUSDAddress,
1261
+ abi: SApUSDABI,
1262
+ functionName: "previewRedeemMulti",
1263
+ args: [shares]
1264
+ }),
1265
+ this.publicClient.readContract({
1266
+ address: sApUSDAddress,
1267
+ abi: SApUSDABI,
1268
+ functionName: "hasStabilityConversion"
1269
+ })
1270
+ ]);
1271
+ return {
1272
+ apUSD: result[1][0],
1273
+ xBNB: result[1][1],
1274
+ hasXBNB: conversion[0]
1275
+ };
1276
+ }
1080
1277
  async getPendingYield() {
1081
1278
  try {
1082
1279
  return await this.publicClient.readContract({
@@ -1230,6 +1427,93 @@ var AspanReadClient = class _AspanReadClient {
1230
1427
  apUSDMintDisabled: result[5]
1231
1428
  };
1232
1429
  }
1430
+ /**
1431
+ * Get all fee tiers
1432
+ * @returns Array of fee tiers sorted by minCR descending
1433
+ */
1434
+ async getAllFeeTiers() {
1435
+ const result = await this.publicClient.readContract({
1436
+ address: this.diamondAddress,
1437
+ abi: DiamondABI,
1438
+ functionName: "getAllFeeTiers"
1439
+ });
1440
+ return result.map((tier) => ({
1441
+ minCR: tier.minCR,
1442
+ apUSDMintFee: tier.apUSDMintFee,
1443
+ apUSDRedeemFee: tier.apUSDRedeemFee,
1444
+ xBNBMintFee: tier.xBNBMintFee,
1445
+ xBNBRedeemFee: tier.xBNBRedeemFee,
1446
+ apUSDMintDisabled: tier.apUSDMintDisabled
1447
+ }));
1448
+ }
1449
+ /**
1450
+ * Get comprehensive vault (sApUSD) info for frontend display
1451
+ * @returns Vault state including TVL, exchange rate, xBNB status
1452
+ */
1453
+ async getVaultInfo() {
1454
+ const sApUSDAddress = await this.getSApUSD();
1455
+ const [totalSupply, totalAssets, exchangeRate, conversion] = await Promise.all([
1456
+ this.publicClient.readContract({
1457
+ address: sApUSDAddress,
1458
+ abi: SApUSDABI,
1459
+ functionName: "totalSupply"
1460
+ }),
1461
+ this.publicClient.readContract({
1462
+ address: sApUSDAddress,
1463
+ abi: SApUSDABI,
1464
+ functionName: "totalAssets"
1465
+ }),
1466
+ this.publicClient.readContract({
1467
+ address: sApUSDAddress,
1468
+ abi: SApUSDABI,
1469
+ functionName: "exchangeRate"
1470
+ }),
1471
+ this.publicClient.readContract({
1472
+ address: sApUSDAddress,
1473
+ abi: SApUSDABI,
1474
+ functionName: "hasStabilityConversion"
1475
+ })
1476
+ ]);
1477
+ return {
1478
+ totalSupply,
1479
+ totalAssets,
1480
+ exchangeRate,
1481
+ hasXBNB: conversion[0],
1482
+ xBNBAmount: conversion[1]
1483
+ };
1484
+ }
1485
+ /**
1486
+ * Get full protocol dashboard data in one call (for frontend overview page)
1487
+ * Batches all key metrics into a single multicall
1488
+ */
1489
+ async getProtocolOverview() {
1490
+ const [cr, tvlBNB, tvlUSD, apUSD, xBNB, xPriceUSD, xPriceBNB, bnbPrice, fees, sm, staked] = await Promise.all([
1491
+ this.getCollateralRatio(),
1492
+ this.getTVLInBNB(),
1493
+ this.getTVLInUSD(),
1494
+ this.getApUSDSupply(),
1495
+ this.getXBNBSupply(),
1496
+ this.getXBNBPriceUSD(),
1497
+ this.getXBNBPriceBNB(),
1498
+ this.getBNBPriceUSD(),
1499
+ this.getCurrentFeeTier(),
1500
+ this.getStabilityMode(),
1501
+ this.getTotalStaked()
1502
+ ]);
1503
+ return {
1504
+ collateralRatio: cr,
1505
+ tvlBNB,
1506
+ tvlUSD,
1507
+ apUSDSupply: apUSD,
1508
+ xBNBSupply: xBNB,
1509
+ xBNBPriceUSD: xPriceUSD,
1510
+ xBNBPriceBNB: xPriceBNB,
1511
+ bnbPriceUSD: bnbPrice,
1512
+ currentFees: fees,
1513
+ stabilityMode: sm,
1514
+ totalStaked: staked
1515
+ };
1516
+ }
1233
1517
  async getCurrentFeeTier() {
1234
1518
  try {
1235
1519
  const result = await this.publicClient.readContract({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aspan/sdk",
3
- "version": "0.4.4",
3
+ "version": "0.4.5",
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",
@@ -0,0 +1,212 @@
1
+ /**
2
+ * AspanReadClient Tests - Read-only queries against BSC mainnet
3
+ *
4
+ * Tests new SDK methods: previewRedeemMulti, getVaultInfo, getAllFeeTiers, getProtocolOverview
5
+ * No wallet/private key needed — pure view calls.
6
+ *
7
+ * Run with: npm test -- read-client
8
+ */
9
+
10
+ import { describe, it, expect, beforeAll } from "vitest";
11
+ import { parseEther, zeroAddress, type Address } from "viem";
12
+ import { bsc } from "viem/chains";
13
+ import { AspanReadClient, BSC_ADDRESSES } from "../index";
14
+
15
+ // ============ Configuration ============
16
+
17
+ const DIAMOND = BSC_ADDRESSES.diamond;
18
+ const RPC_URL = process.env.BSC_RPC_URL || "https://bsc-dataseed.binance.org";
19
+
20
+ describe("AspanReadClient - View Functions", () => {
21
+ let client: AspanReadClient;
22
+
23
+ beforeAll(() => {
24
+ client = new AspanReadClient({
25
+ diamondAddress: DIAMOND,
26
+ chain: bsc,
27
+ rpcUrl: RPC_URL,
28
+ });
29
+ });
30
+
31
+ // ============ Vault Info ============
32
+
33
+ describe("getVaultInfo", () => {
34
+ it("should return vault state with all fields", async () => {
35
+ const info = await client.getVaultInfo();
36
+
37
+ expect(info).toHaveProperty("totalSupply");
38
+ expect(info).toHaveProperty("totalAssets");
39
+ expect(info).toHaveProperty("exchangeRate");
40
+ expect(info).toHaveProperty("hasXBNB");
41
+ expect(info).toHaveProperty("xBNBAmount");
42
+
43
+ expect(typeof info.totalSupply).toBe("bigint");
44
+ expect(typeof info.totalAssets).toBe("bigint");
45
+ expect(typeof info.exchangeRate).toBe("bigint");
46
+ expect(typeof info.hasXBNB).toBe("boolean");
47
+ expect(typeof info.xBNBAmount).toBe("bigint");
48
+
49
+ // Exchange rate should be > 0 (at least 1e18 for 1:1)
50
+ expect(info.exchangeRate).toBeGreaterThan(0n);
51
+ });
52
+ });
53
+
54
+ // ============ Preview Redeem Multi ============
55
+
56
+ describe("previewRedeemMulti", () => {
57
+ it("should return apUSD and xBNB amounts for given shares", async () => {
58
+ const shares = parseEther("1"); // 1 share
59
+ const preview = await client.previewRedeemMulti(shares);
60
+
61
+ expect(preview).toHaveProperty("apUSD");
62
+ expect(preview).toHaveProperty("xBNB");
63
+ expect(preview).toHaveProperty("hasXBNB");
64
+
65
+ expect(typeof preview.apUSD).toBe("bigint");
66
+ expect(typeof preview.xBNB).toBe("bigint");
67
+ expect(typeof preview.hasXBNB).toBe("boolean");
68
+ });
69
+
70
+ it("should return zero for zero shares", async () => {
71
+ const preview = await client.previewRedeemMulti(0n);
72
+
73
+ expect(preview.apUSD).toBe(0n);
74
+ expect(preview.xBNB).toBe(0n);
75
+ });
76
+
77
+ it("should have consistent hasXBNB with getVaultInfo", async () => {
78
+ const shares = parseEther("1");
79
+ const [preview, vault] = await Promise.all([
80
+ client.previewRedeemMulti(shares),
81
+ client.getVaultInfo(),
82
+ ]);
83
+
84
+ expect(preview.hasXBNB).toBe(vault.hasXBNB);
85
+ });
86
+ });
87
+
88
+ // ============ Fee Tiers ============
89
+
90
+ describe("getAllFeeTiers", () => {
91
+ it("should return non-empty array of fee tiers", async () => {
92
+ const tiers = await client.getAllFeeTiers();
93
+
94
+ expect(Array.isArray(tiers)).toBe(true);
95
+ expect(tiers.length).toBeGreaterThan(0);
96
+ });
97
+
98
+ it("should have correct fee tier structure", async () => {
99
+ const tiers = await client.getAllFeeTiers();
100
+ const tier = tiers[0];
101
+
102
+ expect(tier).toHaveProperty("minCR");
103
+ expect(tier).toHaveProperty("apUSDMintFee");
104
+ expect(tier).toHaveProperty("apUSDRedeemFee");
105
+ expect(tier).toHaveProperty("xBNBMintFee");
106
+ expect(tier).toHaveProperty("xBNBRedeemFee");
107
+ expect(tier).toHaveProperty("apUSDMintDisabled");
108
+
109
+ expect(typeof tier.minCR).toBe("bigint");
110
+ expect(typeof tier.apUSDMintDisabled).toBe("boolean");
111
+ });
112
+
113
+ it("should be sorted by minCR descending", async () => {
114
+ const tiers = await client.getAllFeeTiers();
115
+
116
+ for (let i = 1; i < tiers.length; i++) {
117
+ expect(tiers[i - 1].minCR).toBeGreaterThanOrEqual(tiers[i].minCR);
118
+ }
119
+ });
120
+
121
+ it("should match getFeeTierCount", async () => {
122
+ const [tiers, count] = await Promise.all([
123
+ client.getAllFeeTiers(),
124
+ client.getFeeTierCount(),
125
+ ]);
126
+
127
+ expect(BigInt(tiers.length)).toBe(count);
128
+ });
129
+ });
130
+
131
+ // ============ Protocol Overview ============
132
+
133
+ describe("getProtocolOverview", () => {
134
+ it("should return all protocol metrics", async () => {
135
+ const overview = await client.getProtocolOverview();
136
+
137
+ expect(overview).toHaveProperty("collateralRatio");
138
+ expect(overview).toHaveProperty("tvlBNB");
139
+ expect(overview).toHaveProperty("tvlUSD");
140
+ expect(overview).toHaveProperty("apUSDSupply");
141
+ expect(overview).toHaveProperty("xBNBSupply");
142
+ expect(overview).toHaveProperty("xBNBPriceUSD");
143
+ expect(overview).toHaveProperty("xBNBPriceBNB");
144
+ expect(overview).toHaveProperty("bnbPriceUSD");
145
+ expect(overview).toHaveProperty("currentFees");
146
+ expect(overview).toHaveProperty("stabilityMode");
147
+ expect(overview).toHaveProperty("totalStaked");
148
+ });
149
+
150
+ it("should have positive TVL and prices", async () => {
151
+ const overview = await client.getProtocolOverview();
152
+
153
+ expect(overview.tvlBNB).toBeGreaterThan(0n);
154
+ expect(overview.tvlUSD).toBeGreaterThan(0n);
155
+ expect(overview.bnbPriceUSD).toBeGreaterThan(0n);
156
+ expect(overview.collateralRatio).toBeGreaterThan(0n);
157
+ });
158
+
159
+ it("should have valid currentFees structure", async () => {
160
+ const overview = await client.getProtocolOverview();
161
+ const fees = overview.currentFees;
162
+
163
+ expect(fees).toHaveProperty("minCR");
164
+ expect(fees).toHaveProperty("apUSDMintFee");
165
+ });
166
+
167
+ it("should have valid stabilityMode structure", async () => {
168
+ const overview = await client.getProtocolOverview();
169
+ const sm = overview.stabilityMode;
170
+
171
+ expect(sm).toHaveProperty("mode");
172
+ // mode should be 0 (normal), 1, or 2
173
+ expect([0, 1, 2]).toContain(sm.mode);
174
+ });
175
+ });
176
+
177
+ // ============ Existing view methods consistency ============
178
+
179
+ describe("cross-method consistency", () => {
180
+ it("vault totalStaked should match getVaultInfo totalSupply context", async () => {
181
+ const [staked, vault] = await Promise.all([
182
+ client.getTotalStaked(),
183
+ client.getVaultInfo(),
184
+ ]);
185
+
186
+ // totalStaked from Diamond should relate to vault totalAssets
187
+ expect(typeof staked).toBe("bigint");
188
+ expect(typeof vault.totalAssets).toBe("bigint");
189
+ });
190
+
191
+ it("exchangeRate from getVaultInfo should match getExchangeRate", async () => {
192
+ const [vault, rate] = await Promise.all([
193
+ client.getVaultInfo(),
194
+ client.getExchangeRate(),
195
+ ]);
196
+
197
+ expect(vault.exchangeRate).toBe(rate);
198
+ });
199
+ });
200
+
201
+ describe("getBalanceMulti", () => {
202
+ it("should return apUSD and xBNB balances for a user", async () => {
203
+ const testUser = zeroAddress; // zero address will have 0 balances
204
+ const result = await client.getBalanceMulti(testUser);
205
+
206
+ expect(typeof result.apUSDBalance).toBe("bigint");
207
+ expect(typeof result.xBNBBalance).toBe("bigint");
208
+ expect(result.apUSDBalance).toBe(0n);
209
+ expect(result.xBNBBalance).toBe(0n);
210
+ });
211
+ });
212
+ });
@@ -406,6 +406,13 @@ export const DiamondABI = [
406
406
  outputs: [{ name: "", type: "address", internalType: "address" }],
407
407
  stateMutability: "view"
408
408
  },
409
+ {
410
+ type: "function",
411
+ name: "setSApUSD",
412
+ inputs: [{ name: "_sApUSD", type: "address", internalType: "address" }],
413
+ outputs: [],
414
+ stateMutability: "nonpayable"
415
+ },
409
416
  {
410
417
  type: "function",
411
418
  name: "getStabilityPool",
@@ -427,6 +434,25 @@ export const DiamondABI = [
427
434
  outputs: [{ name: "", type: "uint256", internalType: "uint256" }],
428
435
  stateMutability: "view"
429
436
  },
437
+ {
438
+ type: "function",
439
+ name: "getAllFeeTiers",
440
+ inputs: [],
441
+ outputs: [{
442
+ name: "",
443
+ type: "tuple[]",
444
+ internalType: "struct LibAppStorage.FeeTier[]",
445
+ components: [
446
+ { name: "minCR", type: "uint256", internalType: "uint256" },
447
+ { name: "apUSDMintFee", type: "uint16", internalType: "uint16" },
448
+ { name: "apUSDRedeemFee", type: "uint16", internalType: "uint16" },
449
+ { name: "xBNBMintFee", type: "uint16", internalType: "uint16" },
450
+ { name: "xBNBRedeemFee", type: "uint16", internalType: "uint16" },
451
+ { name: "apUSDMintDisabled", type: "bool", internalType: "bool" },
452
+ ],
453
+ }],
454
+ stateMutability: "view"
455
+ },
430
456
  {
431
457
  type: "function",
432
458
  name: "getFeeTier",
package/src/abi/sApUSD.ts CHANGED
@@ -25,6 +25,18 @@ export const SApUSDABI = [
25
25
  outputs: [{ name: "", type: "uint256", internalType: "uint256" }],
26
26
  stateMutability: "view"
27
27
  },
28
+ {
29
+ type: "function",
30
+ name: "previewRedeemMulti",
31
+ inputs: [
32
+ { name: "shares", type: "uint256", internalType: "uint256" }
33
+ ],
34
+ outputs: [
35
+ { name: "assets", type: "address[]", internalType: "address[]" },
36
+ { name: "amounts", type: "uint256[]", internalType: "uint256[]" }
37
+ ],
38
+ stateMutability: "view"
39
+ },
28
40
  {
29
41
  type: "function",
30
42
  name: "hasStabilityConversion",
@@ -35,6 +47,30 @@ export const SApUSDABI = [
35
47
  ],
36
48
  stateMutability: "view"
37
49
  },
50
+ {
51
+ type: "function",
52
+ name: "totalValue",
53
+ inputs: [],
54
+ outputs: [{ name: "", type: "uint256", internalType: "uint256" }],
55
+ stateMutability: "view"
56
+ },
57
+ {
58
+ type: "function",
59
+ name: "underlyingBalances",
60
+ inputs: [{ name: "_user", type: "address", internalType: "address" }],
61
+ outputs: [
62
+ { name: "apUSDBalance", type: "uint256", internalType: "uint256" },
63
+ { name: "xBNBBalance", type: "uint256", internalType: "uint256" }
64
+ ],
65
+ stateMutability: "view"
66
+ },
67
+ {
68
+ type: "function",
69
+ name: "xBNBToApUSDRate",
70
+ inputs: [],
71
+ outputs: [{ name: "", type: "uint256", internalType: "uint256" }],
72
+ stateMutability: "view"
73
+ },
38
74
  {
39
75
  type: "function",
40
76
  name: "exchangeRate",
package/src/client.ts CHANGED
@@ -54,6 +54,7 @@ export function getChainById(chainId: number): Chain {
54
54
  return chain;
55
55
  }
56
56
  import { DiamondABI } from "./abi/diamond";
57
+ import { SApUSDABI } from "./abi/sApUSD";
57
58
  import type {
58
59
  LSTInfo,
59
60
  LSTYieldInfo,
@@ -525,6 +526,20 @@ export class AspanReadClient {
525
526
  });
526
527
  }
527
528
 
529
+ /**
530
+ * Get user's underlying balances (apUSD + xBNB) from sApUSD vault
531
+ */
532
+ async getBalanceMulti(user: Address): Promise<{ apUSDBalance: bigint; xBNBBalance: bigint }> {
533
+ const sApUSDAddress = await this.getSApUSD();
534
+ const [apUSDBalance, xBNBBalance] = await this.publicClient.readContract({
535
+ address: sApUSDAddress,
536
+ abi: SApUSDABI,
537
+ functionName: "underlyingBalances",
538
+ args: [user],
539
+ });
540
+ return { apUSDBalance, xBNBBalance };
541
+ }
542
+
528
543
  async getUserStabilityPoolPosition(
529
544
  user: Address
530
545
  ): Promise<UserStabilityPoolPosition> {
@@ -599,6 +614,37 @@ export class AspanReadClient {
599
614
  }
600
615
  }
601
616
 
617
+ /**
618
+ * Preview withdraw with multi-asset support (apUSD + xBNB)
619
+ * @param shares Amount of sApUSD shares to redeem
620
+ * @returns Object with apUSD and xBNB amounts, plus whether vault has xBNB
621
+ */
622
+ async previewRedeemMulti(shares: bigint): Promise<{
623
+ apUSD: bigint;
624
+ xBNB: bigint;
625
+ hasXBNB: boolean;
626
+ }> {
627
+ const sApUSDAddress = await this.getSApUSD();
628
+ const [result, conversion] = await Promise.all([
629
+ this.publicClient.readContract({
630
+ address: sApUSDAddress,
631
+ abi: SApUSDABI,
632
+ functionName: "previewRedeemMulti",
633
+ args: [shares],
634
+ }) as Promise<[Address[], bigint[]]>,
635
+ this.publicClient.readContract({
636
+ address: sApUSDAddress,
637
+ abi: SApUSDABI,
638
+ functionName: "hasStabilityConversion",
639
+ }) as Promise<[boolean, bigint]>,
640
+ ]);
641
+ return {
642
+ apUSD: result[1][0],
643
+ xBNB: result[1][1],
644
+ hasXBNB: conversion[0],
645
+ };
646
+ }
647
+
602
648
  async getPendingYield(): Promise<bigint> {
603
649
  try {
604
650
  return await this.publicClient.readContract({
@@ -770,6 +816,122 @@ export class AspanReadClient {
770
816
  };
771
817
  }
772
818
 
819
+ /**
820
+ * Get all fee tiers
821
+ * @returns Array of fee tiers sorted by minCR descending
822
+ */
823
+ async getAllFeeTiers(): Promise<FeeTier[]> {
824
+ const result = await this.publicClient.readContract({
825
+ address: this.diamondAddress,
826
+ abi: DiamondABI,
827
+ functionName: "getAllFeeTiers",
828
+ }) as unknown as Array<{
829
+ minCR: bigint;
830
+ apUSDMintFee: number;
831
+ apUSDRedeemFee: number;
832
+ xBNBMintFee: number;
833
+ xBNBRedeemFee: number;
834
+ apUSDMintDisabled: boolean;
835
+ }>;
836
+ return result.map((tier) => ({
837
+ minCR: tier.minCR,
838
+ apUSDMintFee: tier.apUSDMintFee,
839
+ apUSDRedeemFee: tier.apUSDRedeemFee,
840
+ xBNBMintFee: tier.xBNBMintFee,
841
+ xBNBRedeemFee: tier.xBNBRedeemFee,
842
+ apUSDMintDisabled: tier.apUSDMintDisabled,
843
+ }));
844
+ }
845
+
846
+ /**
847
+ * Get comprehensive vault (sApUSD) info for frontend display
848
+ * @returns Vault state including TVL, exchange rate, xBNB status
849
+ */
850
+ async getVaultInfo(): Promise<{
851
+ totalSupply: bigint;
852
+ totalAssets: bigint;
853
+ exchangeRate: bigint;
854
+ hasXBNB: boolean;
855
+ xBNBAmount: bigint;
856
+ }> {
857
+ const sApUSDAddress = await this.getSApUSD();
858
+ const [totalSupply, totalAssets, exchangeRate, conversion] = await Promise.all([
859
+ this.publicClient.readContract({
860
+ address: sApUSDAddress,
861
+ abi: SApUSDABI,
862
+ functionName: "totalSupply",
863
+ }) as Promise<bigint>,
864
+ this.publicClient.readContract({
865
+ address: sApUSDAddress,
866
+ abi: SApUSDABI,
867
+ functionName: "totalAssets",
868
+ }) as Promise<bigint>,
869
+ this.publicClient.readContract({
870
+ address: sApUSDAddress,
871
+ abi: SApUSDABI,
872
+ functionName: "exchangeRate",
873
+ }) as Promise<bigint>,
874
+ this.publicClient.readContract({
875
+ address: sApUSDAddress,
876
+ abi: SApUSDABI,
877
+ functionName: "hasStabilityConversion",
878
+ }) as Promise<[boolean, bigint]>,
879
+ ]);
880
+ return {
881
+ totalSupply,
882
+ totalAssets,
883
+ exchangeRate,
884
+ hasXBNB: conversion[0],
885
+ xBNBAmount: conversion[1],
886
+ };
887
+ }
888
+
889
+ /**
890
+ * Get full protocol dashboard data in one call (for frontend overview page)
891
+ * Batches all key metrics into a single multicall
892
+ */
893
+ async getProtocolOverview(): Promise<{
894
+ collateralRatio: bigint;
895
+ tvlBNB: bigint;
896
+ tvlUSD: bigint;
897
+ apUSDSupply: bigint;
898
+ xBNBSupply: bigint;
899
+ xBNBPriceUSD: bigint;
900
+ xBNBPriceBNB: bigint;
901
+ bnbPriceUSD: bigint;
902
+ currentFees: CurrentFeeTier;
903
+ stabilityMode: StabilityModeInfo;
904
+ totalStaked: bigint;
905
+ }> {
906
+ const [cr, tvlBNB, tvlUSD, apUSD, xBNB, xPriceUSD, xPriceBNB, bnbPrice, fees, sm, staked] =
907
+ await Promise.all([
908
+ this.getCollateralRatio(),
909
+ this.getTVLInBNB(),
910
+ this.getTVLInUSD(),
911
+ this.getApUSDSupply(),
912
+ this.getXBNBSupply(),
913
+ this.getXBNBPriceUSD(),
914
+ this.getXBNBPriceBNB(),
915
+ this.getBNBPriceUSD(),
916
+ this.getCurrentFeeTier(),
917
+ this.getStabilityMode(),
918
+ this.getTotalStaked(),
919
+ ]);
920
+ return {
921
+ collateralRatio: cr,
922
+ tvlBNB,
923
+ tvlUSD,
924
+ apUSDSupply: apUSD,
925
+ xBNBSupply: xBNB,
926
+ xBNBPriceUSD: xPriceUSD,
927
+ xBNBPriceBNB: xPriceBNB,
928
+ bnbPriceUSD: bnbPrice,
929
+ currentFees: fees,
930
+ stabilityMode: sm,
931
+ totalStaked: staked,
932
+ };
933
+ }
934
+
773
935
  async getCurrentFeeTier(): Promise<CurrentFeeTier> {
774
936
  try {
775
937
  const result = await this.publicClient.readContract({