@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.
@@ -0,0 +1,75 @@
1
+ import { IConfig } from "@/interfaces/common";
2
+ import { TokenInfo } from "./common";
3
+ import { ContractAddr } from "@/dataTypes/address";
4
+ import { loggers } from "winston";
5
+ import { logger } from "@/global";
6
+ import { log } from "console";
7
+ import { Web3Number } from "@/dataTypes/bignumber";
8
+
9
+ export interface ILendingMetadata {
10
+ name: string;
11
+ logo: string;
12
+ }
13
+
14
+ export enum MarginType {
15
+ SHARED = "shared",
16
+ NONE = "none",
17
+ }
18
+
19
+ export interface ILendingPosition {
20
+ tokenName: string;
21
+ tokenSymbol: string;
22
+ marginType: MarginType,
23
+ debtAmount: Web3Number;
24
+ debtUSD: Web3Number;
25
+ supplyAmount: Web3Number;
26
+ supplyUSD: Web3Number;
27
+ }
28
+
29
+ export interface LendingToken extends TokenInfo {
30
+ borrowFactor: Web3Number;
31
+ collareralFactor: Web3Number;
32
+ }
33
+
34
+ export abstract class ILending {
35
+ readonly config: IConfig;
36
+ readonly metadata: ILendingMetadata;
37
+ readonly tokens: LendingToken[] = [];
38
+
39
+ protected initialised: boolean = false;
40
+ constructor(config:IConfig, metadata: ILendingMetadata) {
41
+ this.metadata = metadata;
42
+ this.config = config;
43
+ this.init();
44
+ }
45
+
46
+ /** Async function to init the class */
47
+ abstract init(): Promise<void>;
48
+
49
+ /** Wait for initialisation */
50
+ waitForInitilisation() {
51
+ return new Promise<void>((resolve, reject) => {
52
+ const interval = setInterval(() => {
53
+ logger.verbose(`Waiting for ${this.metadata.name} to initialise`);
54
+ if (this.initialised) {
55
+ logger.verbose(`${this.metadata.name} initialised`);
56
+ clearInterval(interval);
57
+ resolve();
58
+ }
59
+ }, 1000);
60
+ });
61
+ }
62
+
63
+ /**
64
+ *
65
+ * @param lending_tokens Array of tokens to consider for compute collateral value
66
+ * @param debt_tokens Array of tokens to consider to compute debt values
67
+ * @param user
68
+ */
69
+ abstract get_health_factor_tokenwise(lending_tokens: TokenInfo[], debt_tokens: TokenInfo[], user: ContractAddr): Promise<number>;
70
+ abstract get_health_factor(user: ContractAddr): Promise<number>;
71
+ abstract getPositionsSummary(user: ContractAddr): Promise<{
72
+ collateralUSD: number,
73
+ debtUSD: number,
74
+ }>
75
+ }
@@ -0,0 +1,3 @@
1
+ export * from './pricer';
2
+ export * from './pragma';
3
+ export * from './zkLend';
@@ -0,0 +1,22 @@
1
+ import { Contract, RpcProvider } from "starknet";
2
+ import PragmaAbi from '@/data/pragma.abi.json';
3
+ import { logger } from "@/global";
4
+
5
+ export class Pragma {
6
+ contractAddr = '0x023fb3afbff2c0e3399f896dcf7400acf1a161941cfb386e34a123f228c62832';
7
+ readonly contract: Contract;
8
+
9
+ constructor(provider: RpcProvider) {
10
+ this.contract = new Contract(PragmaAbi, this.contractAddr, provider);
11
+ }
12
+
13
+ async getPrice(tokenAddr: string) {
14
+ if (!tokenAddr) {
15
+ throw new Error(`Pragma:getPrice - no token`)
16
+ }
17
+ const result: any = await this.contract.call('get_price', [tokenAddr]);
18
+ const price = Number(result.price) / 10**8;
19
+ logger.verbose(`Pragma:${tokenAddr}: ${price}`);
20
+ return price;
21
+ }
22
+ }
@@ -0,0 +1,125 @@
1
+ import axios from "axios";
2
+ import { FatalError, Global, logger } from "@/global";
3
+ import { TokenInfo } from "@/interfaces/common";
4
+ import { IConfig } from "@/interfaces/common";
5
+
6
+ export interface PriceInfo {
7
+ price: number,
8
+ timestamp: Date
9
+ }
10
+ export class Pricer {
11
+ readonly config: IConfig;
12
+ readonly tokens: TokenInfo[] = [];
13
+ protected prices: {
14
+ [key: string]: PriceInfo
15
+ } = {}
16
+
17
+ /**
18
+ * TOKENA and TOKENB are the two token names to get price of TokenA in terms of TokenB
19
+ */
20
+ protected PRICE_API = `https://api.coinbase.com/v2/prices/{{PRICER_KEY}}/buy`;
21
+ constructor(config: IConfig, tokens: TokenInfo[]) {
22
+ this.config = config;
23
+ this.tokens = tokens;
24
+ }
25
+
26
+ isReady() {
27
+ const allPricesExist = Object.keys(this.prices).length === this.tokens.length;
28
+ if (!allPricesExist) return false;
29
+
30
+ let atleastOneStale = false;
31
+ for (let token of this.tokens) {
32
+ const priceInfo = this.prices[token.symbol];
33
+ const isStale = this.isStale(priceInfo.timestamp, token.symbol);
34
+ if (isStale) {
35
+ atleastOneStale = true;
36
+ logger.warn(`Atleast one stale: ${token.symbol}: ${JSON.stringify(this.prices[token.symbol])}`);
37
+ break;
38
+ }
39
+ }
40
+ return allPricesExist && !atleastOneStale;
41
+ }
42
+
43
+ waitTillReady() {
44
+ return new Promise<void>((resolve, reject) => {
45
+ const interval = setInterval(() => {
46
+ logger.verbose(`Waiting for pricer to initialise`);
47
+ if (this.isReady()) {
48
+ logger.verbose(`Pricer initialised`);
49
+ clearInterval(interval);
50
+ resolve();
51
+ }
52
+ }, 1000);
53
+ });
54
+ }
55
+
56
+ start() {
57
+ this._loadPrices();
58
+ setInterval(() => {
59
+ this._loadPrices();
60
+ }, 30000);
61
+ }
62
+
63
+ isStale(timestamp: Date, tokenName: string) {
64
+ const STALE_TIME = 60000;
65
+ return (new Date().getTime() - timestamp.getTime()) > STALE_TIME;
66
+ }
67
+
68
+ assertNotStale(timestamp: Date, tokenName: string) {
69
+ Global.assert(!this.isStale(timestamp, tokenName), `Price of ${tokenName} is stale`);
70
+
71
+ }
72
+ async getPrice(tokenName: string) {
73
+ Global.assert(this.prices[tokenName], `Price of ${tokenName} not found`);
74
+ this.assertNotStale(this.prices[tokenName].timestamp, tokenName);
75
+ return this.prices[tokenName];
76
+ }
77
+
78
+ protected _loadPrices(onUpdate: (tokenSymbol: string) => void = () => {}) {
79
+ this.tokens.forEach(async (token) => {
80
+ const MAX_RETRIES = 10;
81
+ let retry = 0;
82
+ while (retry < MAX_RETRIES) {
83
+ try {
84
+ if (token.symbol === 'USDT') {
85
+ this.prices[token.symbol] = {
86
+ price: 1,
87
+ timestamp: new Date()
88
+ }
89
+ onUpdate(token.symbol);
90
+ return;
91
+ }
92
+ if (!token.pricerKey) {
93
+ throw new FatalError(`Pricer key not found for ${token.name}`);
94
+ }
95
+ const url = this.PRICE_API.replace("{{PRICER_KEY}}", token.pricerKey);
96
+ const result = await axios.get(url);
97
+ const data: any = result.data;
98
+ const price = Number(data.data.amount);
99
+ this.prices[token.symbol] = {
100
+ price,
101
+ timestamp: new Date()
102
+ }
103
+ onUpdate(token.symbol);
104
+ logger.verbose(`Fetched price of ${token.name} as ${price}`);
105
+ break;
106
+ } catch (error: any) {
107
+ if (retry < MAX_RETRIES) {
108
+ logger.warn(`Error fetching data from ${token.name}, retry: ${retry}`);
109
+ logger.warn(error);
110
+ retry++;
111
+ await new Promise((resolve) => setTimeout(resolve, retry * 2000));
112
+ } else {
113
+ throw new FatalError(`Error fetching data from ${token.name}`, error);
114
+ }
115
+ }
116
+ }
117
+ })
118
+ if (this.isReady() && this.config.heartbeatUrl) {
119
+ console.log(`sending beat`)
120
+ axios.get(this.config.heartbeatUrl).catch(err => {
121
+ console.error('Pricer: Heartbeat err', err);
122
+ })
123
+ }
124
+ }
125
+ }
@@ -0,0 +1,162 @@
1
+ import axios from "axios";
2
+ import BigNumber from "bignumber.js";
3
+ import { Web3Number } from "@/dataTypes/bignumber";
4
+ import { FatalError, Global, logger } from "@/global";
5
+ import { TokenInfo } from "@/interfaces";
6
+ import { ILending, ILendingPosition, LendingToken, MarginType } from "@/interfaces/lending";
7
+ import { ContractAddr } from "@/dataTypes/address";
8
+ import { IConfig } from "@/interfaces";
9
+ import { Pricer } from "./pricer";
10
+
11
+ export class ZkLend extends ILending implements ILending {
12
+ readonly pricer: Pricer;
13
+ static readonly POOLS_URL = 'https://app.zklend.com/api/pools';
14
+ private POSITION_URL = 'https://app.zklend.com/api/users/{{USER_ADDR}}/all';
15
+
16
+ constructor(config: IConfig, pricer: Pricer) {
17
+ super(config, {
18
+ name: "zkLend",
19
+ logo: 'https://app.zklend.com/favicon.ico'
20
+ });
21
+ this.pricer = pricer;
22
+ }
23
+
24
+ async init() {
25
+ try {
26
+ logger.verbose(`Initialising ${this.metadata.name}`);
27
+ const result = await axios.get(ZkLend.POOLS_URL);
28
+ const data: any[] = result.data;
29
+ const savedTokens = await Global.getTokens()
30
+ data.forEach((pool) => {
31
+ let collareralFactor = new Web3Number(0, 0);
32
+ if (pool.collateral_factor) {
33
+ collareralFactor = Web3Number.fromWei(pool.collateral_factor.value, pool.collateral_factor.decimals);
34
+ }
35
+ const savedTokenInfo = savedTokens.find(t => t.symbol == pool.token.symbol);
36
+ const token: LendingToken = {
37
+ name: pool.token.name,
38
+ symbol: pool.token.symbol,
39
+ address: savedTokenInfo?.address || '',
40
+ decimals: pool.token.decimals,
41
+ borrowFactor: Web3Number.fromWei(pool.borrow_factor.value, pool.borrow_factor.decimals),
42
+ collareralFactor
43
+ }
44
+ this.tokens.push(token);
45
+ });
46
+ logger.info(`Initialised ${this.metadata.name} with ${this.tokens.length} tokens`);
47
+ this.initialised = true;
48
+ } catch (error: any) {
49
+ return Global.httpError(ZkLend.POOLS_URL, error);
50
+ }
51
+ }
52
+
53
+ /**
54
+ * @description Get the health factor of the user for given lending and debt tokens
55
+ * @param lending_tokens
56
+ * @param debt_tokens
57
+ * @param user
58
+ * @returns hf (e.g. returns 1.5 for 150% health factor)
59
+ */
60
+ async get_health_factor_tokenwise(lending_tokens: TokenInfo[], debt_tokens: TokenInfo[], user: ContractAddr): Promise<number> {
61
+ const positions = await this.getPositions(user);
62
+ logger.verbose(`${this.metadata.name}:: Positions: ${JSON.stringify(positions)}`);
63
+
64
+ // Computes Sum of debt USD / borrow factor
65
+ let effectiveDebt = new Web3Number(0, 6);
66
+ positions.filter((pos) => {
67
+ return debt_tokens.find((t) => t.symbol === pos.tokenSymbol);
68
+ }).forEach((pos) => {
69
+ const token = this.tokens.find((t) => t.symbol === pos.tokenSymbol);
70
+ if (!token) {
71
+ throw new FatalError(`Token ${pos.tokenName} not found in ${this.metadata.name}`);
72
+ }
73
+ effectiveDebt = effectiveDebt.plus(pos.debtUSD.dividedBy(token.borrowFactor.toFixed(6)).toString());
74
+ });
75
+ logger.verbose(`${this.metadata.name}:: Effective debt: ${effectiveDebt}`);
76
+ if (effectiveDebt.isZero()) {
77
+ return Infinity;
78
+ }
79
+
80
+ // Computs Sum of collateral USD * collateral factor
81
+ let effectiveCollateral = new Web3Number(0, 6);
82
+ positions.filter(pos => {
83
+ const exp1 = lending_tokens.find((t) => t.symbol === pos.tokenSymbol);
84
+ const exp2 = pos.marginType === MarginType.SHARED;
85
+ return exp1 && exp2;
86
+ }).forEach((pos) => {
87
+ const token = this.tokens.find((t) => t.symbol === pos.tokenSymbol);
88
+ if (!token) {
89
+ throw new FatalError(`Token ${pos.tokenName} not found in ${this.metadata.name}`);
90
+ }
91
+ logger.verbose(`${this.metadata.name}:: Token: ${pos.tokenName}, Collateral factor: ${token.collareralFactor.toFixed(6)}`);
92
+ effectiveCollateral = effectiveCollateral.plus(pos.supplyUSD.multipliedBy(token.collareralFactor.toFixed(6)).toString());
93
+ });
94
+ logger.verbose(`${this.metadata.name}:: Effective collateral: ${effectiveCollateral}`);
95
+
96
+ // Health factor = Effective collateral / Effective debt
97
+ const healthFactor = effectiveCollateral.dividedBy(effectiveDebt.toFixed(6)).toNumber();
98
+ logger.verbose(`${this.metadata.name}:: Health factor: ${healthFactor}`);
99
+ return healthFactor;
100
+ }
101
+
102
+ /**
103
+ * @description Get the health factor of the user
104
+ * - Considers all tokens for collateral and debt
105
+ */
106
+ async get_health_factor(user: ContractAddr): Promise<number> {
107
+ return this.get_health_factor_tokenwise(this.tokens, this.tokens, user);
108
+ }
109
+
110
+ async getPositionsSummary(user: ContractAddr): Promise<{
111
+ collateralUSD: number,
112
+ debtUSD: number,
113
+ }> {
114
+ const pos = await this.getPositions(user);
115
+ const collateralUSD = pos.reduce((acc, p) => acc + p.supplyUSD.toNumber(), 0);
116
+ const debtUSD = pos.reduce((acc, p) => acc + p.debtUSD.toNumber(), 0);
117
+ return {
118
+ collateralUSD,
119
+ debtUSD
120
+ }
121
+ }
122
+ /**
123
+ * @description Get the token-wise collateral and debt positions of the user
124
+ * @param user Contract address of the user
125
+ * @returns Promise<ILendingPosition[]>
126
+ */
127
+ async getPositions(user: ContractAddr): Promise<ILendingPosition[]> {
128
+ const url = this.POSITION_URL.replace('{{USER_ADDR}}', user.address);
129
+ /**
130
+ * Sample response:
131
+ {"pools":[{"data":{"debt_amount":"0x0","is_collateral":false,"supply_amount":"0x0","wallet_balance":"0x0"},
132
+ "token_symbol":"ETH"},{"data":{"debt_amount":"0x0","is_collateral":false,"supply_amount":"0x0",
133
+ "wallet_balance":"0x0"},"token_symbol":"USDC"}]}
134
+ */
135
+ const result = await axios.get(url);
136
+ const data: any = result.data;
137
+ const lendingPosition: ILendingPosition[] = [];
138
+ logger.verbose(`${this.metadata.name}:: Positions: ${JSON.stringify(data)}`);
139
+ for(let i=0; i<data.pools.length; i++) {
140
+ const pool = data.pools[i];
141
+ const token = this.tokens.find((t) => {
142
+ return t.symbol === pool.token_symbol
143
+ });
144
+ if (!token) {
145
+ throw new FatalError(`Token ${pool.token_symbol} not found in ${this.metadata.name}`);
146
+ }
147
+ const debtAmount = Web3Number.fromWei(pool.data.debt_amount, token.decimals);
148
+ const supplyAmount = Web3Number.fromWei(pool.data.supply_amount, token.decimals);
149
+ const price = (await this.pricer.getPrice(token.symbol)).price;
150
+ lendingPosition.push({
151
+ tokenName: token.name,
152
+ tokenSymbol: token.symbol,
153
+ marginType: pool.data.is_collateral ? MarginType.SHARED : MarginType.NONE,
154
+ debtAmount,
155
+ debtUSD: debtAmount.multipliedBy(price.toFixed(6)),
156
+ supplyAmount,
157
+ supplyUSD: supplyAmount.multipliedBy(price.toFixed(6))
158
+ });
159
+ };
160
+ return lendingPosition;
161
+ }
162
+ }
@@ -0,0 +1 @@
1
+ export * from './pricer-redis';
@@ -0,0 +1,71 @@
1
+ import { FatalError, Global, logger } from '@/global';
2
+ import { IConfig, TokenInfo } from '@/interfaces';
3
+ import { PriceInfo, Pricer } from '@/modules/pricer';
4
+ import { createClient } from 'redis';
5
+ import type { RedisClientType } from 'redis'
6
+
7
+ export class PricerRedis extends Pricer {
8
+ private redisClient: RedisClientType | null = null;
9
+ constructor(config: IConfig, tokens: TokenInfo[]) {
10
+ super(config, tokens)
11
+ }
12
+
13
+ /** Reads prices from Pricer._loadPrices and uses a callback to set prices in redis */
14
+ async startWithRedis(redisUrl: string) {
15
+ await this.initRedis(redisUrl);
16
+
17
+ logger.info(`Starting Pricer with Redis`);
18
+ this._loadPrices(this._setRedisPrices.bind(this));
19
+ setInterval(() => {
20
+ this._loadPrices(this._setRedisPrices.bind(this));
21
+ }, 30000);
22
+ }
23
+
24
+ async close() {
25
+ if (this.redisClient) {
26
+ await this.redisClient.disconnect();
27
+ }
28
+ }
29
+
30
+ async initRedis(redisUrl: string) {
31
+ logger.info(`Initialising Redis Client`);
32
+ this.redisClient = <RedisClientType>(await createClient({
33
+ url: redisUrl
34
+ }));
35
+ this.redisClient.on('error', (err: any) => console.log('Redis Client Error', err))
36
+ .connect();
37
+ logger.info(`Redis Client Initialised`);
38
+ }
39
+
40
+ /** sets current local price in redis */
41
+ private _setRedisPrices(tokenSymbol: string) {
42
+ if (!this.redisClient) {
43
+ throw new FatalError(`Redis client not initialised`);
44
+ }
45
+ this.redisClient.set(`Price:${tokenSymbol}`, JSON.stringify(this.prices[tokenSymbol]))
46
+ .catch(err => {
47
+ logger.warn(`Error setting price in redis for ${tokenSymbol}`);
48
+ })
49
+ }
50
+
51
+ /** Returns price from redis */
52
+ async getPrice(tokenSymbol: string) {
53
+ const STALE_TIME = 60000;
54
+ if (!this.redisClient) {
55
+ throw new FatalError(`Redis client not initialised`);
56
+ }
57
+ const data = await this.redisClient.get(`Price:${tokenSymbol}`);
58
+ if (!data) {
59
+ throw new FatalError(`Redis:Price of ${tokenSymbol} not found`);
60
+ }
61
+
62
+ logger.verbose(`Redis:Price of ${tokenSymbol} is ${data}`);
63
+
64
+ const priceInfo: PriceInfo = JSON.parse(data);
65
+ priceInfo.timestamp = new Date(priceInfo.timestamp);
66
+ const isStale = (new Date().getTime() - priceInfo.timestamp.getTime()) > STALE_TIME;
67
+ Global.assert(!isStale, `Price of ${tokenSymbol} is stale`);
68
+ return priceInfo;
69
+
70
+ }
71
+ }
@@ -0,0 +1 @@
1
+ export * from './telegram';
@@ -0,0 +1,48 @@
1
+ import { logger } from "@/global";
2
+ import TelegramBot from "node-telegram-bot-api";
3
+
4
+ export class TelegramNotif {
5
+ private subscribers: string[] = [
6
+ // '6820228303',
7
+ '1505578076',
8
+ // '5434736198', // maaza
9
+ '1356705582', // langs
10
+ '1388729514', // hwashere
11
+ '6020162572', //minato
12
+ '985902592'
13
+ ];
14
+ readonly bot: TelegramBot;
15
+
16
+ constructor(token: string, shouldPoll: boolean) {
17
+ this.bot = new TelegramBot(token, { polling: shouldPoll });
18
+ }
19
+
20
+ // listen to start msgs, register chatId and send registered msg
21
+ activateChatBot() {
22
+ this.bot.on('message', (msg: any) => {
23
+ const chatId = msg.chat.id;
24
+ let text = msg.text.toLowerCase().trim()
25
+ logger.verbose(`Tg: IncomingMsg: ID: ${chatId}, msg: ${text}`)
26
+ if(text=='start') {
27
+ this.bot.sendMessage(chatId, "Registered")
28
+ this.subscribers.push(chatId)
29
+ logger.verbose(`Tg: New subscriber: ${chatId}`);
30
+ } else {
31
+ this.bot.sendMessage(chatId, "Unrecognized command. Supported commands: start");
32
+ }
33
+ });
34
+ }
35
+
36
+ // send a given msg to all registered users
37
+ sendMessage(msg: string) {
38
+ logger.verbose(`Tg: Sending message: ${msg}`);
39
+ for (let chatId of this.subscribers) {
40
+ this.bot.sendMessage(chatId, msg).catch((err: any) => {
41
+ logger.error(`Tg: Error sending msg to ${chatId}`);
42
+ logger.error(`Tg: Error sending message: ${err.message}`);
43
+ }).then(() => {
44
+ logger.verbose(`Tg: Message sent to ${chatId}`);
45
+ })
46
+ }
47
+ }
48
+ }
@@ -0,0 +1,71 @@
1
+ import { ContractAddr, Web3Number } from "@/dataTypes";
2
+ import { IConfig } from "@/interfaces";
3
+ import { Contract, uint256 } from "starknet";
4
+ import { Pricer } from "@/modules/pricer";
5
+
6
+ export class AutoCompounderSTRK {
7
+ readonly config: IConfig;
8
+ readonly addr = ContractAddr.from('0x541681b9ad63dff1b35f79c78d8477f64857de29a27902f7298f7b620838ea');
9
+ readonly pricer: Pricer;
10
+ private initialized: boolean = false;
11
+
12
+ contract: Contract | null = null;
13
+
14
+ readonly metadata = {
15
+ decimals: 18,
16
+ underlying: {
17
+ // zSTRK
18
+ address: ContractAddr.from('0x06d8fa671ef84f791b7f601fa79fea8f6ceb70b5fa84189e3159d532162efc21'),
19
+ name: 'STRK',
20
+ symbol: 'STRK',
21
+ },
22
+ name: 'AutoCompounderSTRK',
23
+ }
24
+
25
+ constructor(config: IConfig, pricer: Pricer) {
26
+ this.config = config;
27
+ this.pricer = pricer;
28
+ this.init();
29
+ }
30
+
31
+ async init() {
32
+ const cls = await this.config.provider.getClassAt(this.addr.address);
33
+ this.contract = new Contract(cls.abi, this.addr.address, this.config.provider);
34
+ this.initialized = true;
35
+ }
36
+
37
+ async waitForInitilisation() {
38
+ return new Promise<void>((resolve, reject) => {
39
+ const interval = setInterval(() => {
40
+ if (this.initialized) {
41
+ clearInterval(interval);
42
+ resolve();
43
+ }
44
+ }, 1000);
45
+ });
46
+ }
47
+
48
+ /** Returns shares of user */
49
+ async balanceOf(user: ContractAddr) {
50
+ const result = await this.contract!.balanceOf(user.address);
51
+ return Web3Number.fromWei(result.toString(), this.metadata.decimals);
52
+ }
53
+
54
+ /** Returns underlying assets of user */
55
+ async balanceOfUnderlying(user: ContractAddr) {
56
+ const balanceShares = await this.balanceOf(user);
57
+ const assets = await this.contract!.convert_to_assets(uint256.bnToUint256(balanceShares.toWei()));
58
+ return Web3Number.fromWei(assets.toString(), this.metadata.decimals);
59
+ }
60
+
61
+ /** Returns usd value of assets */
62
+ async usdBalanceOfUnderlying(user: ContractAddr) {
63
+ const assets = await this.balanceOfUnderlying(user);
64
+ const price = await this.pricer.getPrice(this.metadata.underlying.name);
65
+ const usd = assets.multipliedBy(price.price.toFixed(6))
66
+ return {
67
+ usd,
68
+ assets
69
+ }
70
+ }
71
+ }
@@ -0,0 +1 @@
1
+ export * from './autoCompounderStrk';
@@ -0,0 +1,68 @@
1
+ import * as crypto from 'crypto';
2
+
3
+ export class PasswordJsonCryptoUtil {
4
+ private readonly algorithm = 'aes-256-gcm';
5
+ private readonly keyLength = 32; // 256 bits
6
+ private readonly saltLength = 16; // 128 bits
7
+ private readonly ivLength = 12; // 96 bits for GCM
8
+ private readonly tagLength = 16; // 128 bits
9
+ private readonly pbkdf2Iterations = 100000; // Number of iterations for PBKDF2
10
+
11
+ private deriveKey(password: string, salt: Buffer): Buffer {
12
+ return crypto.pbkdf2Sync(password, salt, this.pbkdf2Iterations, this.keyLength, 'sha256');
13
+ }
14
+
15
+ encrypt(data: any, password: string): string {
16
+ // Convert data to JSON string
17
+ const jsonString = JSON.stringify(data);
18
+
19
+ // Generate a random salt and IV
20
+ const salt = crypto.randomBytes(this.saltLength);
21
+ const iv = crypto.randomBytes(this.ivLength);
22
+
23
+ // Derive a key from the password and salt
24
+ const key = this.deriveKey(password, salt);
25
+
26
+ // Create cipher
27
+ const cipher = crypto.createCipheriv(this.algorithm, key, iv, { authTagLength: this.tagLength });
28
+
29
+ // Encrypt the data
30
+ let encrypted = cipher.update(jsonString, 'utf8', 'hex');
31
+ encrypted += cipher.final('hex');
32
+
33
+ // Get the authentication tag
34
+ const tag = cipher.getAuthTag();
35
+
36
+ // Combine all pieces: salt (16 bytes) + iv (12 bytes) + tag (16 bytes) + encrypted data
37
+ return Buffer.concat([salt, iv, tag, Buffer.from(encrypted, 'hex')]).toString('base64');
38
+ }
39
+
40
+ decrypt(encryptedData: string, password: string): any {
41
+ // Convert the base64 string back to a buffer
42
+ const data = Buffer.from(encryptedData, 'base64');
43
+
44
+ // Extract the pieces
45
+ const salt = data.subarray(0, this.saltLength);
46
+ const iv = data.subarray(this.saltLength, this.saltLength + this.ivLength);
47
+ const tag = data.subarray(this.saltLength + this.ivLength, this.saltLength + this.ivLength + this.tagLength);
48
+ const encrypted = data.subarray(this.saltLength + this.ivLength + this.tagLength);
49
+
50
+ // Derive the key
51
+ const key = this.deriveKey(password, salt);
52
+
53
+ // Create decipher
54
+ const decipher = crypto.createDecipheriv(this.algorithm, key, iv, { authTagLength: this.tagLength });
55
+ decipher.setAuthTag(tag);
56
+
57
+ try {
58
+ // Decrypt the data
59
+ let decrypted = decipher.update(encrypted.toString('hex'), 'hex', 'utf8');
60
+ decrypted += decipher.final('utf8');
61
+
62
+ // Parse the JSON string
63
+ return JSON.parse(decrypted);
64
+ } catch (error) {
65
+ throw new Error('Decryption failed. This could be due to an incorrect password or corrupted data.');
66
+ }
67
+ }
68
+ }
@@ -0,0 +1,12 @@
1
+ export * from './store';
2
+ export * from './encrypt';
3
+
4
+ // Utility type to make all optional properties required
5
+ export type RequiredFields<T> = {
6
+ [K in keyof T]-?: T[K]
7
+ }
8
+
9
+ // Utility type to get only the required fields of a type
10
+ export type RequiredKeys<T> = {
11
+ [K in keyof T]-?: {} extends Pick<T, K> ? never : K
12
+ }[keyof T]