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