@aspan/sdk 0.1.4 → 0.1.8

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.
@@ -9,7 +9,8 @@ export const DiamondABI = [
9
9
  name: "mintApUSD",
10
10
  inputs: [
11
11
  { name: "_lstToken", type: "address", internalType: "address" },
12
- { name: "_lstAmount", type: "uint256", internalType: "uint256" }
12
+ { name: "_lstAmount", type: "uint256", internalType: "uint256" },
13
+ { name: "_minOut", type: "uint256", internalType: "uint256" }
13
14
  ],
14
15
  outputs: [{ name: "apUSDAmount", type: "uint256", internalType: "uint256" }],
15
16
  stateMutability: "nonpayable"
@@ -19,7 +20,8 @@ export const DiamondABI = [
19
20
  name: "redeemApUSD",
20
21
  inputs: [
21
22
  { name: "_lstToken", type: "address", internalType: "address" },
22
- { name: "_apUSDAmount", type: "uint256", internalType: "uint256" }
23
+ { name: "_apUSDAmount", type: "uint256", internalType: "uint256" },
24
+ { name: "_minOut", type: "uint256", internalType: "uint256" }
23
25
  ],
24
26
  outputs: [{ name: "lstAmount", type: "uint256", internalType: "uint256" }],
25
27
  stateMutability: "nonpayable"
@@ -29,7 +31,8 @@ export const DiamondABI = [
29
31
  name: "mintXBNB",
30
32
  inputs: [
31
33
  { name: "_lstToken", type: "address", internalType: "address" },
32
- { name: "_lstAmount", type: "uint256", internalType: "uint256" }
34
+ { name: "_lstAmount", type: "uint256", internalType: "uint256" },
35
+ { name: "_minOut", type: "uint256", internalType: "uint256" }
33
36
  ],
34
37
  outputs: [{ name: "xBNBAmount", type: "uint256", internalType: "uint256" }],
35
38
  stateMutability: "nonpayable"
@@ -39,7 +42,8 @@ export const DiamondABI = [
39
42
  name: "redeemXBNB",
40
43
  inputs: [
41
44
  { name: "_lstToken", type: "address", internalType: "address" },
42
- { name: "_xBNBAmount", type: "uint256", internalType: "uint256" }
45
+ { name: "_xBNBAmount", type: "uint256", internalType: "uint256" },
46
+ { name: "_minOut", type: "uint256", internalType: "uint256" }
43
47
  ],
44
48
  outputs: [{ name: "lstAmount", type: "uint256", internalType: "uint256" }],
45
49
  stateMutability: "nonpayable"
package/src/bot/config.ts CHANGED
@@ -17,7 +17,7 @@ export interface BotConfig {
17
17
  statsReportInterval: number;
18
18
  crCheckInterval: number;
19
19
 
20
- // CR Thresholds (18 decimals: 1.5e18 = 150%)
20
+ // CR Thresholds (BPS: 15000 = 150%, matches contract format)
21
21
  crThresholdMode1: bigint;
22
22
  crThresholdMode2: bigint;
23
23
 
@@ -59,13 +59,13 @@ export function loadConfig(): BotConfig {
59
59
  ), // 5 min
60
60
  crCheckInterval: parseInt(process.env.CR_CHECK_INTERVAL_MS ?? "30000", 10), // 30 sec
61
61
 
62
- // CR Thresholds
62
+ // CR Thresholds (BPS format to match contract)
63
63
  crThresholdMode1: BigInt(
64
- process.env.CR_THRESHOLD_MODE1 ?? "1500000000000000000"
65
- ), // 150%
64
+ process.env.CR_THRESHOLD_MODE1 ?? "15000"
65
+ ), // 150% in BPS
66
66
  crThresholdMode2: BigInt(
67
- process.env.CR_THRESHOLD_MODE2 ?? "1300000000000000000"
68
- ), // 130%
67
+ process.env.CR_THRESHOLD_MODE2 ?? "13000"
68
+ ), // 130% in BPS
69
69
 
70
70
  // TVL
71
71
  tvlImpactThresholdPercent: parseFloat(
@@ -79,6 +79,14 @@ export class CRMonitor {
79
79
  const currentCR = stabilityMode.currentCR;
80
80
  const now = Date.now();
81
81
 
82
+ // Skip alerts when protocol is effectively empty
83
+ // CR < 100 BPS (1%) indicates negligible TVL/supply, not a real crisis
84
+ const MIN_MEANINGFUL_CR = 100n; // 1% in BPS
85
+ if (currentCR < MIN_MEANINGFUL_CR) {
86
+ console.log(`[CRMonitor] Protocol effectively empty (CR = ${formatCR(currentCR)}), skipping alerts`);
87
+ return;
88
+ }
89
+
82
90
  // Check Mode 2 threshold (< 130%)
83
91
  if (currentCR < this.config.crThresholdMode2) {
84
92
  if (!this.state.mode2Triggered || this.shouldRepeatAlert(now)) {
@@ -143,7 +143,7 @@ export class StatsMonitor {
143
143
  },
144
144
  {
145
145
  title: "xBNB Supply",
146
- value: formatAmount(stats.xBNBSupply, 4),
146
+ value: formatAmount(stats.xBNBSupply, 18),
147
147
  short: true,
148
148
  },
149
149
  {
package/src/client.ts CHANGED
@@ -52,8 +52,10 @@ export interface AspanClientConfig {
52
52
  }
53
53
 
54
54
  export interface AspanWriteClientConfig extends AspanClientConfig {
55
- /** Account for signing transactions */
56
- account: Account;
55
+ /** Account for signing transactions (required if walletClient not provided) */
56
+ account?: Account;
57
+ /** External wallet client (for browser environments with wagmi/rainbowkit) */
58
+ walletClient?: WalletClient<Transport, Chain, Account>;
57
59
  }
58
60
 
59
61
  // ============ Read-Only Client ============
@@ -100,25 +102,28 @@ export class AspanReadClient {
100
102
  * Get comprehensive protocol statistics
101
103
  */
102
104
  async getProtocolStats(): Promise<ProtocolStats> {
103
- const [
104
- tvlInBNB,
105
- tvlInUSD,
106
- collateralRatio,
107
- apUSDSupply,
108
- xBNBSupply,
109
- xBNBPriceBNB,
110
- xBNBPriceUSD,
111
- effectiveLeverage,
112
- ] = await Promise.all([
113
- this.getTVLInBNB(),
114
- this.getTVLInUSD(),
115
- this.getCollateralRatio(),
116
- this.getApUSDSupply(),
117
- this.getXBNBSupply(),
118
- this.getXBNBPriceBNB(),
119
- this.getXBNBPriceUSD(),
120
- this.getEffectiveLeverage(),
121
- ]);
105
+ // First get supplies to determine if xBNB exists
106
+ const [tvlInBNB, tvlInUSD, collateralRatio, apUSDSupply, xBNBSupply] =
107
+ await Promise.all([
108
+ this.getTVLInBNB(),
109
+ this.getTVLInUSD(),
110
+ this.getCollateralRatio(),
111
+ this.getApUSDSupply(),
112
+ this.getXBNBSupply(),
113
+ ]);
114
+
115
+ // Only fetch xBNB price and leverage if xBNB supply exists
116
+ let xBNBPriceBNB = 0n;
117
+ let xBNBPriceUSD = 0n;
118
+ let effectiveLeverage = 0n;
119
+
120
+ if (xBNBSupply > 0n) {
121
+ [xBNBPriceBNB, xBNBPriceUSD, effectiveLeverage] = await Promise.all([
122
+ this.getXBNBPriceBNB(),
123
+ this.getXBNBPriceUSD(),
124
+ this.getEffectiveLeverage(),
125
+ ]);
126
+ }
122
127
 
123
128
  return {
124
129
  tvlInBNB,
@@ -216,8 +221,8 @@ export class AspanReadClient {
216
221
  functionName: "getCollateralRatio",
217
222
  });
218
223
  // Contract returns max uint256 when supply is zero (infinite CR)
219
- // Treat anything above 1000000% (10000 * 1e18) as zero/undefined
220
- if (cr > 10000n * 10n ** 18n) {
224
+ // CR is in BPS format (10000 = 100%), anything above 100000000 BPS is invalid
225
+ if (cr > 100000000n) {
221
226
  return 0n;
222
227
  }
223
228
  return cr;
@@ -232,11 +237,17 @@ export class AspanReadClient {
232
237
 
233
238
  async getXBNBPriceBNB(): Promise<bigint> {
234
239
  try {
235
- return await this.publicClient.readContract({
240
+ const price = await this.publicClient.readContract({
236
241
  address: this.diamondAddress,
237
242
  abi: DiamondABI,
238
243
  functionName: "getXBNBPriceBNB",
239
244
  });
245
+ // Contract returns extreme values when xBNB supply is zero
246
+ // Normal xBNB price should be < 1000 BNB (1000 * 1e18)
247
+ if (price > 1000n * 10n ** 18n) {
248
+ return 0n;
249
+ }
250
+ return price;
240
251
  } catch (error) {
241
252
  // Price undefined when no xBNB exists
242
253
  if (this.isZeroSupplyError(error)) {
@@ -248,11 +259,17 @@ export class AspanReadClient {
248
259
 
249
260
  async getXBNBPriceUSD(): Promise<bigint> {
250
261
  try {
251
- return await this.publicClient.readContract({
262
+ const price = await this.publicClient.readContract({
252
263
  address: this.diamondAddress,
253
264
  abi: DiamondABI,
254
265
  functionName: "getXBNBPriceUSD",
255
266
  });
267
+ // Contract returns extreme values when xBNB supply is zero
268
+ // xBNBPriceUSD is in 18 decimals, normal price should be < $1,000,000 (1e6 * 1e18)
269
+ if (price > 1000000n * 10n ** 18n) {
270
+ return 0n;
271
+ }
272
+ return price;
256
273
  } catch (error) {
257
274
  // Price undefined when no xBNB exists
258
275
  if (this.isZeroSupplyError(error)) {
@@ -264,11 +281,17 @@ export class AspanReadClient {
264
281
 
265
282
  async getEffectiveLeverage(): Promise<bigint> {
266
283
  try {
267
- return await this.publicClient.readContract({
284
+ const leverage = await this.publicClient.readContract({
268
285
  address: this.diamondAddress,
269
286
  abi: DiamondABI,
270
287
  functionName: "getEffectiveLeverage",
271
288
  });
289
+ // Contract returns extreme values when xBNB supply is zero
290
+ // Normal leverage should be < 100x (100 * 1e18)
291
+ if (leverage > 100n * 10n ** 18n) {
292
+ return 0n;
293
+ }
294
+ return leverage;
272
295
  } catch (error) {
273
296
  // Leverage undefined when no xBNB exists
274
297
  if (this.isZeroSupplyError(error)) {
@@ -334,8 +357,15 @@ export class AspanReadClient {
334
357
  functionName: "getCurrentFees",
335
358
  });
336
359
 
360
+ let currentCR = result[0];
361
+ // Contract returns max uint256 when supply is zero (infinite CR)
362
+ // CR is in BPS format (10000 = 100%)
363
+ if (currentCR > 100000000n) {
364
+ currentCR = 0n;
365
+ }
366
+
337
367
  return {
338
- currentCR: result[0],
368
+ currentCR,
339
369
  tierMinCR: result[1],
340
370
  apUSDMintFee: result[2],
341
371
  apUSDRedeemFee: result[3],
@@ -398,12 +428,13 @@ export class AspanReadClient {
398
428
  };
399
429
  }
400
430
 
401
- async getSupportedLSTs(): Promise<readonly Address[]> {
402
- return this.publicClient.readContract({
431
+ async getSupportedLSTs(): Promise<Address[]> {
432
+ const result = await this.publicClient.readContract({
403
433
  address: this.diamondAddress,
404
434
  abi: DiamondABI,
405
435
  functionName: "getSupportedLSTs",
406
436
  });
437
+ return [...result];
407
438
  }
408
439
 
409
440
  async isLSTSupported(lstToken: Address): Promise<boolean> {
@@ -710,6 +741,13 @@ export class AspanReadClient {
710
741
  functionName: "getCurrentFeeTier",
711
742
  });
712
743
 
744
+ let currentCR = result[6];
745
+ // Contract returns max uint256 when supply is zero (infinite CR)
746
+ // CR is in BPS format (10000 = 100%)
747
+ if (currentCR > 100000000n) {
748
+ currentCR = 0n;
749
+ }
750
+
713
751
  return {
714
752
  minCR: result[0],
715
753
  apUSDMintFee: result[1],
@@ -717,7 +755,7 @@ export class AspanReadClient {
717
755
  xBNBMintFee: result[3],
718
756
  xBNBRedeemFee: result[4],
719
757
  apUSDMintDisabled: result[5],
720
- currentCR: result[6],
758
+ currentCR,
721
759
  };
722
760
  } catch (error) {
723
761
  // Return default fee tier when protocol is empty
@@ -770,9 +808,16 @@ export class AspanReadClient {
770
808
  functionName: "getStabilityMode",
771
809
  });
772
810
 
811
+ let currentCR = result[1];
812
+ // Contract returns max uint256 when supply is zero (infinite CR)
813
+ // CR is in BPS format (10000 = 100%)
814
+ if (currentCR > 100000000n) {
815
+ currentCR = 0n;
816
+ }
817
+
773
818
  return {
774
819
  mode: result[0],
775
- currentCR: result[1],
820
+ currentCR,
776
821
  };
777
822
  } catch (error) {
778
823
  // Return normal mode when protocol is empty
@@ -794,9 +839,16 @@ export class AspanReadClient {
794
839
  functionName: "canTriggerStabilityMode2",
795
840
  });
796
841
 
842
+ let currentCR = result[1];
843
+ // Contract returns max uint256 when supply is zero (infinite CR)
844
+ // CR is in BPS format (10000 = 100%)
845
+ if (currentCR > 100000000n) {
846
+ currentCR = 0n;
847
+ }
848
+
797
849
  return {
798
850
  canTrigger: result[0],
799
- currentCR: result[1],
851
+ currentCR,
800
852
  potentialConversion: result[2],
801
853
  };
802
854
  } catch (error) {
@@ -829,16 +881,24 @@ export class AspanReadClient {
829
881
  * Full client with write capabilities for interacting with Aspan Protocol
830
882
  */
831
883
  export class AspanClient extends AspanReadClient {
832
- private readonly walletClient: WalletClient<Transport, Chain, Account>;
884
+ private readonly walletClient: WalletClient;
833
885
 
834
886
  constructor(config: AspanWriteClientConfig) {
835
887
  super(config);
836
888
 
837
- this.walletClient = createWalletClient({
838
- account: config.account,
839
- chain: this.chain,
840
- transport: http(config.rpcUrl),
841
- });
889
+ // Use external walletClient if provided (browser environment with wagmi/rainbowkit)
890
+ // Otherwise create internal walletClient (server environment)
891
+ if (config.walletClient) {
892
+ this.walletClient = config.walletClient;
893
+ } else if (config.account) {
894
+ this.walletClient = createWalletClient({
895
+ account: config.account,
896
+ chain: this.chain,
897
+ transport: http(config.rpcUrl),
898
+ });
899
+ } else {
900
+ throw new Error('Either walletClient or account must be provided');
901
+ }
842
902
  }
843
903
 
844
904
  // ============ Pool Write Functions ============
@@ -850,10 +910,12 @@ export class AspanClient extends AspanReadClient {
850
910
  */
851
911
  async mintApUSD(params: MintApUSDParams): Promise<Hash> {
852
912
  return this.walletClient.writeContract({
913
+ chain: this.chain,
914
+ account: this.walletClient.account!,
853
915
  address: this.diamondAddress,
854
916
  abi: DiamondABI,
855
917
  functionName: "mintApUSD",
856
- args: [params.lstToken, params.lstAmount],
918
+ args: [params.lstToken, params.lstAmount, params.minOut ?? 0n],
857
919
  });
858
920
  }
859
921
 
@@ -864,10 +926,12 @@ export class AspanClient extends AspanReadClient {
864
926
  */
865
927
  async redeemApUSD(params: RedeemApUSDParams): Promise<Hash> {
866
928
  return this.walletClient.writeContract({
929
+ chain: this.chain,
930
+ account: this.walletClient.account!,
867
931
  address: this.diamondAddress,
868
932
  abi: DiamondABI,
869
933
  functionName: "redeemApUSD",
870
- args: [params.lstToken, params.apUSDAmount],
934
+ args: [params.lstToken, params.apUSDAmount, params.minOut ?? 0n],
871
935
  });
872
936
  }
873
937
 
@@ -878,10 +942,12 @@ export class AspanClient extends AspanReadClient {
878
942
  */
879
943
  async mintXBNB(params: MintXBNBParams): Promise<Hash> {
880
944
  return this.walletClient.writeContract({
945
+ chain: this.chain,
946
+ account: this.walletClient.account!,
881
947
  address: this.diamondAddress,
882
948
  abi: DiamondABI,
883
949
  functionName: "mintXBNB",
884
- args: [params.lstToken, params.lstAmount],
950
+ args: [params.lstToken, params.lstAmount, params.minOut ?? 0n],
885
951
  });
886
952
  }
887
953
 
@@ -892,10 +958,12 @@ export class AspanClient extends AspanReadClient {
892
958
  */
893
959
  async redeemXBNB(params: RedeemXBNBParams): Promise<Hash> {
894
960
  return this.walletClient.writeContract({
961
+ chain: this.chain,
962
+ account: this.walletClient.account!,
895
963
  address: this.diamondAddress,
896
964
  abi: DiamondABI,
897
965
  functionName: "redeemXBNB",
898
- args: [params.lstToken, params.xBNBAmount],
966
+ args: [params.lstToken, params.xBNBAmount, params.minOut ?? 0n],
899
967
  });
900
968
  }
901
969
 
@@ -908,6 +976,8 @@ export class AspanClient extends AspanReadClient {
908
976
  */
909
977
  async deposit(params: DepositParams): Promise<Hash> {
910
978
  return this.walletClient.writeContract({
979
+ chain: this.chain,
980
+ account: this.walletClient.account!,
911
981
  address: this.diamondAddress,
912
982
  abi: DiamondABI,
913
983
  functionName: "deposit",
@@ -922,6 +992,8 @@ export class AspanClient extends AspanReadClient {
922
992
  */
923
993
  async withdraw(params: WithdrawParams): Promise<Hash> {
924
994
  return this.walletClient.writeContract({
995
+ chain: this.chain,
996
+ account: this.walletClient.account!,
925
997
  address: this.diamondAddress,
926
998
  abi: DiamondABI,
927
999
  functionName: "withdraw",
@@ -936,6 +1008,8 @@ export class AspanClient extends AspanReadClient {
936
1008
  */
937
1009
  async withdrawAssets(params: WithdrawAssetsParams): Promise<Hash> {
938
1010
  return this.walletClient.writeContract({
1011
+ chain: this.chain,
1012
+ account: this.walletClient.account!,
939
1013
  address: this.diamondAddress,
940
1014
  abi: DiamondABI,
941
1015
  functionName: "withdrawAssets",
@@ -949,6 +1023,8 @@ export class AspanClient extends AspanReadClient {
949
1023
  */
950
1024
  async harvestYield(): Promise<Hash> {
951
1025
  return this.walletClient.writeContract({
1026
+ chain: this.chain,
1027
+ account: this.walletClient.account!,
952
1028
  address: this.diamondAddress,
953
1029
  abi: DiamondABI,
954
1030
  functionName: "harvestYield",
package/src/index.ts CHANGED
@@ -102,12 +102,13 @@ export function formatFeeBPS(bps: number): string {
102
102
 
103
103
  /**
104
104
  * Format collateral ratio to percentage string
105
- * @param cr Collateral ratio (18 decimals, 1e18 = 100%)
106
- * @returns Percentage string (e.g., "150%")
105
+ * @param cr Collateral ratio in BPS (10000 = 100%, contract format)
106
+ * @returns Percentage string (e.g., "150.00%")
107
107
  */
108
108
  export function formatCR(cr: bigint): string {
109
- const percentage = (cr * 100n) / PRECISION;
110
- return `${percentage}%`;
109
+ // CR is in BPS: 15000 = 150%
110
+ const percentage = Number(cr) / 100;
111
+ return `${percentage.toFixed(2)}%`;
111
112
  }
112
113
 
113
114
  /**
package/src/types.ts CHANGED
@@ -90,24 +90,32 @@ export interface StabilityMode2Result {
90
90
  export interface MintApUSDParams {
91
91
  lstToken: Address;
92
92
  lstAmount: bigint;
93
+ /** Minimum apUSD output for slippage protection (default: 0n) */
94
+ minOut?: bigint;
93
95
  }
94
96
 
95
97
  /** Parameters for redeeming apUSD */
96
98
  export interface RedeemApUSDParams {
97
99
  lstToken: Address;
98
100
  apUSDAmount: bigint;
101
+ /** Minimum LST output for slippage protection (default: 0n) */
102
+ minOut?: bigint;
99
103
  }
100
104
 
101
105
  /** Parameters for minting xBNB */
102
106
  export interface MintXBNBParams {
103
107
  lstToken: Address;
104
108
  lstAmount: bigint;
109
+ /** Minimum xBNB output for slippage protection (default: 0n) */
110
+ minOut?: bigint;
105
111
  }
106
112
 
107
113
  /** Parameters for redeeming xBNB */
108
114
  export interface RedeemXBNBParams {
109
115
  lstToken: Address;
110
116
  xBNBAmount: bigint;
117
+ /** Minimum LST output for slippage protection (default: 0n) */
118
+ minOut?: bigint;
111
119
  }
112
120
 
113
121
  /** Parameters for depositing to stability pool */