@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/cli.js +636 -0
- package/dist/cli.mjs +613 -0
- package/dist/index.browser.global.js +33487 -0
- package/dist/index.d.ts +360 -0
- package/dist/index.js +1003 -0
- package/dist/index.mjs +948 -0
- package/package.json +58 -0
- package/src/cli.ts +160 -0
- package/src/data/pragma.abi.json +96 -0
- package/src/data/tokens.json +72 -0
- package/src/dataTypes/address.ts +38 -0
- package/src/dataTypes/bignumber.ts +53 -0
- package/src/dataTypes/index.ts +2 -0
- package/src/global.ts +73 -0
- package/src/index.browser.ts +6 -0
- package/src/index.ts +9 -0
- package/src/interfaces/common.ts +33 -0
- package/src/interfaces/index.ts +3 -0
- package/src/interfaces/initializable.ts +21 -0
- package/src/interfaces/lending.ts +75 -0
- package/src/modules/index.ts +3 -0
- package/src/modules/pragma.ts +22 -0
- package/src/modules/pricer.ts +125 -0
- package/src/modules/zkLend.ts +162 -0
- package/src/node/index.ts +1 -0
- package/src/node/pricer-redis.ts +71 -0
- package/src/notifs/index.ts +1 -0
- package/src/notifs/telegram.ts +48 -0
- package/src/strategies/autoCompounderStrk.ts +71 -0
- package/src/strategies/index.ts +1 -0
- package/src/utils/encrypt.ts +68 -0
- package/src/utils/index.ts +12 -0
- package/src/utils/store.ts +173 -0
package/dist/cli.mjs
ADDED
|
@@ -0,0 +1,613 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
import inquirer from "inquirer";
|
|
6
|
+
|
|
7
|
+
// src/utils/store.ts
|
|
8
|
+
import fs, { readFileSync, writeFileSync } from "fs";
|
|
9
|
+
import { Account } from "starknet";
|
|
10
|
+
import * as crypto2 from "crypto";
|
|
11
|
+
|
|
12
|
+
// src/utils/encrypt.ts
|
|
13
|
+
import * as crypto from "crypto";
|
|
14
|
+
var PasswordJsonCryptoUtil = class {
|
|
15
|
+
constructor() {
|
|
16
|
+
this.algorithm = "aes-256-gcm";
|
|
17
|
+
this.keyLength = 32;
|
|
18
|
+
// 256 bits
|
|
19
|
+
this.saltLength = 16;
|
|
20
|
+
// 128 bits
|
|
21
|
+
this.ivLength = 12;
|
|
22
|
+
// 96 bits for GCM
|
|
23
|
+
this.tagLength = 16;
|
|
24
|
+
// 128 bits
|
|
25
|
+
this.pbkdf2Iterations = 1e5;
|
|
26
|
+
}
|
|
27
|
+
// Number of iterations for PBKDF2
|
|
28
|
+
deriveKey(password, salt) {
|
|
29
|
+
return crypto.pbkdf2Sync(password, salt, this.pbkdf2Iterations, this.keyLength, "sha256");
|
|
30
|
+
}
|
|
31
|
+
encrypt(data, password) {
|
|
32
|
+
const jsonString = JSON.stringify(data);
|
|
33
|
+
const salt = crypto.randomBytes(this.saltLength);
|
|
34
|
+
const iv = crypto.randomBytes(this.ivLength);
|
|
35
|
+
const key = this.deriveKey(password, salt);
|
|
36
|
+
const cipher = crypto.createCipheriv(this.algorithm, key, iv, { authTagLength: this.tagLength });
|
|
37
|
+
let encrypted = cipher.update(jsonString, "utf8", "hex");
|
|
38
|
+
encrypted += cipher.final("hex");
|
|
39
|
+
const tag = cipher.getAuthTag();
|
|
40
|
+
return Buffer.concat([salt, iv, tag, Buffer.from(encrypted, "hex")]).toString("base64");
|
|
41
|
+
}
|
|
42
|
+
decrypt(encryptedData, password) {
|
|
43
|
+
const data = Buffer.from(encryptedData, "base64");
|
|
44
|
+
const salt = data.subarray(0, this.saltLength);
|
|
45
|
+
const iv = data.subarray(this.saltLength, this.saltLength + this.ivLength);
|
|
46
|
+
const tag = data.subarray(this.saltLength + this.ivLength, this.saltLength + this.ivLength + this.tagLength);
|
|
47
|
+
const encrypted = data.subarray(this.saltLength + this.ivLength + this.tagLength);
|
|
48
|
+
const key = this.deriveKey(password, salt);
|
|
49
|
+
const decipher = crypto.createDecipheriv(this.algorithm, key, iv, { authTagLength: this.tagLength });
|
|
50
|
+
decipher.setAuthTag(tag);
|
|
51
|
+
try {
|
|
52
|
+
let decrypted = decipher.update(encrypted.toString("hex"), "hex", "utf8");
|
|
53
|
+
decrypted += decipher.final("utf8");
|
|
54
|
+
return JSON.parse(decrypted);
|
|
55
|
+
} catch (error) {
|
|
56
|
+
throw new Error("Decryption failed. This could be due to an incorrect password or corrupted data.");
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// src/modules/pricer.ts
|
|
62
|
+
import axios from "axios";
|
|
63
|
+
|
|
64
|
+
// src/data/tokens.json
|
|
65
|
+
var tokens_default = [
|
|
66
|
+
{
|
|
67
|
+
name: "Ether",
|
|
68
|
+
symbol: "ETH",
|
|
69
|
+
address: "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7",
|
|
70
|
+
decimals: 18,
|
|
71
|
+
pricerKey: "ETH-USDT"
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: "USD Coin",
|
|
75
|
+
symbol: "USDC",
|
|
76
|
+
address: "0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8",
|
|
77
|
+
decimals: 6,
|
|
78
|
+
pricerKey: "USDC-USDT"
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: "Wrapped BTC",
|
|
82
|
+
symbol: "WBTC",
|
|
83
|
+
address: "0x03fe2b97c1fd336e750087d68b9b867997fd64a2661ff3ca5a7c771641e8e7ac",
|
|
84
|
+
decimals: 8,
|
|
85
|
+
pricerKey: "WBTC-USDT"
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
name: "Tether USD",
|
|
89
|
+
symbol: "USDT",
|
|
90
|
+
address: "0x068f5c6a61780768455de69077e07e89787839bf8166decfbf92b645209c0fb8",
|
|
91
|
+
decimals: 6,
|
|
92
|
+
pricerKey: "USDT-USDT"
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
name: "Dai Stablecoin",
|
|
96
|
+
symbol: "DAIv0",
|
|
97
|
+
address: "",
|
|
98
|
+
decimals: 18,
|
|
99
|
+
pricerKey: "DAI-USDT"
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: "Starknet Wrapped Staked Ether",
|
|
103
|
+
symbol: "wstETH",
|
|
104
|
+
address: "0x042b8f0484674ca266ac5d08e4ac6a3fe65bd3129795def2dca5c34ecc5f96d2",
|
|
105
|
+
decimals: 18,
|
|
106
|
+
pricerKey: "wstETH-USDT"
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
name: "Starknet Token",
|
|
110
|
+
symbol: "STRK",
|
|
111
|
+
address: "0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d",
|
|
112
|
+
decimals: 18,
|
|
113
|
+
pricerKey: "STRK-USDT"
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
name: "zkLend Token",
|
|
117
|
+
symbol: "ZEND",
|
|
118
|
+
address: "",
|
|
119
|
+
decimals: 18,
|
|
120
|
+
pricerKey: "ZEND-USDT"
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
name: "Dai Stablecoin",
|
|
124
|
+
symbol: "DAI",
|
|
125
|
+
address: "",
|
|
126
|
+
decimals: 18,
|
|
127
|
+
pricerKey: "DAI-USDT"
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
name: "Ekubo Protocol",
|
|
131
|
+
symbol: "EKUBO",
|
|
132
|
+
address: "",
|
|
133
|
+
decimals: 18,
|
|
134
|
+
pricerKey: "DAI-USDT"
|
|
135
|
+
}
|
|
136
|
+
];
|
|
137
|
+
|
|
138
|
+
// src/global.ts
|
|
139
|
+
var logger = {
|
|
140
|
+
...console,
|
|
141
|
+
verbose(message) {
|
|
142
|
+
console.log(`[VERBOSE] ${message}`);
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
var FatalError = class extends Error {
|
|
146
|
+
constructor(message, err) {
|
|
147
|
+
super(message);
|
|
148
|
+
logger.error(message);
|
|
149
|
+
if (err)
|
|
150
|
+
logger.error(err.message);
|
|
151
|
+
this.name = "FatalError";
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
var Global = class {
|
|
155
|
+
static fatalError(message, err) {
|
|
156
|
+
logger.error(message);
|
|
157
|
+
console.error(message, err);
|
|
158
|
+
if (err)
|
|
159
|
+
console.error(err);
|
|
160
|
+
process.exit(1);
|
|
161
|
+
}
|
|
162
|
+
static httpError(url, err, message) {
|
|
163
|
+
logger.error(`${url}: ${message}`);
|
|
164
|
+
console.error(err);
|
|
165
|
+
}
|
|
166
|
+
static async getTokens() {
|
|
167
|
+
return tokens_default;
|
|
168
|
+
}
|
|
169
|
+
static assert(condition, message) {
|
|
170
|
+
if (!condition) {
|
|
171
|
+
throw new FatalError(message);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
// src/modules/pragma.ts
|
|
177
|
+
import { Contract } from "starknet";
|
|
178
|
+
|
|
179
|
+
// src/modules/zkLend.ts
|
|
180
|
+
import axios2 from "axios";
|
|
181
|
+
|
|
182
|
+
// src/dataTypes/bignumber.ts
|
|
183
|
+
import BigNumber from "bignumber.js";
|
|
184
|
+
var Web3Number = class _Web3Number extends BigNumber {
|
|
185
|
+
constructor(value, decimals) {
|
|
186
|
+
super(value);
|
|
187
|
+
this.decimals = decimals;
|
|
188
|
+
}
|
|
189
|
+
static fromWei(weiNumber, decimals) {
|
|
190
|
+
const bn = new _Web3Number(weiNumber, decimals).dividedBy(10 ** decimals);
|
|
191
|
+
return new _Web3Number(bn.toString(), decimals);
|
|
192
|
+
}
|
|
193
|
+
toWei() {
|
|
194
|
+
return this.mul(10 ** this.decimals).toFixed(0);
|
|
195
|
+
}
|
|
196
|
+
multipliedBy(value) {
|
|
197
|
+
return new _Web3Number(this.mul(value).toString(), this.decimals);
|
|
198
|
+
}
|
|
199
|
+
dividedBy(value) {
|
|
200
|
+
return new _Web3Number(this.div(value).toString(), this.decimals);
|
|
201
|
+
}
|
|
202
|
+
plus(value) {
|
|
203
|
+
return new _Web3Number(this.add(value).toString(), this.decimals);
|
|
204
|
+
}
|
|
205
|
+
minus(n, base) {
|
|
206
|
+
return new _Web3Number(super.minus(n, base).toString(), this.decimals);
|
|
207
|
+
}
|
|
208
|
+
toString(base) {
|
|
209
|
+
return super.toString(base);
|
|
210
|
+
}
|
|
211
|
+
// [customInspectSymbol](depth: any, inspectOptions: any, inspect: any) {
|
|
212
|
+
// return this.toString();
|
|
213
|
+
// }
|
|
214
|
+
};
|
|
215
|
+
BigNumber.config({ DECIMAL_PLACES: 18 });
|
|
216
|
+
Web3Number.config({ DECIMAL_PLACES: 18 });
|
|
217
|
+
|
|
218
|
+
// src/interfaces/lending.ts
|
|
219
|
+
var ILending = class {
|
|
220
|
+
constructor(config, metadata) {
|
|
221
|
+
this.tokens = [];
|
|
222
|
+
this.initialised = false;
|
|
223
|
+
this.metadata = metadata;
|
|
224
|
+
this.config = config;
|
|
225
|
+
this.init();
|
|
226
|
+
}
|
|
227
|
+
/** Wait for initialisation */
|
|
228
|
+
waitForInitilisation() {
|
|
229
|
+
return new Promise((resolve, reject) => {
|
|
230
|
+
const interval = setInterval(() => {
|
|
231
|
+
logger.verbose(`Waiting for ${this.metadata.name} to initialise`);
|
|
232
|
+
if (this.initialised) {
|
|
233
|
+
logger.verbose(`${this.metadata.name} initialised`);
|
|
234
|
+
clearInterval(interval);
|
|
235
|
+
resolve();
|
|
236
|
+
}
|
|
237
|
+
}, 1e3);
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
// src/modules/zkLend.ts
|
|
243
|
+
var _ZkLend = class _ZkLend extends ILending {
|
|
244
|
+
constructor(config, pricer) {
|
|
245
|
+
super(config, {
|
|
246
|
+
name: "zkLend",
|
|
247
|
+
logo: "https://app.zklend.com/favicon.ico"
|
|
248
|
+
});
|
|
249
|
+
this.POSITION_URL = "https://app.zklend.com/api/users/{{USER_ADDR}}/all";
|
|
250
|
+
this.pricer = pricer;
|
|
251
|
+
}
|
|
252
|
+
async init() {
|
|
253
|
+
try {
|
|
254
|
+
logger.verbose(`Initialising ${this.metadata.name}`);
|
|
255
|
+
const result = await axios2.get(_ZkLend.POOLS_URL);
|
|
256
|
+
const data = result.data;
|
|
257
|
+
const savedTokens = await Global.getTokens();
|
|
258
|
+
data.forEach((pool) => {
|
|
259
|
+
let collareralFactor = new Web3Number(0, 0);
|
|
260
|
+
if (pool.collateral_factor) {
|
|
261
|
+
collareralFactor = Web3Number.fromWei(pool.collateral_factor.value, pool.collateral_factor.decimals);
|
|
262
|
+
}
|
|
263
|
+
const savedTokenInfo = savedTokens.find((t) => t.symbol == pool.token.symbol);
|
|
264
|
+
const token = {
|
|
265
|
+
name: pool.token.name,
|
|
266
|
+
symbol: pool.token.symbol,
|
|
267
|
+
address: savedTokenInfo?.address || "",
|
|
268
|
+
decimals: pool.token.decimals,
|
|
269
|
+
borrowFactor: Web3Number.fromWei(pool.borrow_factor.value, pool.borrow_factor.decimals),
|
|
270
|
+
collareralFactor
|
|
271
|
+
};
|
|
272
|
+
this.tokens.push(token);
|
|
273
|
+
});
|
|
274
|
+
logger.info(`Initialised ${this.metadata.name} with ${this.tokens.length} tokens`);
|
|
275
|
+
this.initialised = true;
|
|
276
|
+
} catch (error) {
|
|
277
|
+
return Global.httpError(_ZkLend.POOLS_URL, error);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* @description Get the health factor of the user for given lending and debt tokens
|
|
282
|
+
* @param lending_tokens
|
|
283
|
+
* @param debt_tokens
|
|
284
|
+
* @param user
|
|
285
|
+
* @returns hf (e.g. returns 1.5 for 150% health factor)
|
|
286
|
+
*/
|
|
287
|
+
async get_health_factor_tokenwise(lending_tokens, debt_tokens, user) {
|
|
288
|
+
const positions = await this.getPositions(user);
|
|
289
|
+
logger.verbose(`${this.metadata.name}:: Positions: ${JSON.stringify(positions)}`);
|
|
290
|
+
let effectiveDebt = new Web3Number(0, 6);
|
|
291
|
+
positions.filter((pos) => {
|
|
292
|
+
return debt_tokens.find((t) => t.symbol === pos.tokenSymbol);
|
|
293
|
+
}).forEach((pos) => {
|
|
294
|
+
const token = this.tokens.find((t) => t.symbol === pos.tokenSymbol);
|
|
295
|
+
if (!token) {
|
|
296
|
+
throw new FatalError(`Token ${pos.tokenName} not found in ${this.metadata.name}`);
|
|
297
|
+
}
|
|
298
|
+
effectiveDebt = effectiveDebt.plus(pos.debtUSD.dividedBy(token.borrowFactor.toFixed(6)).toString());
|
|
299
|
+
});
|
|
300
|
+
logger.verbose(`${this.metadata.name}:: Effective debt: ${effectiveDebt}`);
|
|
301
|
+
if (effectiveDebt.isZero()) {
|
|
302
|
+
return Infinity;
|
|
303
|
+
}
|
|
304
|
+
let effectiveCollateral = new Web3Number(0, 6);
|
|
305
|
+
positions.filter((pos) => {
|
|
306
|
+
const exp1 = lending_tokens.find((t) => t.symbol === pos.tokenSymbol);
|
|
307
|
+
const exp2 = pos.marginType === "shared" /* SHARED */;
|
|
308
|
+
return exp1 && exp2;
|
|
309
|
+
}).forEach((pos) => {
|
|
310
|
+
const token = this.tokens.find((t) => t.symbol === pos.tokenSymbol);
|
|
311
|
+
if (!token) {
|
|
312
|
+
throw new FatalError(`Token ${pos.tokenName} not found in ${this.metadata.name}`);
|
|
313
|
+
}
|
|
314
|
+
logger.verbose(`${this.metadata.name}:: Token: ${pos.tokenName}, Collateral factor: ${token.collareralFactor.toFixed(6)}`);
|
|
315
|
+
effectiveCollateral = effectiveCollateral.plus(pos.supplyUSD.multipliedBy(token.collareralFactor.toFixed(6)).toString());
|
|
316
|
+
});
|
|
317
|
+
logger.verbose(`${this.metadata.name}:: Effective collateral: ${effectiveCollateral}`);
|
|
318
|
+
const healthFactor = effectiveCollateral.dividedBy(effectiveDebt.toFixed(6)).toNumber();
|
|
319
|
+
logger.verbose(`${this.metadata.name}:: Health factor: ${healthFactor}`);
|
|
320
|
+
return healthFactor;
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* @description Get the health factor of the user
|
|
324
|
+
* - Considers all tokens for collateral and debt
|
|
325
|
+
*/
|
|
326
|
+
async get_health_factor(user) {
|
|
327
|
+
return this.get_health_factor_tokenwise(this.tokens, this.tokens, user);
|
|
328
|
+
}
|
|
329
|
+
async getPositionsSummary(user) {
|
|
330
|
+
const pos = await this.getPositions(user);
|
|
331
|
+
const collateralUSD = pos.reduce((acc, p) => acc + p.supplyUSD.toNumber(), 0);
|
|
332
|
+
const debtUSD = pos.reduce((acc, p) => acc + p.debtUSD.toNumber(), 0);
|
|
333
|
+
return {
|
|
334
|
+
collateralUSD,
|
|
335
|
+
debtUSD
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* @description Get the token-wise collateral and debt positions of the user
|
|
340
|
+
* @param user Contract address of the user
|
|
341
|
+
* @returns Promise<ILendingPosition[]>
|
|
342
|
+
*/
|
|
343
|
+
async getPositions(user) {
|
|
344
|
+
const url = this.POSITION_URL.replace("{{USER_ADDR}}", user.address);
|
|
345
|
+
const result = await axios2.get(url);
|
|
346
|
+
const data = result.data;
|
|
347
|
+
const lendingPosition = [];
|
|
348
|
+
logger.verbose(`${this.metadata.name}:: Positions: ${JSON.stringify(data)}`);
|
|
349
|
+
for (let i = 0; i < data.pools.length; i++) {
|
|
350
|
+
const pool = data.pools[i];
|
|
351
|
+
const token = this.tokens.find((t) => {
|
|
352
|
+
return t.symbol === pool.token_symbol;
|
|
353
|
+
});
|
|
354
|
+
if (!token) {
|
|
355
|
+
throw new FatalError(`Token ${pool.token_symbol} not found in ${this.metadata.name}`);
|
|
356
|
+
}
|
|
357
|
+
const debtAmount = Web3Number.fromWei(pool.data.debt_amount, token.decimals);
|
|
358
|
+
const supplyAmount = Web3Number.fromWei(pool.data.supply_amount, token.decimals);
|
|
359
|
+
const price = (await this.pricer.getPrice(token.symbol)).price;
|
|
360
|
+
lendingPosition.push({
|
|
361
|
+
tokenName: token.name,
|
|
362
|
+
tokenSymbol: token.symbol,
|
|
363
|
+
marginType: pool.data.is_collateral ? "shared" /* SHARED */ : "none" /* NONE */,
|
|
364
|
+
debtAmount,
|
|
365
|
+
debtUSD: debtAmount.multipliedBy(price.toFixed(6)),
|
|
366
|
+
supplyAmount,
|
|
367
|
+
supplyUSD: supplyAmount.multipliedBy(price.toFixed(6))
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
;
|
|
371
|
+
return lendingPosition;
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
_ZkLend.POOLS_URL = "https://app.zklend.com/api/pools";
|
|
375
|
+
var ZkLend = _ZkLend;
|
|
376
|
+
|
|
377
|
+
// src/interfaces/common.ts
|
|
378
|
+
import { RpcProvider as RpcProvider2 } from "starknet";
|
|
379
|
+
|
|
380
|
+
// src/dataTypes/address.ts
|
|
381
|
+
import { num } from "starknet";
|
|
382
|
+
|
|
383
|
+
// src/strategies/autoCompounderStrk.ts
|
|
384
|
+
import { Contract as Contract2, uint256 } from "starknet";
|
|
385
|
+
|
|
386
|
+
// src/notifs/telegram.ts
|
|
387
|
+
import TelegramBot from "node-telegram-bot-api";
|
|
388
|
+
|
|
389
|
+
// src/node/pricer-redis.ts
|
|
390
|
+
import { createClient } from "redis";
|
|
391
|
+
|
|
392
|
+
// src/utils/store.ts
|
|
393
|
+
function getDefaultStoreConfig(network) {
|
|
394
|
+
if (!process.env.HOME) {
|
|
395
|
+
throw new Error("StoreConfig: HOME environment variable not found");
|
|
396
|
+
}
|
|
397
|
+
return {
|
|
398
|
+
SECRET_FILE_FOLDER: `${process.env.HOME}/.starknet-store`,
|
|
399
|
+
NETWORK: network,
|
|
400
|
+
ACCOUNTS_FILE_NAME: "accounts.json",
|
|
401
|
+
PASSWORD: crypto2.randomBytes(16).toString("hex")
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
var Store = class _Store {
|
|
405
|
+
constructor(config, storeConfig) {
|
|
406
|
+
this.encryptor = new PasswordJsonCryptoUtil();
|
|
407
|
+
this.config = config;
|
|
408
|
+
const defaultStoreConfig = getDefaultStoreConfig(config.network);
|
|
409
|
+
if (!storeConfig.PASSWORD) {
|
|
410
|
+
_Store.logPassword(defaultStoreConfig.PASSWORD);
|
|
411
|
+
}
|
|
412
|
+
this.storeConfig = {
|
|
413
|
+
...defaultStoreConfig,
|
|
414
|
+
...storeConfig
|
|
415
|
+
};
|
|
416
|
+
_Store.ensureFolder(this.storeConfig.SECRET_FILE_FOLDER);
|
|
417
|
+
}
|
|
418
|
+
static logPassword(password) {
|
|
419
|
+
logger.warn(`\u26A0\uFE0F=========================================\u26A0\uFE0F`);
|
|
420
|
+
logger.warn(`Generated a random password for store`);
|
|
421
|
+
logger.warn(`\u26A0\uFE0F Password: ${password}`);
|
|
422
|
+
logger.warn(`This not stored anywhere, please you backup this password for future use`);
|
|
423
|
+
logger.warn(`\u26A0\uFE0F=========================================\u26A0\uFE0F`);
|
|
424
|
+
}
|
|
425
|
+
getAccount(accountKey) {
|
|
426
|
+
const accounts = this.loadAccounts();
|
|
427
|
+
logger.verbose(`nAccounts loaded for network: ${Object.keys(accounts).length}`);
|
|
428
|
+
const data = accounts[accountKey];
|
|
429
|
+
if (!data) {
|
|
430
|
+
throw new Error(`Account not found: ${accountKey}`);
|
|
431
|
+
}
|
|
432
|
+
logger.verbose(`Account loaded: ${accountKey} from network: ${this.config.network}`);
|
|
433
|
+
logger.verbose(`Address: ${data.address}`);
|
|
434
|
+
return new Account(this.config.provider, data.address, data.pk);
|
|
435
|
+
}
|
|
436
|
+
addAccount(accountKey, address, pk) {
|
|
437
|
+
const allAccounts = this.getAllAccounts();
|
|
438
|
+
if (!allAccounts[this.config.network]) {
|
|
439
|
+
allAccounts[this.config.network] = {};
|
|
440
|
+
}
|
|
441
|
+
allAccounts[this.config.network][accountKey] = {
|
|
442
|
+
address,
|
|
443
|
+
pk
|
|
444
|
+
};
|
|
445
|
+
const encryptedData = this.encryptor.encrypt(allAccounts, this.storeConfig.PASSWORD);
|
|
446
|
+
writeFileSync(this.getAccountFilePath(), encryptedData);
|
|
447
|
+
logger.verbose(`Account added: ${accountKey} to network: ${this.config.network}`);
|
|
448
|
+
}
|
|
449
|
+
getAccountFilePath() {
|
|
450
|
+
const path = `${this.storeConfig.SECRET_FILE_FOLDER}/${this.storeConfig.ACCOUNTS_FILE_NAME}`;
|
|
451
|
+
logger.verbose(`Path: ${path}`);
|
|
452
|
+
return path;
|
|
453
|
+
}
|
|
454
|
+
getAllAccounts() {
|
|
455
|
+
const PATH = this.getAccountFilePath();
|
|
456
|
+
if (!fs.existsSync(PATH)) {
|
|
457
|
+
logger.verbose(`Accounts: files doesnt exist`);
|
|
458
|
+
return {};
|
|
459
|
+
}
|
|
460
|
+
let encryptedData = readFileSync(PATH, {
|
|
461
|
+
encoding: "utf-8"
|
|
462
|
+
});
|
|
463
|
+
let data = this.encryptor.decrypt(encryptedData, this.storeConfig.PASSWORD);
|
|
464
|
+
return data;
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* @description Load all accounts of the network
|
|
468
|
+
* @returns NetworkAccounts
|
|
469
|
+
*/
|
|
470
|
+
loadAccounts() {
|
|
471
|
+
const allData = this.getAllAccounts();
|
|
472
|
+
logger.verbose(`Accounts loaded for network: ${this.config.network}`);
|
|
473
|
+
if (!allData[this.config.network]) {
|
|
474
|
+
allData[this.config.network] = {};
|
|
475
|
+
}
|
|
476
|
+
return allData[this.config.network];
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* @description List all accountKeys of the network
|
|
480
|
+
* @returns string[]
|
|
481
|
+
*/
|
|
482
|
+
listAccounts() {
|
|
483
|
+
return Object.keys(this.loadAccounts());
|
|
484
|
+
}
|
|
485
|
+
static ensureFolder(folder) {
|
|
486
|
+
if (!fs.existsSync(folder)) {
|
|
487
|
+
fs.mkdirSync(folder, { recursive: true });
|
|
488
|
+
}
|
|
489
|
+
if (!fs.existsSync(`${folder}`)) {
|
|
490
|
+
throw new Error(`Store folder not found: ${folder}`);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
};
|
|
494
|
+
|
|
495
|
+
// src/cli.ts
|
|
496
|
+
import chalk from "chalk";
|
|
497
|
+
import { RpcProvider as RpcProvider3 } from "starknet";
|
|
498
|
+
var program = new Command();
|
|
499
|
+
var getConfig = (network) => {
|
|
500
|
+
return {
|
|
501
|
+
provider: new RpcProvider3({
|
|
502
|
+
nodeUrl: "https://starknet-mainnet.public.blastapi.io"
|
|
503
|
+
}),
|
|
504
|
+
network,
|
|
505
|
+
stage: "production"
|
|
506
|
+
};
|
|
507
|
+
};
|
|
508
|
+
async function createStore() {
|
|
509
|
+
console.log(chalk.blue.bold("Welcome to the Account Secure project for Starknet!"));
|
|
510
|
+
const networkAnswers = await inquirer.prompt([
|
|
511
|
+
{
|
|
512
|
+
type: "list",
|
|
513
|
+
name: "network",
|
|
514
|
+
message: chalk.yellow("What is the network?"),
|
|
515
|
+
choices: ["mainnet", "sepolia", "devnet"]
|
|
516
|
+
}
|
|
517
|
+
]);
|
|
518
|
+
const network = networkAnswers.network;
|
|
519
|
+
const defaultStoreConfig = getDefaultStoreConfig(network);
|
|
520
|
+
const storeConfigAnswers = await inquirer.prompt([
|
|
521
|
+
{
|
|
522
|
+
type: "input",
|
|
523
|
+
name: "secrets_folder",
|
|
524
|
+
message: chalk.yellow(`What is your secrets folder? (${defaultStoreConfig.SECRET_FILE_FOLDER})`),
|
|
525
|
+
default: defaultStoreConfig.SECRET_FILE_FOLDER,
|
|
526
|
+
validate: (input) => true
|
|
527
|
+
},
|
|
528
|
+
{
|
|
529
|
+
type: "input",
|
|
530
|
+
name: "accounts_file",
|
|
531
|
+
message: chalk.yellow(`What is your accounts file? (${defaultStoreConfig.ACCOUNTS_FILE_NAME})`),
|
|
532
|
+
default: defaultStoreConfig.ACCOUNTS_FILE_NAME,
|
|
533
|
+
validate: (input) => true
|
|
534
|
+
},
|
|
535
|
+
{
|
|
536
|
+
type: "input",
|
|
537
|
+
name: "encryption_password",
|
|
538
|
+
message: chalk.yellow(`What is your decryption password? (To generate one, press enter)`),
|
|
539
|
+
default: defaultStoreConfig.PASSWORD,
|
|
540
|
+
validate: (input) => true
|
|
541
|
+
}
|
|
542
|
+
]);
|
|
543
|
+
const config = getConfig(network);
|
|
544
|
+
const secrets_folder = storeConfigAnswers.secrets_folder;
|
|
545
|
+
const accounts_file = storeConfigAnswers.accounts_file;
|
|
546
|
+
const encryption_password = storeConfigAnswers.encryption_password;
|
|
547
|
+
const store = new Store(config, {
|
|
548
|
+
SECRET_FILE_FOLDER: secrets_folder,
|
|
549
|
+
ACCOUNTS_FILE_NAME: accounts_file,
|
|
550
|
+
PASSWORD: storeConfigAnswers.encryption_password,
|
|
551
|
+
NETWORK: network
|
|
552
|
+
});
|
|
553
|
+
if (defaultStoreConfig.PASSWORD === encryption_password) {
|
|
554
|
+
Store.logPassword(encryption_password);
|
|
555
|
+
}
|
|
556
|
+
return store;
|
|
557
|
+
}
|
|
558
|
+
program.version("1.0.0").description("Manage accounts securely on your disk with encryption");
|
|
559
|
+
program.description("Add accounts securely to your disk with encryption").command("add-account").action(async (options) => {
|
|
560
|
+
const store = await createStore();
|
|
561
|
+
const existingAccountKeys = store.listAccounts();
|
|
562
|
+
const accountAnswers = await inquirer.prompt([
|
|
563
|
+
{
|
|
564
|
+
type: "input",
|
|
565
|
+
name: "account_key",
|
|
566
|
+
message: chalk.yellow(`Provide a unique account key`),
|
|
567
|
+
validate: (input) => input.length > 0 && !existingAccountKeys.includes(input) || "Please enter a unique account key"
|
|
568
|
+
},
|
|
569
|
+
{
|
|
570
|
+
type: "input",
|
|
571
|
+
name: "address",
|
|
572
|
+
message: chalk.yellow(`What is your account address?`),
|
|
573
|
+
validate: (input) => input.length > 0 || "Please enter a valid address"
|
|
574
|
+
},
|
|
575
|
+
{
|
|
576
|
+
type: "input",
|
|
577
|
+
name: "pk",
|
|
578
|
+
message: chalk.yellow(`What is your account private key?`),
|
|
579
|
+
validate: (input) => input.length > 0 || "Please enter a valid pk"
|
|
580
|
+
}
|
|
581
|
+
]);
|
|
582
|
+
const address = accountAnswers.address;
|
|
583
|
+
const pk = accountAnswers.pk;
|
|
584
|
+
const account_key = accountAnswers.account_key;
|
|
585
|
+
store.addAccount(account_key, address, pk);
|
|
586
|
+
console.log(`${chalk.blue("Account added:")} ${account_key} to network: ${store.config.network}`);
|
|
587
|
+
});
|
|
588
|
+
program.description("List account names of a network").command("list-accounts").action(async (options) => {
|
|
589
|
+
const store = await createStore();
|
|
590
|
+
const accounts = store.listAccounts();
|
|
591
|
+
console.log(`${chalk.blue("Account keys:")} ${accounts.join(", ")}`);
|
|
592
|
+
});
|
|
593
|
+
program.description("List account names of a network").command("get-account").action(async (options) => {
|
|
594
|
+
const store = await createStore();
|
|
595
|
+
const existingAccountKeys = store.listAccounts();
|
|
596
|
+
const accountAnswers = await inquirer.prompt([
|
|
597
|
+
{
|
|
598
|
+
type: "input",
|
|
599
|
+
name: "account_key",
|
|
600
|
+
message: chalk.yellow(`Provide a unique account key`),
|
|
601
|
+
validate: (input) => input.length > 0 && existingAccountKeys.includes(input) || "Please enter a value account key"
|
|
602
|
+
}
|
|
603
|
+
]);
|
|
604
|
+
const account = store.getAccount(accountAnswers.account_key);
|
|
605
|
+
console.log(`${chalk.blue("Account Address:")} ${account.address}`);
|
|
606
|
+
});
|
|
607
|
+
program.action(() => {
|
|
608
|
+
program.help();
|
|
609
|
+
});
|
|
610
|
+
program.parse(process.argv);
|
|
611
|
+
if (!process.argv.slice(2).length) {
|
|
612
|
+
program.outputHelp();
|
|
613
|
+
}
|