@aspan/sdk 0.1.4
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 +451 -0
- package/dist/index.d.mts +1324 -0
- package/dist/index.d.ts +1324 -0
- package/dist/index.js +1451 -0
- package/dist/index.mjs +1413 -0
- package/package.json +63 -0
- package/src/abi/diamond.ts +531 -0
- package/src/bot/config.ts +84 -0
- package/src/bot/index.ts +133 -0
- package/src/bot/monitors/cr-monitor.ts +182 -0
- package/src/bot/monitors/mempool-monitor.ts +167 -0
- package/src/bot/monitors/stats-monitor.ts +287 -0
- package/src/bot/services/rpc-client.ts +61 -0
- package/src/bot/services/slack.ts +95 -0
- package/src/client.ts +1030 -0
- package/src/index.ts +144 -0
- package/src/types.ts +237 -0
package/src/client.ts
ADDED
|
@@ -0,0 +1,1030 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Aspan SDK Client
|
|
3
|
+
* TypeScript client for interacting with the Aspan Protocol
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
createPublicClient,
|
|
8
|
+
createWalletClient,
|
|
9
|
+
http,
|
|
10
|
+
type Address,
|
|
11
|
+
type PublicClient,
|
|
12
|
+
type WalletClient,
|
|
13
|
+
type Transport,
|
|
14
|
+
type Chain,
|
|
15
|
+
type Account,
|
|
16
|
+
type Hash,
|
|
17
|
+
} from "viem";
|
|
18
|
+
import { bsc, bscTestnet } from "viem/chains";
|
|
19
|
+
import { DiamondABI } from "./abi/diamond";
|
|
20
|
+
import type {
|
|
21
|
+
LSTInfo,
|
|
22
|
+
LSTYieldInfo,
|
|
23
|
+
FeeTier,
|
|
24
|
+
CurrentFeeTier,
|
|
25
|
+
CurrentFees,
|
|
26
|
+
OracleBounds,
|
|
27
|
+
StabilityModeInfo,
|
|
28
|
+
StabilityMode2Info,
|
|
29
|
+
ProtocolStats,
|
|
30
|
+
StabilityPoolStats,
|
|
31
|
+
YieldStats,
|
|
32
|
+
UserStabilityPoolPosition,
|
|
33
|
+
TokenAddresses,
|
|
34
|
+
MintApUSDParams,
|
|
35
|
+
RedeemApUSDParams,
|
|
36
|
+
MintXBNBParams,
|
|
37
|
+
RedeemXBNBParams,
|
|
38
|
+
DepositParams,
|
|
39
|
+
WithdrawParams,
|
|
40
|
+
WithdrawAssetsParams,
|
|
41
|
+
} from "./types";
|
|
42
|
+
|
|
43
|
+
// ============ Configuration ============
|
|
44
|
+
|
|
45
|
+
export interface AspanClientConfig {
|
|
46
|
+
/** Diamond contract address */
|
|
47
|
+
diamondAddress: Address;
|
|
48
|
+
/** Chain to connect to */
|
|
49
|
+
chain?: Chain;
|
|
50
|
+
/** RPC URL (optional, uses default if not provided) */
|
|
51
|
+
rpcUrl?: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface AspanWriteClientConfig extends AspanClientConfig {
|
|
55
|
+
/** Account for signing transactions */
|
|
56
|
+
account: Account;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ============ Read-Only Client ============
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Read-only client for querying Aspan Protocol state
|
|
63
|
+
*/
|
|
64
|
+
export class AspanReadClient {
|
|
65
|
+
protected readonly publicClient: PublicClient;
|
|
66
|
+
protected readonly diamondAddress: Address;
|
|
67
|
+
protected readonly chain: Chain;
|
|
68
|
+
|
|
69
|
+
// Known error signatures for zero supply states
|
|
70
|
+
private static readonly ZERO_SUPPLY_ERROR_SIGNATURES = [
|
|
71
|
+
"0x403e7fa6", // Custom error when xBNB supply is zero
|
|
72
|
+
];
|
|
73
|
+
|
|
74
|
+
constructor(config: AspanClientConfig) {
|
|
75
|
+
this.diamondAddress = config.diamondAddress;
|
|
76
|
+
this.chain = config.chain ?? bsc;
|
|
77
|
+
|
|
78
|
+
this.publicClient = createPublicClient({
|
|
79
|
+
chain: this.chain,
|
|
80
|
+
transport: http(config.rpcUrl),
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Check if error is a known zero supply error from the contract
|
|
86
|
+
*/
|
|
87
|
+
protected isZeroSupplyError(error: unknown): boolean {
|
|
88
|
+
if (error && typeof error === "object" && "message" in error) {
|
|
89
|
+
const message = (error as { message: string }).message;
|
|
90
|
+
return AspanReadClient.ZERO_SUPPLY_ERROR_SIGNATURES.some((sig) =>
|
|
91
|
+
message.includes(sig)
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// ============ Protocol Stats ============
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Get comprehensive protocol statistics
|
|
101
|
+
*/
|
|
102
|
+
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
|
+
]);
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
tvlInBNB,
|
|
125
|
+
tvlInUSD,
|
|
126
|
+
collateralRatio,
|
|
127
|
+
apUSDSupply,
|
|
128
|
+
xBNBSupply,
|
|
129
|
+
xBNBPriceBNB,
|
|
130
|
+
xBNBPriceUSD,
|
|
131
|
+
effectiveLeverage,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Get stability pool statistics
|
|
137
|
+
*/
|
|
138
|
+
async getStabilityPoolStats(): Promise<StabilityPoolStats> {
|
|
139
|
+
const [totalStaked, exchangeRate, pendingYield] = await Promise.all([
|
|
140
|
+
this.getTotalStaked(),
|
|
141
|
+
this.getExchangeRate(),
|
|
142
|
+
this.getPendingYield(),
|
|
143
|
+
]);
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
totalStaked,
|
|
147
|
+
exchangeRate,
|
|
148
|
+
pendingYield,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Get yield statistics
|
|
154
|
+
*/
|
|
155
|
+
async getYieldStats(): Promise<YieldStats> {
|
|
156
|
+
const [
|
|
157
|
+
totalYieldGenerated,
|
|
158
|
+
pendingYield,
|
|
159
|
+
lastHarvestTimestamp,
|
|
160
|
+
minHarvestInterval,
|
|
161
|
+
previewedHarvest,
|
|
162
|
+
] = await Promise.all([
|
|
163
|
+
this.getTotalYieldGenerated(),
|
|
164
|
+
this.getPendingYield(),
|
|
165
|
+
this.getLastHarvestTimestamp(),
|
|
166
|
+
this.getMinHarvestInterval(),
|
|
167
|
+
this.previewHarvest(),
|
|
168
|
+
]);
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
totalYieldGenerated,
|
|
172
|
+
pendingYield,
|
|
173
|
+
lastHarvestTimestamp,
|
|
174
|
+
minHarvestInterval,
|
|
175
|
+
previewedHarvest,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ============ Pool View Functions ============
|
|
180
|
+
|
|
181
|
+
async getTVLInBNB(): Promise<bigint> {
|
|
182
|
+
try {
|
|
183
|
+
return await this.publicClient.readContract({
|
|
184
|
+
address: this.diamondAddress,
|
|
185
|
+
abi: DiamondABI,
|
|
186
|
+
functionName: "getTVLInBNB",
|
|
187
|
+
});
|
|
188
|
+
} catch (error) {
|
|
189
|
+
if (this.isZeroSupplyError(error)) {
|
|
190
|
+
return 0n;
|
|
191
|
+
}
|
|
192
|
+
throw error;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async getTVLInUSD(): Promise<bigint> {
|
|
197
|
+
try {
|
|
198
|
+
return await this.publicClient.readContract({
|
|
199
|
+
address: this.diamondAddress,
|
|
200
|
+
abi: DiamondABI,
|
|
201
|
+
functionName: "getTVLInUSD",
|
|
202
|
+
});
|
|
203
|
+
} catch (error) {
|
|
204
|
+
if (this.isZeroSupplyError(error)) {
|
|
205
|
+
return 0n;
|
|
206
|
+
}
|
|
207
|
+
throw error;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async getCollateralRatio(): Promise<bigint> {
|
|
212
|
+
try {
|
|
213
|
+
const cr = await this.publicClient.readContract({
|
|
214
|
+
address: this.diamondAddress,
|
|
215
|
+
abi: DiamondABI,
|
|
216
|
+
functionName: "getCollateralRatio",
|
|
217
|
+
});
|
|
218
|
+
// 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) {
|
|
221
|
+
return 0n;
|
|
222
|
+
}
|
|
223
|
+
return cr;
|
|
224
|
+
} catch (error) {
|
|
225
|
+
// CR undefined when no apUSD supply exists
|
|
226
|
+
if (this.isZeroSupplyError(error)) {
|
|
227
|
+
return 0n;
|
|
228
|
+
}
|
|
229
|
+
throw error;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
async getXBNBPriceBNB(): Promise<bigint> {
|
|
234
|
+
try {
|
|
235
|
+
return await this.publicClient.readContract({
|
|
236
|
+
address: this.diamondAddress,
|
|
237
|
+
abi: DiamondABI,
|
|
238
|
+
functionName: "getXBNBPriceBNB",
|
|
239
|
+
});
|
|
240
|
+
} catch (error) {
|
|
241
|
+
// Price undefined when no xBNB exists
|
|
242
|
+
if (this.isZeroSupplyError(error)) {
|
|
243
|
+
return 0n;
|
|
244
|
+
}
|
|
245
|
+
throw error;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async getXBNBPriceUSD(): Promise<bigint> {
|
|
250
|
+
try {
|
|
251
|
+
return await this.publicClient.readContract({
|
|
252
|
+
address: this.diamondAddress,
|
|
253
|
+
abi: DiamondABI,
|
|
254
|
+
functionName: "getXBNBPriceUSD",
|
|
255
|
+
});
|
|
256
|
+
} catch (error) {
|
|
257
|
+
// Price undefined when no xBNB exists
|
|
258
|
+
if (this.isZeroSupplyError(error)) {
|
|
259
|
+
return 0n;
|
|
260
|
+
}
|
|
261
|
+
throw error;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
async getEffectiveLeverage(): Promise<bigint> {
|
|
266
|
+
try {
|
|
267
|
+
return await this.publicClient.readContract({
|
|
268
|
+
address: this.diamondAddress,
|
|
269
|
+
abi: DiamondABI,
|
|
270
|
+
functionName: "getEffectiveLeverage",
|
|
271
|
+
});
|
|
272
|
+
} catch (error) {
|
|
273
|
+
// Leverage undefined when no xBNB exists
|
|
274
|
+
if (this.isZeroSupplyError(error)) {
|
|
275
|
+
return 0n;
|
|
276
|
+
}
|
|
277
|
+
throw error;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
async getApUSDSupply(): Promise<bigint> {
|
|
282
|
+
try {
|
|
283
|
+
return await this.publicClient.readContract({
|
|
284
|
+
address: this.diamondAddress,
|
|
285
|
+
abi: DiamondABI,
|
|
286
|
+
functionName: "getApUSDSupply",
|
|
287
|
+
});
|
|
288
|
+
} catch (error) {
|
|
289
|
+
// Contract reverts with custom error when no apUSD has been minted
|
|
290
|
+
if (this.isZeroSupplyError(error)) {
|
|
291
|
+
return 0n;
|
|
292
|
+
}
|
|
293
|
+
throw error;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
async getXBNBSupply(): Promise<bigint> {
|
|
298
|
+
try {
|
|
299
|
+
return await this.publicClient.readContract({
|
|
300
|
+
address: this.diamondAddress,
|
|
301
|
+
abi: DiamondABI,
|
|
302
|
+
functionName: "getXBNBSupply",
|
|
303
|
+
});
|
|
304
|
+
} catch (error) {
|
|
305
|
+
// Contract reverts with custom error when no xBNB has been minted
|
|
306
|
+
if (this.isZeroSupplyError(error)) {
|
|
307
|
+
return 0n;
|
|
308
|
+
}
|
|
309
|
+
throw error;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
async getLSTCollateral(lstToken: Address): Promise<bigint> {
|
|
314
|
+
try {
|
|
315
|
+
return await this.publicClient.readContract({
|
|
316
|
+
address: this.diamondAddress,
|
|
317
|
+
abi: DiamondABI,
|
|
318
|
+
functionName: "getLSTCollateral",
|
|
319
|
+
args: [lstToken],
|
|
320
|
+
});
|
|
321
|
+
} catch (error) {
|
|
322
|
+
if (this.isZeroSupplyError(error)) {
|
|
323
|
+
return 0n;
|
|
324
|
+
}
|
|
325
|
+
throw error;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
async getCurrentFees(): Promise<CurrentFees> {
|
|
330
|
+
try {
|
|
331
|
+
const result = await this.publicClient.readContract({
|
|
332
|
+
address: this.diamondAddress,
|
|
333
|
+
abi: DiamondABI,
|
|
334
|
+
functionName: "getCurrentFees",
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
return {
|
|
338
|
+
currentCR: result[0],
|
|
339
|
+
tierMinCR: result[1],
|
|
340
|
+
apUSDMintFee: result[2],
|
|
341
|
+
apUSDRedeemFee: result[3],
|
|
342
|
+
xBNBMintFee: result[4],
|
|
343
|
+
xBNBRedeemFee: result[5],
|
|
344
|
+
apUSDMintDisabled: result[6],
|
|
345
|
+
};
|
|
346
|
+
} catch (error) {
|
|
347
|
+
// Return default fees when protocol is empty
|
|
348
|
+
if (this.isZeroSupplyError(error)) {
|
|
349
|
+
return {
|
|
350
|
+
currentCR: 0n,
|
|
351
|
+
tierMinCR: 0n,
|
|
352
|
+
apUSDMintFee: 0,
|
|
353
|
+
apUSDRedeemFee: 0,
|
|
354
|
+
xBNBMintFee: 0,
|
|
355
|
+
xBNBRedeemFee: 0,
|
|
356
|
+
apUSDMintDisabled: false,
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
throw error;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// ============ Oracle View Functions ============
|
|
364
|
+
|
|
365
|
+
async getBNBPriceUSD(): Promise<bigint> {
|
|
366
|
+
return this.publicClient.readContract({
|
|
367
|
+
address: this.diamondAddress,
|
|
368
|
+
abi: DiamondABI,
|
|
369
|
+
functionName: "getBNBPriceUSD",
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
async getLSTPriceUSD(lstToken: Address): Promise<bigint> {
|
|
374
|
+
return this.publicClient.readContract({
|
|
375
|
+
address: this.diamondAddress,
|
|
376
|
+
abi: DiamondABI,
|
|
377
|
+
functionName: "getLSTPriceUSD",
|
|
378
|
+
args: [lstToken],
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
async getLSTInfo(lstToken: Address): Promise<LSTInfo> {
|
|
383
|
+
const result = await this.publicClient.readContract({
|
|
384
|
+
address: this.diamondAddress,
|
|
385
|
+
abi: DiamondABI,
|
|
386
|
+
functionName: "getLSTInfo",
|
|
387
|
+
args: [lstToken],
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
return {
|
|
391
|
+
isAccepted: result[0],
|
|
392
|
+
priceFeed: result[1],
|
|
393
|
+
manualPriceUSD: result[2],
|
|
394
|
+
collateralAmount: result[3],
|
|
395
|
+
decimals: result[4],
|
|
396
|
+
exchangeRateProvider: result[5],
|
|
397
|
+
useIntrinsicValue: result[6],
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
async getSupportedLSTs(): Promise<readonly Address[]> {
|
|
402
|
+
return this.publicClient.readContract({
|
|
403
|
+
address: this.diamondAddress,
|
|
404
|
+
abi: DiamondABI,
|
|
405
|
+
functionName: "getSupportedLSTs",
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
async isLSTSupported(lstToken: Address): Promise<boolean> {
|
|
410
|
+
return this.publicClient.readContract({
|
|
411
|
+
address: this.diamondAddress,
|
|
412
|
+
abi: DiamondABI,
|
|
413
|
+
functionName: "isLSTSupported",
|
|
414
|
+
args: [lstToken],
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
async getBNBPriceFeed(): Promise<Address> {
|
|
419
|
+
return this.publicClient.readContract({
|
|
420
|
+
address: this.diamondAddress,
|
|
421
|
+
abi: DiamondABI,
|
|
422
|
+
functionName: "bnbPriceFeed",
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
async getOracleBounds(priceFeed: Address): Promise<OracleBounds> {
|
|
427
|
+
const result = await this.publicClient.readContract({
|
|
428
|
+
address: this.diamondAddress,
|
|
429
|
+
abi: DiamondABI,
|
|
430
|
+
functionName: "getOracleBounds",
|
|
431
|
+
args: [priceFeed],
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
return {
|
|
435
|
+
minPrice: result[0],
|
|
436
|
+
maxPrice: result[1],
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// ============ Stability Pool View Functions ============
|
|
441
|
+
|
|
442
|
+
async getShares(user: Address): Promise<bigint> {
|
|
443
|
+
return this.publicClient.readContract({
|
|
444
|
+
address: this.diamondAddress,
|
|
445
|
+
abi: DiamondABI,
|
|
446
|
+
functionName: "getShares",
|
|
447
|
+
args: [user],
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
async getBalance(user: Address): Promise<bigint> {
|
|
452
|
+
return this.publicClient.readContract({
|
|
453
|
+
address: this.diamondAddress,
|
|
454
|
+
abi: DiamondABI,
|
|
455
|
+
functionName: "getBalance",
|
|
456
|
+
args: [user],
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
async getUserStabilityPoolPosition(
|
|
461
|
+
user: Address
|
|
462
|
+
): Promise<UserStabilityPoolPosition> {
|
|
463
|
+
const [shares, balance] = await Promise.all([
|
|
464
|
+
this.getShares(user),
|
|
465
|
+
this.getBalance(user),
|
|
466
|
+
]);
|
|
467
|
+
|
|
468
|
+
return { shares, balance };
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
async getExchangeRate(): Promise<bigint> {
|
|
472
|
+
try {
|
|
473
|
+
return await this.publicClient.readContract({
|
|
474
|
+
address: this.diamondAddress,
|
|
475
|
+
abi: DiamondABI,
|
|
476
|
+
functionName: "getExchangeRate",
|
|
477
|
+
});
|
|
478
|
+
} catch (error) {
|
|
479
|
+
// Exchange rate defaults to 1e18 when stability pool is empty
|
|
480
|
+
if (this.isZeroSupplyError(error)) {
|
|
481
|
+
return 10n ** 18n; // 1:1 ratio
|
|
482
|
+
}
|
|
483
|
+
throw error;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
async getTotalStaked(): Promise<bigint> {
|
|
488
|
+
try {
|
|
489
|
+
return await this.publicClient.readContract({
|
|
490
|
+
address: this.diamondAddress,
|
|
491
|
+
abi: DiamondABI,
|
|
492
|
+
functionName: "getTotalStaked",
|
|
493
|
+
});
|
|
494
|
+
} catch (error) {
|
|
495
|
+
if (this.isZeroSupplyError(error)) {
|
|
496
|
+
return 0n;
|
|
497
|
+
}
|
|
498
|
+
throw error;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
async previewDeposit(assets: bigint): Promise<bigint> {
|
|
503
|
+
try {
|
|
504
|
+
return await this.publicClient.readContract({
|
|
505
|
+
address: this.diamondAddress,
|
|
506
|
+
abi: DiamondABI,
|
|
507
|
+
functionName: "previewDeposit",
|
|
508
|
+
args: [assets],
|
|
509
|
+
});
|
|
510
|
+
} catch (error) {
|
|
511
|
+
if (this.isZeroSupplyError(error)) {
|
|
512
|
+
return assets; // 1:1 when pool is empty
|
|
513
|
+
}
|
|
514
|
+
throw error;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
async previewRedeem(shares: bigint): Promise<bigint> {
|
|
519
|
+
try {
|
|
520
|
+
return await this.publicClient.readContract({
|
|
521
|
+
address: this.diamondAddress,
|
|
522
|
+
abi: DiamondABI,
|
|
523
|
+
functionName: "previewRedeem",
|
|
524
|
+
args: [shares],
|
|
525
|
+
});
|
|
526
|
+
} catch (error) {
|
|
527
|
+
if (this.isZeroSupplyError(error)) {
|
|
528
|
+
return shares; // 1:1 when pool is empty
|
|
529
|
+
}
|
|
530
|
+
throw error;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
async getPendingYield(): Promise<bigint> {
|
|
535
|
+
try {
|
|
536
|
+
return await this.publicClient.readContract({
|
|
537
|
+
address: this.diamondAddress,
|
|
538
|
+
abi: DiamondABI,
|
|
539
|
+
functionName: "getPendingYield",
|
|
540
|
+
});
|
|
541
|
+
} catch (error) {
|
|
542
|
+
if (this.isZeroSupplyError(error)) {
|
|
543
|
+
return 0n;
|
|
544
|
+
}
|
|
545
|
+
throw error;
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// ============ Yield View Functions ============
|
|
550
|
+
|
|
551
|
+
async getTotalYieldGenerated(): Promise<bigint> {
|
|
552
|
+
try {
|
|
553
|
+
return await this.publicClient.readContract({
|
|
554
|
+
address: this.diamondAddress,
|
|
555
|
+
abi: DiamondABI,
|
|
556
|
+
functionName: "getTotalYieldGenerated",
|
|
557
|
+
});
|
|
558
|
+
} catch (error) {
|
|
559
|
+
if (this.isZeroSupplyError(error)) {
|
|
560
|
+
return 0n;
|
|
561
|
+
}
|
|
562
|
+
throw error;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
async getLSTYieldInfo(lstToken: Address): Promise<LSTYieldInfo> {
|
|
567
|
+
try {
|
|
568
|
+
const result = await this.publicClient.readContract({
|
|
569
|
+
address: this.diamondAddress,
|
|
570
|
+
abi: DiamondABI,
|
|
571
|
+
functionName: "getLSTYieldInfo",
|
|
572
|
+
args: [lstToken],
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
return {
|
|
576
|
+
lastExchangeRate: result[0],
|
|
577
|
+
lastUpdateTimestamp: result[1],
|
|
578
|
+
};
|
|
579
|
+
} catch (error) {
|
|
580
|
+
if (this.isZeroSupplyError(error)) {
|
|
581
|
+
return {
|
|
582
|
+
lastExchangeRate: 0n,
|
|
583
|
+
lastUpdateTimestamp: 0n,
|
|
584
|
+
};
|
|
585
|
+
}
|
|
586
|
+
throw error;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
async getMinHarvestInterval(): Promise<bigint> {
|
|
591
|
+
try {
|
|
592
|
+
return await this.publicClient.readContract({
|
|
593
|
+
address: this.diamondAddress,
|
|
594
|
+
abi: DiamondABI,
|
|
595
|
+
functionName: "getMinHarvestInterval",
|
|
596
|
+
});
|
|
597
|
+
} catch (error) {
|
|
598
|
+
if (this.isZeroSupplyError(error)) {
|
|
599
|
+
return 0n;
|
|
600
|
+
}
|
|
601
|
+
throw error;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
async getLastHarvestTimestamp(): Promise<bigint> {
|
|
606
|
+
try {
|
|
607
|
+
return await this.publicClient.readContract({
|
|
608
|
+
address: this.diamondAddress,
|
|
609
|
+
abi: DiamondABI,
|
|
610
|
+
functionName: "getLastHarvestTimestamp",
|
|
611
|
+
});
|
|
612
|
+
} catch (error) {
|
|
613
|
+
if (this.isZeroSupplyError(error)) {
|
|
614
|
+
return 0n;
|
|
615
|
+
}
|
|
616
|
+
throw error;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
async previewHarvest(): Promise<bigint> {
|
|
621
|
+
try {
|
|
622
|
+
return await this.publicClient.readContract({
|
|
623
|
+
address: this.diamondAddress,
|
|
624
|
+
abi: DiamondABI,
|
|
625
|
+
functionName: "previewHarvest",
|
|
626
|
+
});
|
|
627
|
+
} catch (error) {
|
|
628
|
+
// No yield to harvest when no collateral exists
|
|
629
|
+
if (this.isZeroSupplyError(error)) {
|
|
630
|
+
return 0n;
|
|
631
|
+
}
|
|
632
|
+
throw error;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
// ============ Config View Functions ============
|
|
637
|
+
|
|
638
|
+
async getTokens(): Promise<TokenAddresses> {
|
|
639
|
+
const [tokens, sApUSD] = await Promise.all([
|
|
640
|
+
this.publicClient.readContract({
|
|
641
|
+
address: this.diamondAddress,
|
|
642
|
+
abi: DiamondABI,
|
|
643
|
+
functionName: "getTokens",
|
|
644
|
+
}),
|
|
645
|
+
this.getSApUSD(),
|
|
646
|
+
]);
|
|
647
|
+
|
|
648
|
+
return {
|
|
649
|
+
apUSD: tokens[0],
|
|
650
|
+
xBNB: tokens[1],
|
|
651
|
+
sApUSD,
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
async getSApUSD(): Promise<Address> {
|
|
656
|
+
return this.publicClient.readContract({
|
|
657
|
+
address: this.diamondAddress,
|
|
658
|
+
abi: DiamondABI,
|
|
659
|
+
functionName: "getSApUSD",
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
async getStabilityPool(): Promise<Address> {
|
|
664
|
+
return this.publicClient.readContract({
|
|
665
|
+
address: this.diamondAddress,
|
|
666
|
+
abi: DiamondABI,
|
|
667
|
+
functionName: "getStabilityPool",
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
async getTreasury(): Promise<Address> {
|
|
672
|
+
return this.publicClient.readContract({
|
|
673
|
+
address: this.diamondAddress,
|
|
674
|
+
abi: DiamondABI,
|
|
675
|
+
functionName: "getTreasury",
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
async getFeeTierCount(): Promise<bigint> {
|
|
680
|
+
return this.publicClient.readContract({
|
|
681
|
+
address: this.diamondAddress,
|
|
682
|
+
abi: DiamondABI,
|
|
683
|
+
functionName: "getFeeTierCount",
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
async getFeeTier(index: bigint): Promise<FeeTier> {
|
|
688
|
+
const result = await this.publicClient.readContract({
|
|
689
|
+
address: this.diamondAddress,
|
|
690
|
+
abi: DiamondABI,
|
|
691
|
+
functionName: "getFeeTier",
|
|
692
|
+
args: [index],
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
return {
|
|
696
|
+
minCR: result[0],
|
|
697
|
+
apUSDMintFee: result[1],
|
|
698
|
+
apUSDRedeemFee: result[2],
|
|
699
|
+
xBNBMintFee: result[3],
|
|
700
|
+
xBNBRedeemFee: result[4],
|
|
701
|
+
apUSDMintDisabled: result[5],
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
async getCurrentFeeTier(): Promise<CurrentFeeTier> {
|
|
706
|
+
try {
|
|
707
|
+
const result = await this.publicClient.readContract({
|
|
708
|
+
address: this.diamondAddress,
|
|
709
|
+
abi: DiamondABI,
|
|
710
|
+
functionName: "getCurrentFeeTier",
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
return {
|
|
714
|
+
minCR: result[0],
|
|
715
|
+
apUSDMintFee: result[1],
|
|
716
|
+
apUSDRedeemFee: result[2],
|
|
717
|
+
xBNBMintFee: result[3],
|
|
718
|
+
xBNBRedeemFee: result[4],
|
|
719
|
+
apUSDMintDisabled: result[5],
|
|
720
|
+
currentCR: result[6],
|
|
721
|
+
};
|
|
722
|
+
} catch (error) {
|
|
723
|
+
// Return default fee tier when protocol is empty
|
|
724
|
+
if (this.isZeroSupplyError(error)) {
|
|
725
|
+
return {
|
|
726
|
+
minCR: 0n,
|
|
727
|
+
apUSDMintFee: 0,
|
|
728
|
+
apUSDRedeemFee: 0,
|
|
729
|
+
xBNBMintFee: 0,
|
|
730
|
+
xBNBRedeemFee: 0,
|
|
731
|
+
apUSDMintDisabled: false,
|
|
732
|
+
currentCR: 0n,
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
throw error;
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
async getMaxPriceAge(): Promise<bigint> {
|
|
740
|
+
return this.publicClient.readContract({
|
|
741
|
+
address: this.diamondAddress,
|
|
742
|
+
abi: DiamondABI,
|
|
743
|
+
functionName: "getMaxPriceAge",
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
async getMinDepositPeriod(): Promise<bigint> {
|
|
748
|
+
return this.publicClient.readContract({
|
|
749
|
+
address: this.diamondAddress,
|
|
750
|
+
abi: DiamondABI,
|
|
751
|
+
functionName: "getMinDepositPeriod",
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
async isPaused(): Promise<boolean> {
|
|
756
|
+
return this.publicClient.readContract({
|
|
757
|
+
address: this.diamondAddress,
|
|
758
|
+
abi: DiamondABI,
|
|
759
|
+
functionName: "isPaused",
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
// ============ Stability Mode View Functions ============
|
|
764
|
+
|
|
765
|
+
async getStabilityMode(): Promise<StabilityModeInfo> {
|
|
766
|
+
try {
|
|
767
|
+
const result = await this.publicClient.readContract({
|
|
768
|
+
address: this.diamondAddress,
|
|
769
|
+
abi: DiamondABI,
|
|
770
|
+
functionName: "getStabilityMode",
|
|
771
|
+
});
|
|
772
|
+
|
|
773
|
+
return {
|
|
774
|
+
mode: result[0],
|
|
775
|
+
currentCR: result[1],
|
|
776
|
+
};
|
|
777
|
+
} catch (error) {
|
|
778
|
+
// Return normal mode when protocol is empty
|
|
779
|
+
if (this.isZeroSupplyError(error)) {
|
|
780
|
+
return {
|
|
781
|
+
mode: 0, // Normal mode
|
|
782
|
+
currentCR: 0n,
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
throw error;
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
async canTriggerStabilityMode2(): Promise<StabilityMode2Info> {
|
|
790
|
+
try {
|
|
791
|
+
const result = await this.publicClient.readContract({
|
|
792
|
+
address: this.diamondAddress,
|
|
793
|
+
abi: DiamondABI,
|
|
794
|
+
functionName: "canTriggerStabilityMode2",
|
|
795
|
+
});
|
|
796
|
+
|
|
797
|
+
return {
|
|
798
|
+
canTrigger: result[0],
|
|
799
|
+
currentCR: result[1],
|
|
800
|
+
potentialConversion: result[2],
|
|
801
|
+
};
|
|
802
|
+
} catch (error) {
|
|
803
|
+
// Cannot trigger when protocol is empty
|
|
804
|
+
if (this.isZeroSupplyError(error)) {
|
|
805
|
+
return {
|
|
806
|
+
canTrigger: false,
|
|
807
|
+
currentCR: 0n,
|
|
808
|
+
potentialConversion: 0n,
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
throw error;
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
// ============ Ownership View Functions ============
|
|
816
|
+
|
|
817
|
+
async getOwner(): Promise<Address> {
|
|
818
|
+
return this.publicClient.readContract({
|
|
819
|
+
address: this.diamondAddress,
|
|
820
|
+
abi: DiamondABI,
|
|
821
|
+
functionName: "owner",
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
// ============ Write Client ============
|
|
827
|
+
|
|
828
|
+
/**
|
|
829
|
+
* Full client with write capabilities for interacting with Aspan Protocol
|
|
830
|
+
*/
|
|
831
|
+
export class AspanClient extends AspanReadClient {
|
|
832
|
+
private readonly walletClient: WalletClient<Transport, Chain, Account>;
|
|
833
|
+
|
|
834
|
+
constructor(config: AspanWriteClientConfig) {
|
|
835
|
+
super(config);
|
|
836
|
+
|
|
837
|
+
this.walletClient = createWalletClient({
|
|
838
|
+
account: config.account,
|
|
839
|
+
chain: this.chain,
|
|
840
|
+
transport: http(config.rpcUrl),
|
|
841
|
+
});
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
// ============ Pool Write Functions ============
|
|
845
|
+
|
|
846
|
+
/**
|
|
847
|
+
* Mint apUSD by depositing LST
|
|
848
|
+
* @param params Mint parameters
|
|
849
|
+
* @returns Transaction hash
|
|
850
|
+
*/
|
|
851
|
+
async mintApUSD(params: MintApUSDParams): Promise<Hash> {
|
|
852
|
+
return this.walletClient.writeContract({
|
|
853
|
+
address: this.diamondAddress,
|
|
854
|
+
abi: DiamondABI,
|
|
855
|
+
functionName: "mintApUSD",
|
|
856
|
+
args: [params.lstToken, params.lstAmount],
|
|
857
|
+
});
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
/**
|
|
861
|
+
* Redeem apUSD for LST
|
|
862
|
+
* @param params Redeem parameters
|
|
863
|
+
* @returns Transaction hash
|
|
864
|
+
*/
|
|
865
|
+
async redeemApUSD(params: RedeemApUSDParams): Promise<Hash> {
|
|
866
|
+
return this.walletClient.writeContract({
|
|
867
|
+
address: this.diamondAddress,
|
|
868
|
+
abi: DiamondABI,
|
|
869
|
+
functionName: "redeemApUSD",
|
|
870
|
+
args: [params.lstToken, params.apUSDAmount],
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
/**
|
|
875
|
+
* Mint xBNB by depositing LST
|
|
876
|
+
* @param params Mint parameters
|
|
877
|
+
* @returns Transaction hash
|
|
878
|
+
*/
|
|
879
|
+
async mintXBNB(params: MintXBNBParams): Promise<Hash> {
|
|
880
|
+
return this.walletClient.writeContract({
|
|
881
|
+
address: this.diamondAddress,
|
|
882
|
+
abi: DiamondABI,
|
|
883
|
+
functionName: "mintXBNB",
|
|
884
|
+
args: [params.lstToken, params.lstAmount],
|
|
885
|
+
});
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
/**
|
|
889
|
+
* Redeem xBNB for LST
|
|
890
|
+
* @param params Redeem parameters
|
|
891
|
+
* @returns Transaction hash
|
|
892
|
+
*/
|
|
893
|
+
async redeemXBNB(params: RedeemXBNBParams): Promise<Hash> {
|
|
894
|
+
return this.walletClient.writeContract({
|
|
895
|
+
address: this.diamondAddress,
|
|
896
|
+
abi: DiamondABI,
|
|
897
|
+
functionName: "redeemXBNB",
|
|
898
|
+
args: [params.lstToken, params.xBNBAmount],
|
|
899
|
+
});
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
// ============ Stability Pool Write Functions ============
|
|
903
|
+
|
|
904
|
+
/**
|
|
905
|
+
* Deposit apUSD to stability pool to earn yield
|
|
906
|
+
* @param params Deposit parameters
|
|
907
|
+
* @returns Transaction hash
|
|
908
|
+
*/
|
|
909
|
+
async deposit(params: DepositParams): Promise<Hash> {
|
|
910
|
+
return this.walletClient.writeContract({
|
|
911
|
+
address: this.diamondAddress,
|
|
912
|
+
abi: DiamondABI,
|
|
913
|
+
functionName: "deposit",
|
|
914
|
+
args: [params.apUSDAmount],
|
|
915
|
+
});
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
/**
|
|
919
|
+
* Withdraw from stability pool by shares
|
|
920
|
+
* @param params Withdraw parameters
|
|
921
|
+
* @returns Transaction hash
|
|
922
|
+
*/
|
|
923
|
+
async withdraw(params: WithdrawParams): Promise<Hash> {
|
|
924
|
+
return this.walletClient.writeContract({
|
|
925
|
+
address: this.diamondAddress,
|
|
926
|
+
abi: DiamondABI,
|
|
927
|
+
functionName: "withdraw",
|
|
928
|
+
args: [params.shares],
|
|
929
|
+
});
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
/**
|
|
933
|
+
* Withdraw from stability pool by asset amount
|
|
934
|
+
* @param params Withdraw parameters
|
|
935
|
+
* @returns Transaction hash
|
|
936
|
+
*/
|
|
937
|
+
async withdrawAssets(params: WithdrawAssetsParams): Promise<Hash> {
|
|
938
|
+
return this.walletClient.writeContract({
|
|
939
|
+
address: this.diamondAddress,
|
|
940
|
+
abi: DiamondABI,
|
|
941
|
+
functionName: "withdrawAssets",
|
|
942
|
+
args: [params.assets],
|
|
943
|
+
});
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
/**
|
|
947
|
+
* Harvest yield from LSTs
|
|
948
|
+
* @returns Transaction hash
|
|
949
|
+
*/
|
|
950
|
+
async harvestYield(): Promise<Hash> {
|
|
951
|
+
return this.walletClient.writeContract({
|
|
952
|
+
address: this.diamondAddress,
|
|
953
|
+
abi: DiamondABI,
|
|
954
|
+
functionName: "harvestYield",
|
|
955
|
+
});
|
|
956
|
+
}
|
|
957
|
+
|
|
958
|
+
// ============ Transaction Helpers ============
|
|
959
|
+
|
|
960
|
+
/**
|
|
961
|
+
* Wait for transaction confirmation
|
|
962
|
+
* @param hash Transaction hash
|
|
963
|
+
* @returns Transaction receipt
|
|
964
|
+
*/
|
|
965
|
+
async waitForTransaction(hash: Hash) {
|
|
966
|
+
return this.publicClient.waitForTransactionReceipt({ hash });
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
// ============ Factory Functions ============
|
|
971
|
+
|
|
972
|
+
/**
|
|
973
|
+
* Create a read-only client for BSC mainnet
|
|
974
|
+
*/
|
|
975
|
+
export function createAspanReadClient(
|
|
976
|
+
diamondAddress: Address,
|
|
977
|
+
rpcUrl?: string
|
|
978
|
+
): AspanReadClient {
|
|
979
|
+
return new AspanReadClient({
|
|
980
|
+
diamondAddress,
|
|
981
|
+
chain: bsc,
|
|
982
|
+
rpcUrl,
|
|
983
|
+
});
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
/**
|
|
987
|
+
* Create a full client for BSC mainnet
|
|
988
|
+
*/
|
|
989
|
+
export function createAspanClient(
|
|
990
|
+
diamondAddress: Address,
|
|
991
|
+
account: Account,
|
|
992
|
+
rpcUrl?: string
|
|
993
|
+
): AspanClient {
|
|
994
|
+
return new AspanClient({
|
|
995
|
+
diamondAddress,
|
|
996
|
+
account,
|
|
997
|
+
chain: bsc,
|
|
998
|
+
rpcUrl,
|
|
999
|
+
});
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
/**
|
|
1003
|
+
* Create a read-only client for BSC testnet
|
|
1004
|
+
*/
|
|
1005
|
+
export function createAspanTestnetReadClient(
|
|
1006
|
+
diamondAddress: Address,
|
|
1007
|
+
rpcUrl?: string
|
|
1008
|
+
): AspanReadClient {
|
|
1009
|
+
return new AspanReadClient({
|
|
1010
|
+
diamondAddress,
|
|
1011
|
+
chain: bscTestnet,
|
|
1012
|
+
rpcUrl,
|
|
1013
|
+
});
|
|
1014
|
+
}
|
|
1015
|
+
|
|
1016
|
+
/**
|
|
1017
|
+
* Create a full client for BSC testnet
|
|
1018
|
+
*/
|
|
1019
|
+
export function createAspanTestnetClient(
|
|
1020
|
+
diamondAddress: Address,
|
|
1021
|
+
account: Account,
|
|
1022
|
+
rpcUrl?: string
|
|
1023
|
+
): AspanClient {
|
|
1024
|
+
return new AspanClient({
|
|
1025
|
+
diamondAddress,
|
|
1026
|
+
account,
|
|
1027
|
+
chain: bscTestnet,
|
|
1028
|
+
rpcUrl,
|
|
1029
|
+
});
|
|
1030
|
+
}
|