@strkfarm/sdk 1.0.6

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.mjs ADDED
@@ -0,0 +1,948 @@
1
+ // src/modules/pricer.ts
2
+ import axios from "axios";
3
+
4
+ // src/data/tokens.json
5
+ var tokens_default = [
6
+ {
7
+ name: "Ether",
8
+ symbol: "ETH",
9
+ address: "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7",
10
+ decimals: 18,
11
+ pricerKey: "ETH-USDT"
12
+ },
13
+ {
14
+ name: "USD Coin",
15
+ symbol: "USDC",
16
+ address: "0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8",
17
+ decimals: 6,
18
+ pricerKey: "USDC-USDT"
19
+ },
20
+ {
21
+ name: "Wrapped BTC",
22
+ symbol: "WBTC",
23
+ address: "0x03fe2b97c1fd336e750087d68b9b867997fd64a2661ff3ca5a7c771641e8e7ac",
24
+ decimals: 8,
25
+ pricerKey: "WBTC-USDT"
26
+ },
27
+ {
28
+ name: "Tether USD",
29
+ symbol: "USDT",
30
+ address: "0x068f5c6a61780768455de69077e07e89787839bf8166decfbf92b645209c0fb8",
31
+ decimals: 6,
32
+ pricerKey: "USDT-USDT"
33
+ },
34
+ {
35
+ name: "Dai Stablecoin",
36
+ symbol: "DAIv0",
37
+ address: "",
38
+ decimals: 18,
39
+ pricerKey: "DAI-USDT"
40
+ },
41
+ {
42
+ name: "Starknet Wrapped Staked Ether",
43
+ symbol: "wstETH",
44
+ address: "0x042b8f0484674ca266ac5d08e4ac6a3fe65bd3129795def2dca5c34ecc5f96d2",
45
+ decimals: 18,
46
+ pricerKey: "wstETH-USDT"
47
+ },
48
+ {
49
+ name: "Starknet Token",
50
+ symbol: "STRK",
51
+ address: "0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d",
52
+ decimals: 18,
53
+ pricerKey: "STRK-USDT"
54
+ },
55
+ {
56
+ name: "zkLend Token",
57
+ symbol: "ZEND",
58
+ address: "",
59
+ decimals: 18,
60
+ pricerKey: "ZEND-USDT"
61
+ },
62
+ {
63
+ name: "Dai Stablecoin",
64
+ symbol: "DAI",
65
+ address: "",
66
+ decimals: 18,
67
+ pricerKey: "DAI-USDT"
68
+ },
69
+ {
70
+ name: "Ekubo Protocol",
71
+ symbol: "EKUBO",
72
+ address: "",
73
+ decimals: 18,
74
+ pricerKey: "DAI-USDT"
75
+ }
76
+ ];
77
+
78
+ // src/global.ts
79
+ var logger = {
80
+ ...console,
81
+ verbose(message) {
82
+ console.log(`[VERBOSE] ${message}`);
83
+ }
84
+ };
85
+ var FatalError = class extends Error {
86
+ constructor(message, err) {
87
+ super(message);
88
+ logger.error(message);
89
+ if (err)
90
+ logger.error(err.message);
91
+ this.name = "FatalError";
92
+ }
93
+ };
94
+ var Global = class {
95
+ static fatalError(message, err) {
96
+ logger.error(message);
97
+ console.error(message, err);
98
+ if (err)
99
+ console.error(err);
100
+ process.exit(1);
101
+ }
102
+ static httpError(url, err, message) {
103
+ logger.error(`${url}: ${message}`);
104
+ console.error(err);
105
+ }
106
+ static async getTokens() {
107
+ return tokens_default;
108
+ }
109
+ static assert(condition, message) {
110
+ if (!condition) {
111
+ throw new FatalError(message);
112
+ }
113
+ }
114
+ };
115
+
116
+ // src/modules/pricer.ts
117
+ var Pricer = class {
118
+ constructor(config, tokens) {
119
+ this.tokens = [];
120
+ this.prices = {};
121
+ /**
122
+ * TOKENA and TOKENB are the two token names to get price of TokenA in terms of TokenB
123
+ */
124
+ this.PRICE_API = `https://api.coinbase.com/v2/prices/{{PRICER_KEY}}/buy`;
125
+ this.config = config;
126
+ this.tokens = tokens;
127
+ }
128
+ isReady() {
129
+ const allPricesExist = Object.keys(this.prices).length === this.tokens.length;
130
+ if (!allPricesExist) return false;
131
+ let atleastOneStale = false;
132
+ for (let token of this.tokens) {
133
+ const priceInfo = this.prices[token.symbol];
134
+ const isStale = this.isStale(priceInfo.timestamp, token.symbol);
135
+ if (isStale) {
136
+ atleastOneStale = true;
137
+ logger.warn(`Atleast one stale: ${token.symbol}: ${JSON.stringify(this.prices[token.symbol])}`);
138
+ break;
139
+ }
140
+ }
141
+ return allPricesExist && !atleastOneStale;
142
+ }
143
+ waitTillReady() {
144
+ return new Promise((resolve, reject) => {
145
+ const interval = setInterval(() => {
146
+ logger.verbose(`Waiting for pricer to initialise`);
147
+ if (this.isReady()) {
148
+ logger.verbose(`Pricer initialised`);
149
+ clearInterval(interval);
150
+ resolve();
151
+ }
152
+ }, 1e3);
153
+ });
154
+ }
155
+ start() {
156
+ this._loadPrices();
157
+ setInterval(() => {
158
+ this._loadPrices();
159
+ }, 3e4);
160
+ }
161
+ isStale(timestamp, tokenName) {
162
+ const STALE_TIME = 6e4;
163
+ return (/* @__PURE__ */ new Date()).getTime() - timestamp.getTime() > STALE_TIME;
164
+ }
165
+ assertNotStale(timestamp, tokenName) {
166
+ Global.assert(!this.isStale(timestamp, tokenName), `Price of ${tokenName} is stale`);
167
+ }
168
+ async getPrice(tokenName) {
169
+ Global.assert(this.prices[tokenName], `Price of ${tokenName} not found`);
170
+ this.assertNotStale(this.prices[tokenName].timestamp, tokenName);
171
+ return this.prices[tokenName];
172
+ }
173
+ _loadPrices(onUpdate = () => {
174
+ }) {
175
+ this.tokens.forEach(async (token) => {
176
+ const MAX_RETRIES = 10;
177
+ let retry = 0;
178
+ while (retry < MAX_RETRIES) {
179
+ try {
180
+ if (token.symbol === "USDT") {
181
+ this.prices[token.symbol] = {
182
+ price: 1,
183
+ timestamp: /* @__PURE__ */ new Date()
184
+ };
185
+ onUpdate(token.symbol);
186
+ return;
187
+ }
188
+ if (!token.pricerKey) {
189
+ throw new FatalError(`Pricer key not found for ${token.name}`);
190
+ }
191
+ const url = this.PRICE_API.replace("{{PRICER_KEY}}", token.pricerKey);
192
+ const result = await axios.get(url);
193
+ const data = result.data;
194
+ const price = Number(data.data.amount);
195
+ this.prices[token.symbol] = {
196
+ price,
197
+ timestamp: /* @__PURE__ */ new Date()
198
+ };
199
+ onUpdate(token.symbol);
200
+ logger.verbose(`Fetched price of ${token.name} as ${price}`);
201
+ break;
202
+ } catch (error) {
203
+ if (retry < MAX_RETRIES) {
204
+ logger.warn(`Error fetching data from ${token.name}, retry: ${retry}`);
205
+ logger.warn(error);
206
+ retry++;
207
+ await new Promise((resolve) => setTimeout(resolve, retry * 2e3));
208
+ } else {
209
+ throw new FatalError(`Error fetching data from ${token.name}`, error);
210
+ }
211
+ }
212
+ }
213
+ });
214
+ if (this.isReady() && this.config.heartbeatUrl) {
215
+ console.log(`sending beat`);
216
+ axios.get(this.config.heartbeatUrl).catch((err) => {
217
+ console.error("Pricer: Heartbeat err", err);
218
+ });
219
+ }
220
+ }
221
+ };
222
+
223
+ // src/modules/pragma.ts
224
+ import { Contract } from "starknet";
225
+
226
+ // src/data/pragma.abi.json
227
+ var pragma_abi_default = [
228
+ {
229
+ data: [
230
+ {
231
+ name: "previousOwner",
232
+ type: "felt"
233
+ },
234
+ {
235
+ name: "newOwner",
236
+ type: "felt"
237
+ }
238
+ ],
239
+ keys: [],
240
+ name: "OwnershipTransferred",
241
+ type: "event"
242
+ },
243
+ {
244
+ data: [
245
+ {
246
+ name: "token",
247
+ type: "felt"
248
+ },
249
+ {
250
+ name: "source",
251
+ type: "felt"
252
+ }
253
+ ],
254
+ keys: [],
255
+ name: "TokenSourceChanged",
256
+ type: "event"
257
+ },
258
+ {
259
+ name: "constructor",
260
+ type: "constructor",
261
+ inputs: [
262
+ {
263
+ name: "owner",
264
+ type: "felt"
265
+ }
266
+ ],
267
+ outputs: []
268
+ },
269
+ {
270
+ name: "get_price",
271
+ type: "function",
272
+ inputs: [
273
+ {
274
+ name: "token",
275
+ type: "felt"
276
+ }
277
+ ],
278
+ outputs: [
279
+ {
280
+ name: "price",
281
+ type: "felt"
282
+ }
283
+ ],
284
+ stateMutability: "view"
285
+ },
286
+ {
287
+ name: "get_price_with_time",
288
+ type: "function",
289
+ inputs: [
290
+ {
291
+ name: "token",
292
+ type: "felt"
293
+ }
294
+ ],
295
+ outputs: [
296
+ {
297
+ name: "price",
298
+ type: "felt"
299
+ },
300
+ {
301
+ name: "update_time",
302
+ type: "felt"
303
+ }
304
+ ],
305
+ stateMutability: "view"
306
+ },
307
+ {
308
+ name: "set_token_source",
309
+ type: "function",
310
+ inputs: [
311
+ {
312
+ name: "token",
313
+ type: "felt"
314
+ },
315
+ {
316
+ name: "source",
317
+ type: "felt"
318
+ }
319
+ ],
320
+ outputs: []
321
+ }
322
+ ];
323
+
324
+ // src/modules/pragma.ts
325
+ var Pragma = class {
326
+ constructor(provider) {
327
+ this.contractAddr = "0x023fb3afbff2c0e3399f896dcf7400acf1a161941cfb386e34a123f228c62832";
328
+ this.contract = new Contract(pragma_abi_default, this.contractAddr, provider);
329
+ }
330
+ async getPrice(tokenAddr) {
331
+ if (!tokenAddr) {
332
+ throw new Error(`Pragma:getPrice - no token`);
333
+ }
334
+ const result = await this.contract.call("get_price", [tokenAddr]);
335
+ const price = Number(result.price) / 10 ** 8;
336
+ logger.verbose(`Pragma:${tokenAddr}: ${price}`);
337
+ return price;
338
+ }
339
+ };
340
+
341
+ // src/modules/zkLend.ts
342
+ import axios2 from "axios";
343
+
344
+ // src/dataTypes/bignumber.ts
345
+ import BigNumber from "bignumber.js";
346
+ var Web3Number = class _Web3Number extends BigNumber {
347
+ constructor(value, decimals) {
348
+ super(value);
349
+ this.decimals = decimals;
350
+ }
351
+ static fromWei(weiNumber, decimals) {
352
+ const bn = new _Web3Number(weiNumber, decimals).dividedBy(10 ** decimals);
353
+ return new _Web3Number(bn.toString(), decimals);
354
+ }
355
+ toWei() {
356
+ return this.mul(10 ** this.decimals).toFixed(0);
357
+ }
358
+ multipliedBy(value) {
359
+ return new _Web3Number(this.mul(value).toString(), this.decimals);
360
+ }
361
+ dividedBy(value) {
362
+ return new _Web3Number(this.div(value).toString(), this.decimals);
363
+ }
364
+ plus(value) {
365
+ return new _Web3Number(this.add(value).toString(), this.decimals);
366
+ }
367
+ minus(n, base) {
368
+ return new _Web3Number(super.minus(n, base).toString(), this.decimals);
369
+ }
370
+ toString(base) {
371
+ return super.toString(base);
372
+ }
373
+ // [customInspectSymbol](depth: any, inspectOptions: any, inspect: any) {
374
+ // return this.toString();
375
+ // }
376
+ };
377
+ BigNumber.config({ DECIMAL_PLACES: 18 });
378
+ Web3Number.config({ DECIMAL_PLACES: 18 });
379
+
380
+ // src/interfaces/lending.ts
381
+ var MarginType = /* @__PURE__ */ ((MarginType2) => {
382
+ MarginType2["SHARED"] = "shared";
383
+ MarginType2["NONE"] = "none";
384
+ return MarginType2;
385
+ })(MarginType || {});
386
+ var ILending = class {
387
+ constructor(config, metadata) {
388
+ this.tokens = [];
389
+ this.initialised = false;
390
+ this.metadata = metadata;
391
+ this.config = config;
392
+ this.init();
393
+ }
394
+ /** Wait for initialisation */
395
+ waitForInitilisation() {
396
+ return new Promise((resolve, reject) => {
397
+ const interval = setInterval(() => {
398
+ logger.verbose(`Waiting for ${this.metadata.name} to initialise`);
399
+ if (this.initialised) {
400
+ logger.verbose(`${this.metadata.name} initialised`);
401
+ clearInterval(interval);
402
+ resolve();
403
+ }
404
+ }, 1e3);
405
+ });
406
+ }
407
+ };
408
+
409
+ // src/modules/zkLend.ts
410
+ var _ZkLend = class _ZkLend extends ILending {
411
+ constructor(config, pricer) {
412
+ super(config, {
413
+ name: "zkLend",
414
+ logo: "https://app.zklend.com/favicon.ico"
415
+ });
416
+ this.POSITION_URL = "https://app.zklend.com/api/users/{{USER_ADDR}}/all";
417
+ this.pricer = pricer;
418
+ }
419
+ async init() {
420
+ try {
421
+ logger.verbose(`Initialising ${this.metadata.name}`);
422
+ const result = await axios2.get(_ZkLend.POOLS_URL);
423
+ const data = result.data;
424
+ const savedTokens = await Global.getTokens();
425
+ data.forEach((pool) => {
426
+ let collareralFactor = new Web3Number(0, 0);
427
+ if (pool.collateral_factor) {
428
+ collareralFactor = Web3Number.fromWei(pool.collateral_factor.value, pool.collateral_factor.decimals);
429
+ }
430
+ const savedTokenInfo = savedTokens.find((t) => t.symbol == pool.token.symbol);
431
+ const token = {
432
+ name: pool.token.name,
433
+ symbol: pool.token.symbol,
434
+ address: savedTokenInfo?.address || "",
435
+ decimals: pool.token.decimals,
436
+ borrowFactor: Web3Number.fromWei(pool.borrow_factor.value, pool.borrow_factor.decimals),
437
+ collareralFactor
438
+ };
439
+ this.tokens.push(token);
440
+ });
441
+ logger.info(`Initialised ${this.metadata.name} with ${this.tokens.length} tokens`);
442
+ this.initialised = true;
443
+ } catch (error) {
444
+ return Global.httpError(_ZkLend.POOLS_URL, error);
445
+ }
446
+ }
447
+ /**
448
+ * @description Get the health factor of the user for given lending and debt tokens
449
+ * @param lending_tokens
450
+ * @param debt_tokens
451
+ * @param user
452
+ * @returns hf (e.g. returns 1.5 for 150% health factor)
453
+ */
454
+ async get_health_factor_tokenwise(lending_tokens, debt_tokens, user) {
455
+ const positions = await this.getPositions(user);
456
+ logger.verbose(`${this.metadata.name}:: Positions: ${JSON.stringify(positions)}`);
457
+ let effectiveDebt = new Web3Number(0, 6);
458
+ positions.filter((pos) => {
459
+ return debt_tokens.find((t) => t.symbol === pos.tokenSymbol);
460
+ }).forEach((pos) => {
461
+ const token = this.tokens.find((t) => t.symbol === pos.tokenSymbol);
462
+ if (!token) {
463
+ throw new FatalError(`Token ${pos.tokenName} not found in ${this.metadata.name}`);
464
+ }
465
+ effectiveDebt = effectiveDebt.plus(pos.debtUSD.dividedBy(token.borrowFactor.toFixed(6)).toString());
466
+ });
467
+ logger.verbose(`${this.metadata.name}:: Effective debt: ${effectiveDebt}`);
468
+ if (effectiveDebt.isZero()) {
469
+ return Infinity;
470
+ }
471
+ let effectiveCollateral = new Web3Number(0, 6);
472
+ positions.filter((pos) => {
473
+ const exp1 = lending_tokens.find((t) => t.symbol === pos.tokenSymbol);
474
+ const exp2 = pos.marginType === "shared" /* SHARED */;
475
+ return exp1 && exp2;
476
+ }).forEach((pos) => {
477
+ const token = this.tokens.find((t) => t.symbol === pos.tokenSymbol);
478
+ if (!token) {
479
+ throw new FatalError(`Token ${pos.tokenName} not found in ${this.metadata.name}`);
480
+ }
481
+ logger.verbose(`${this.metadata.name}:: Token: ${pos.tokenName}, Collateral factor: ${token.collareralFactor.toFixed(6)}`);
482
+ effectiveCollateral = effectiveCollateral.plus(pos.supplyUSD.multipliedBy(token.collareralFactor.toFixed(6)).toString());
483
+ });
484
+ logger.verbose(`${this.metadata.name}:: Effective collateral: ${effectiveCollateral}`);
485
+ const healthFactor = effectiveCollateral.dividedBy(effectiveDebt.toFixed(6)).toNumber();
486
+ logger.verbose(`${this.metadata.name}:: Health factor: ${healthFactor}`);
487
+ return healthFactor;
488
+ }
489
+ /**
490
+ * @description Get the health factor of the user
491
+ * - Considers all tokens for collateral and debt
492
+ */
493
+ async get_health_factor(user) {
494
+ return this.get_health_factor_tokenwise(this.tokens, this.tokens, user);
495
+ }
496
+ async getPositionsSummary(user) {
497
+ const pos = await this.getPositions(user);
498
+ const collateralUSD = pos.reduce((acc, p) => acc + p.supplyUSD.toNumber(), 0);
499
+ const debtUSD = pos.reduce((acc, p) => acc + p.debtUSD.toNumber(), 0);
500
+ return {
501
+ collateralUSD,
502
+ debtUSD
503
+ };
504
+ }
505
+ /**
506
+ * @description Get the token-wise collateral and debt positions of the user
507
+ * @param user Contract address of the user
508
+ * @returns Promise<ILendingPosition[]>
509
+ */
510
+ async getPositions(user) {
511
+ const url = this.POSITION_URL.replace("{{USER_ADDR}}", user.address);
512
+ const result = await axios2.get(url);
513
+ const data = result.data;
514
+ const lendingPosition = [];
515
+ logger.verbose(`${this.metadata.name}:: Positions: ${JSON.stringify(data)}`);
516
+ for (let i = 0; i < data.pools.length; i++) {
517
+ const pool = data.pools[i];
518
+ const token = this.tokens.find((t) => {
519
+ return t.symbol === pool.token_symbol;
520
+ });
521
+ if (!token) {
522
+ throw new FatalError(`Token ${pool.token_symbol} not found in ${this.metadata.name}`);
523
+ }
524
+ const debtAmount = Web3Number.fromWei(pool.data.debt_amount, token.decimals);
525
+ const supplyAmount = Web3Number.fromWei(pool.data.supply_amount, token.decimals);
526
+ const price = (await this.pricer.getPrice(token.symbol)).price;
527
+ lendingPosition.push({
528
+ tokenName: token.name,
529
+ tokenSymbol: token.symbol,
530
+ marginType: pool.data.is_collateral ? "shared" /* SHARED */ : "none" /* NONE */,
531
+ debtAmount,
532
+ debtUSD: debtAmount.multipliedBy(price.toFixed(6)),
533
+ supplyAmount,
534
+ supplyUSD: supplyAmount.multipliedBy(price.toFixed(6))
535
+ });
536
+ }
537
+ ;
538
+ return lendingPosition;
539
+ }
540
+ };
541
+ _ZkLend.POOLS_URL = "https://app.zklend.com/api/pools";
542
+ var ZkLend = _ZkLend;
543
+
544
+ // src/interfaces/common.ts
545
+ import { RpcProvider as RpcProvider2 } from "starknet";
546
+ var Network = /* @__PURE__ */ ((Network2) => {
547
+ Network2["mainnet"] = "mainnet";
548
+ Network2["sepolia"] = "sepolia";
549
+ Network2["devnet"] = "devnet";
550
+ return Network2;
551
+ })(Network || {});
552
+ function getMainnetConfig(rpcUrl = "https://starknet-mainnet.public.blastapi.io", blockIdentifier = "pending") {
553
+ return {
554
+ provider: new RpcProvider2({
555
+ nodeUrl: rpcUrl,
556
+ blockIdentifier
557
+ }),
558
+ stage: "production",
559
+ network: "mainnet" /* mainnet */
560
+ };
561
+ }
562
+
563
+ // src/interfaces/initializable.ts
564
+ var Initializable = class {
565
+ constructor() {
566
+ this.initialized = false;
567
+ }
568
+ async waitForInitilisation() {
569
+ return new Promise((resolve, reject) => {
570
+ const interval = setInterval(() => {
571
+ if (this.initialized) {
572
+ console.log("Initialised");
573
+ clearInterval(interval);
574
+ resolve();
575
+ }
576
+ }, 1e3);
577
+ });
578
+ }
579
+ };
580
+
581
+ // src/dataTypes/address.ts
582
+ import { num } from "starknet";
583
+ var ContractAddr = class _ContractAddr {
584
+ constructor(address) {
585
+ this.address = _ContractAddr.standardise(address);
586
+ }
587
+ static from(address) {
588
+ return new _ContractAddr(address);
589
+ }
590
+ eq(other) {
591
+ return this.address === other.address;
592
+ }
593
+ eqString(other) {
594
+ return this.address === _ContractAddr.standardise(other);
595
+ }
596
+ static standardise(address) {
597
+ let _a = address;
598
+ if (!address) {
599
+ _a = "0";
600
+ }
601
+ const a = num.getHexString(num.getDecimalString(_a.toString()));
602
+ return a;
603
+ }
604
+ static eqString(a, b) {
605
+ return _ContractAddr.standardise(a) === _ContractAddr.standardise(b);
606
+ }
607
+ };
608
+
609
+ // src/strategies/autoCompounderStrk.ts
610
+ import { Contract as Contract2, uint256 } from "starknet";
611
+ var AutoCompounderSTRK = class {
612
+ constructor(config, pricer) {
613
+ this.addr = ContractAddr.from("0x541681b9ad63dff1b35f79c78d8477f64857de29a27902f7298f7b620838ea");
614
+ this.initialized = false;
615
+ this.contract = null;
616
+ this.metadata = {
617
+ decimals: 18,
618
+ underlying: {
619
+ // zSTRK
620
+ address: ContractAddr.from("0x06d8fa671ef84f791b7f601fa79fea8f6ceb70b5fa84189e3159d532162efc21"),
621
+ name: "STRK",
622
+ symbol: "STRK"
623
+ },
624
+ name: "AutoCompounderSTRK"
625
+ };
626
+ this.config = config;
627
+ this.pricer = pricer;
628
+ this.init();
629
+ }
630
+ async init() {
631
+ const cls = await this.config.provider.getClassAt(this.addr.address);
632
+ this.contract = new Contract2(cls.abi, this.addr.address, this.config.provider);
633
+ this.initialized = true;
634
+ }
635
+ async waitForInitilisation() {
636
+ return new Promise((resolve, reject) => {
637
+ const interval = setInterval(() => {
638
+ if (this.initialized) {
639
+ clearInterval(interval);
640
+ resolve();
641
+ }
642
+ }, 1e3);
643
+ });
644
+ }
645
+ /** Returns shares of user */
646
+ async balanceOf(user) {
647
+ const result = await this.contract.balanceOf(user.address);
648
+ return Web3Number.fromWei(result.toString(), this.metadata.decimals);
649
+ }
650
+ /** Returns underlying assets of user */
651
+ async balanceOfUnderlying(user) {
652
+ const balanceShares = await this.balanceOf(user);
653
+ const assets = await this.contract.convert_to_assets(uint256.bnToUint256(balanceShares.toWei()));
654
+ return Web3Number.fromWei(assets.toString(), this.metadata.decimals);
655
+ }
656
+ /** Returns usd value of assets */
657
+ async usdBalanceOfUnderlying(user) {
658
+ const assets = await this.balanceOfUnderlying(user);
659
+ const price = await this.pricer.getPrice(this.metadata.underlying.name);
660
+ const usd = assets.multipliedBy(price.price.toFixed(6));
661
+ return {
662
+ usd,
663
+ assets
664
+ };
665
+ }
666
+ };
667
+
668
+ // src/notifs/telegram.ts
669
+ import TelegramBot from "node-telegram-bot-api";
670
+ var TelegramNotif = class {
671
+ constructor(token, shouldPoll) {
672
+ this.subscribers = [
673
+ // '6820228303',
674
+ "1505578076",
675
+ // '5434736198', // maaza
676
+ "1356705582",
677
+ // langs
678
+ "1388729514",
679
+ // hwashere
680
+ "6020162572",
681
+ //minato
682
+ "985902592"
683
+ ];
684
+ this.bot = new TelegramBot(token, { polling: shouldPoll });
685
+ }
686
+ // listen to start msgs, register chatId and send registered msg
687
+ activateChatBot() {
688
+ this.bot.on("message", (msg) => {
689
+ const chatId = msg.chat.id;
690
+ let text = msg.text.toLowerCase().trim();
691
+ logger.verbose(`Tg: IncomingMsg: ID: ${chatId}, msg: ${text}`);
692
+ if (text == "start") {
693
+ this.bot.sendMessage(chatId, "Registered");
694
+ this.subscribers.push(chatId);
695
+ logger.verbose(`Tg: New subscriber: ${chatId}`);
696
+ } else {
697
+ this.bot.sendMessage(chatId, "Unrecognized command. Supported commands: start");
698
+ }
699
+ });
700
+ }
701
+ // send a given msg to all registered users
702
+ sendMessage(msg) {
703
+ logger.verbose(`Tg: Sending message: ${msg}`);
704
+ for (let chatId of this.subscribers) {
705
+ this.bot.sendMessage(chatId, msg).catch((err) => {
706
+ logger.error(`Tg: Error sending msg to ${chatId}`);
707
+ logger.error(`Tg: Error sending message: ${err.message}`);
708
+ }).then(() => {
709
+ logger.verbose(`Tg: Message sent to ${chatId}`);
710
+ });
711
+ }
712
+ }
713
+ };
714
+
715
+ // src/utils/store.ts
716
+ import fs, { readFileSync, writeFileSync } from "fs";
717
+ import { Account } from "starknet";
718
+ import * as crypto2 from "crypto";
719
+
720
+ // src/utils/encrypt.ts
721
+ import * as crypto from "crypto";
722
+ var PasswordJsonCryptoUtil = class {
723
+ constructor() {
724
+ this.algorithm = "aes-256-gcm";
725
+ this.keyLength = 32;
726
+ // 256 bits
727
+ this.saltLength = 16;
728
+ // 128 bits
729
+ this.ivLength = 12;
730
+ // 96 bits for GCM
731
+ this.tagLength = 16;
732
+ // 128 bits
733
+ this.pbkdf2Iterations = 1e5;
734
+ }
735
+ // Number of iterations for PBKDF2
736
+ deriveKey(password, salt) {
737
+ return crypto.pbkdf2Sync(password, salt, this.pbkdf2Iterations, this.keyLength, "sha256");
738
+ }
739
+ encrypt(data, password) {
740
+ const jsonString = JSON.stringify(data);
741
+ const salt = crypto.randomBytes(this.saltLength);
742
+ const iv = crypto.randomBytes(this.ivLength);
743
+ const key = this.deriveKey(password, salt);
744
+ const cipher = crypto.createCipheriv(this.algorithm, key, iv, { authTagLength: this.tagLength });
745
+ let encrypted = cipher.update(jsonString, "utf8", "hex");
746
+ encrypted += cipher.final("hex");
747
+ const tag = cipher.getAuthTag();
748
+ return Buffer.concat([salt, iv, tag, Buffer.from(encrypted, "hex")]).toString("base64");
749
+ }
750
+ decrypt(encryptedData, password) {
751
+ const data = Buffer.from(encryptedData, "base64");
752
+ const salt = data.subarray(0, this.saltLength);
753
+ const iv = data.subarray(this.saltLength, this.saltLength + this.ivLength);
754
+ const tag = data.subarray(this.saltLength + this.ivLength, this.saltLength + this.ivLength + this.tagLength);
755
+ const encrypted = data.subarray(this.saltLength + this.ivLength + this.tagLength);
756
+ const key = this.deriveKey(password, salt);
757
+ const decipher = crypto.createDecipheriv(this.algorithm, key, iv, { authTagLength: this.tagLength });
758
+ decipher.setAuthTag(tag);
759
+ try {
760
+ let decrypted = decipher.update(encrypted.toString("hex"), "hex", "utf8");
761
+ decrypted += decipher.final("utf8");
762
+ return JSON.parse(decrypted);
763
+ } catch (error) {
764
+ throw new Error("Decryption failed. This could be due to an incorrect password or corrupted data.");
765
+ }
766
+ }
767
+ };
768
+
769
+ // src/utils/store.ts
770
+ function getDefaultStoreConfig(network) {
771
+ if (!process.env.HOME) {
772
+ throw new Error("StoreConfig: HOME environment variable not found");
773
+ }
774
+ return {
775
+ SECRET_FILE_FOLDER: `${process.env.HOME}/.starknet-store`,
776
+ NETWORK: network,
777
+ ACCOUNTS_FILE_NAME: "accounts.json",
778
+ PASSWORD: crypto2.randomBytes(16).toString("hex")
779
+ };
780
+ }
781
+ var Store = class _Store {
782
+ constructor(config, storeConfig) {
783
+ this.encryptor = new PasswordJsonCryptoUtil();
784
+ this.config = config;
785
+ const defaultStoreConfig = getDefaultStoreConfig(config.network);
786
+ if (!storeConfig.PASSWORD) {
787
+ _Store.logPassword(defaultStoreConfig.PASSWORD);
788
+ }
789
+ this.storeConfig = {
790
+ ...defaultStoreConfig,
791
+ ...storeConfig
792
+ };
793
+ _Store.ensureFolder(this.storeConfig.SECRET_FILE_FOLDER);
794
+ }
795
+ static logPassword(password) {
796
+ logger.warn(`\u26A0\uFE0F=========================================\u26A0\uFE0F`);
797
+ logger.warn(`Generated a random password for store`);
798
+ logger.warn(`\u26A0\uFE0F Password: ${password}`);
799
+ logger.warn(`This not stored anywhere, please you backup this password for future use`);
800
+ logger.warn(`\u26A0\uFE0F=========================================\u26A0\uFE0F`);
801
+ }
802
+ getAccount(accountKey) {
803
+ const accounts = this.loadAccounts();
804
+ logger.verbose(`nAccounts loaded for network: ${Object.keys(accounts).length}`);
805
+ const data = accounts[accountKey];
806
+ if (!data) {
807
+ throw new Error(`Account not found: ${accountKey}`);
808
+ }
809
+ logger.verbose(`Account loaded: ${accountKey} from network: ${this.config.network}`);
810
+ logger.verbose(`Address: ${data.address}`);
811
+ return new Account(this.config.provider, data.address, data.pk);
812
+ }
813
+ addAccount(accountKey, address, pk) {
814
+ const allAccounts = this.getAllAccounts();
815
+ if (!allAccounts[this.config.network]) {
816
+ allAccounts[this.config.network] = {};
817
+ }
818
+ allAccounts[this.config.network][accountKey] = {
819
+ address,
820
+ pk
821
+ };
822
+ const encryptedData = this.encryptor.encrypt(allAccounts, this.storeConfig.PASSWORD);
823
+ writeFileSync(this.getAccountFilePath(), encryptedData);
824
+ logger.verbose(`Account added: ${accountKey} to network: ${this.config.network}`);
825
+ }
826
+ getAccountFilePath() {
827
+ const path = `${this.storeConfig.SECRET_FILE_FOLDER}/${this.storeConfig.ACCOUNTS_FILE_NAME}`;
828
+ logger.verbose(`Path: ${path}`);
829
+ return path;
830
+ }
831
+ getAllAccounts() {
832
+ const PATH = this.getAccountFilePath();
833
+ if (!fs.existsSync(PATH)) {
834
+ logger.verbose(`Accounts: files doesnt exist`);
835
+ return {};
836
+ }
837
+ let encryptedData = readFileSync(PATH, {
838
+ encoding: "utf-8"
839
+ });
840
+ let data = this.encryptor.decrypt(encryptedData, this.storeConfig.PASSWORD);
841
+ return data;
842
+ }
843
+ /**
844
+ * @description Load all accounts of the network
845
+ * @returns NetworkAccounts
846
+ */
847
+ loadAccounts() {
848
+ const allData = this.getAllAccounts();
849
+ logger.verbose(`Accounts loaded for network: ${this.config.network}`);
850
+ if (!allData[this.config.network]) {
851
+ allData[this.config.network] = {};
852
+ }
853
+ return allData[this.config.network];
854
+ }
855
+ /**
856
+ * @description List all accountKeys of the network
857
+ * @returns string[]
858
+ */
859
+ listAccounts() {
860
+ return Object.keys(this.loadAccounts());
861
+ }
862
+ static ensureFolder(folder) {
863
+ if (!fs.existsSync(folder)) {
864
+ fs.mkdirSync(folder, { recursive: true });
865
+ }
866
+ if (!fs.existsSync(`${folder}`)) {
867
+ throw new Error(`Store folder not found: ${folder}`);
868
+ }
869
+ }
870
+ };
871
+
872
+ // src/node/pricer-redis.ts
873
+ import { createClient } from "redis";
874
+ var PricerRedis = class extends Pricer {
875
+ constructor(config, tokens) {
876
+ super(config, tokens);
877
+ this.redisClient = null;
878
+ }
879
+ /** Reads prices from Pricer._loadPrices and uses a callback to set prices in redis */
880
+ async startWithRedis(redisUrl) {
881
+ await this.initRedis(redisUrl);
882
+ logger.info(`Starting Pricer with Redis`);
883
+ this._loadPrices(this._setRedisPrices.bind(this));
884
+ setInterval(() => {
885
+ this._loadPrices(this._setRedisPrices.bind(this));
886
+ }, 3e4);
887
+ }
888
+ async close() {
889
+ if (this.redisClient) {
890
+ await this.redisClient.disconnect();
891
+ }
892
+ }
893
+ async initRedis(redisUrl) {
894
+ logger.info(`Initialising Redis Client`);
895
+ this.redisClient = await createClient({
896
+ url: redisUrl
897
+ });
898
+ this.redisClient.on("error", (err) => console.log("Redis Client Error", err)).connect();
899
+ logger.info(`Redis Client Initialised`);
900
+ }
901
+ /** sets current local price in redis */
902
+ _setRedisPrices(tokenSymbol) {
903
+ if (!this.redisClient) {
904
+ throw new FatalError(`Redis client not initialised`);
905
+ }
906
+ this.redisClient.set(`Price:${tokenSymbol}`, JSON.stringify(this.prices[tokenSymbol])).catch((err) => {
907
+ logger.warn(`Error setting price in redis for ${tokenSymbol}`);
908
+ });
909
+ }
910
+ /** Returns price from redis */
911
+ async getPrice(tokenSymbol) {
912
+ const STALE_TIME = 6e4;
913
+ if (!this.redisClient) {
914
+ throw new FatalError(`Redis client not initialised`);
915
+ }
916
+ const data = await this.redisClient.get(`Price:${tokenSymbol}`);
917
+ if (!data) {
918
+ throw new FatalError(`Redis:Price of ${tokenSymbol} not found`);
919
+ }
920
+ logger.verbose(`Redis:Price of ${tokenSymbol} is ${data}`);
921
+ const priceInfo = JSON.parse(data);
922
+ priceInfo.timestamp = new Date(priceInfo.timestamp);
923
+ const isStale = (/* @__PURE__ */ new Date()).getTime() - priceInfo.timestamp.getTime() > STALE_TIME;
924
+ Global.assert(!isStale, `Price of ${tokenSymbol} is stale`);
925
+ return priceInfo;
926
+ }
927
+ };
928
+ export {
929
+ AutoCompounderSTRK,
930
+ ContractAddr,
931
+ FatalError,
932
+ Global,
933
+ ILending,
934
+ Initializable,
935
+ MarginType,
936
+ Network,
937
+ PasswordJsonCryptoUtil,
938
+ Pragma,
939
+ Pricer,
940
+ PricerRedis,
941
+ Store,
942
+ TelegramNotif,
943
+ Web3Number,
944
+ ZkLend,
945
+ getDefaultStoreConfig,
946
+ getMainnetConfig,
947
+ logger
948
+ };