@strkfarm/sdk 1.0.16 → 1.0.17

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/package.json CHANGED
@@ -1,18 +1,23 @@
1
1
  {
2
2
  "name": "@strkfarm/sdk",
3
- "version": "1.0.16",
3
+ "version": "1.0.17",
4
4
  "description": "STRKFarm TS SDK (Meant for our internal use, but feel free to use it)",
5
5
  "typings": "dist/index.d.ts",
6
- "main": "dist/index.js",
7
6
  "types": "dist/index.d.ts",
8
7
  "bin": {
9
8
  "accountsecure": "dist/cli.js"
10
9
  },
11
10
  "exports": {
12
11
  ".": {
13
- "import": "./dist/index.mjs",
14
- "require": "./dist/index.js",
15
- "browser": "./dist/index.global.js",
12
+ "import": {
13
+ "browser": "./dist/index.browser.mjs",
14
+ "default": "./dist/index.mjs"
15
+ },
16
+ "require": {
17
+ "browser": "./dist/index.browser.global.js",
18
+ "default": "./dist/index.js"
19
+ },
20
+ "browser": "./dist/index.browser.global.js",
16
21
  "types": "./dist/index.d.ts"
17
22
  }
18
23
  },
@@ -22,12 +27,13 @@
22
27
  ],
23
28
  "scripts": {
24
29
  "test": "jest",
25
- "build": "tsup --clean && pnpm run build:esm && npm run build:dts && npm run build:iife && npm run build-cli",
30
+ "build": "tsup --clean && pnpm run build:esm && npm run build:dts && npm run build:iife && npm run build-cli && pnpm run build:iife-esm",
26
31
  "build:esm": "tsup --clean false --format esm --platform node",
27
32
  "build-cli:esm": "tsup ./src/cli.ts --clean false --format esm --platform node",
28
33
  "build-cli": "tsup ./src/cli.ts --clean false && pnpm run build-cli:esm",
29
34
  "build:dts": "tsup --clean false --dts-only",
30
- "build:iife": "tsup --clean false --format iife --platform browser"
35
+ "build:iife": "tsup --clean false --format iife --platform browser",
36
+ "build:iife-esm": "tsup --clean false --format iife --format esm --platform browser"
31
37
  },
32
38
  "keywords": [],
33
39
  "author": "",
@@ -48,10 +54,10 @@
48
54
  "bignumber.js": "4.0.4",
49
55
  "browser-assert": "^1.2.1",
50
56
  "chalk": "^4.1.2",
51
- "coinmarketcap-api": "^3.1.1",
52
57
  "commander": "^12.1.0",
53
58
  "inquirer": "^10.1.2",
54
59
  "node-telegram-bot-api": "^0.66.0",
60
+ "proxy-from-env": "^1.1.0",
55
61
  "redis": "^4.7.0",
56
62
  "stacktrace-js": "^2.0.2",
57
63
  "starknet": "^6.11.0",
package/src/global.ts CHANGED
@@ -48,6 +48,7 @@ export class FatalError extends Error {
48
48
  const tokens: TokenInfo[] = [{
49
49
  name: 'Starknet',
50
50
  symbol: 'STRK',
51
+ logo: 'https://assets.coingecko.com/coins/images/26433/small/starknet.png',
51
52
  address: '0x4718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d',
52
53
  decimals: 18,
53
54
  coingeckId: 'starknet'
@@ -110,6 +111,7 @@ export class Global {
110
111
  symbol: token.symbol,
111
112
  address: token.address,
112
113
  decimals: token.decimals,
114
+ logo: token.logoUri,
113
115
  coingeckId: token.extensions.coingeckoId,
114
116
  });
115
117
  });
@@ -1,11 +1,27 @@
1
- import { ContractAddr } from "@/dataTypes"
1
+ import { ContractAddr, Web3Number } from "@/dataTypes"
2
2
  import { BlockIdentifier, RpcProvider } from "starknet"
3
3
 
4
+ export enum RiskType {
5
+ MARKET_RISK = 'MARKET_RISK',
6
+ IMPERMANENT_LOSS = 'IMPERMANENT_LOSS',
7
+ LIQUIDITY_RISK = 'LIQUIDITY_RISK',
8
+ SMART_CONTRACT_RISK = 'SMART_CONTRACT_RISK',
9
+ TECHNICAL_RISK = 'TECHNICAL_RISK',
10
+ COUNTERPARTY_RISK = 'COUNTERPARTY_RISK', // e.g. bad debt
11
+ }
12
+
13
+ export interface RiskFactor {
14
+ type: RiskType,
15
+ value: number, // 0 to 5
16
+ weight: number // 0 to 100
17
+ }
18
+
4
19
  export interface TokenInfo {
5
20
  name: string,
6
21
  symbol: string,
7
22
  address: string,
8
23
  decimals: number,
24
+ logo: string,
9
25
  coingeckId?: string,
10
26
  }
11
27
 
@@ -27,13 +43,29 @@ export interface IProtocol {
27
43
  logo: string,
28
44
  }
29
45
 
46
+ /**
47
+ * @property risk.riskFactor.factor - The risk factors that are considered for the strategy.
48
+ * @property risk.riskFactor.factor - The value of the risk factor from 0 to 10, 0 being the lowest and 10 being the highest.
49
+ */
30
50
  export interface IStrategyMetadata {
31
51
  name: string,
32
52
  description: string,
33
53
  address: ContractAddr,
34
54
  type: 'ERC4626' | 'ERC721' | 'Other',
35
55
  depositTokens: TokenInfo[],
36
- protocols: IProtocol[]
56
+ protocols: IProtocol[],
57
+ auditUrl?: string,
58
+ maxTVL: Web3Number,
59
+ risk: {
60
+ riskFactor: RiskFactor[],
61
+ netRisk: number
62
+ }
63
+ }
64
+
65
+ export interface IInvestmentFlow {
66
+ title: string,
67
+ subItems: string[],
68
+ linkedFlows: IInvestmentFlow[],
37
69
  }
38
70
 
39
71
  export function getMainnetConfig(rpcUrl = "https://starknet-mainnet.public.blastapi.io", blockIdentifier: BlockIdentifier = 'pending'): IConfig {
@@ -1,3 +1,4 @@
1
1
  export * from './pricer';
2
2
  export * from './pragma';
3
- export * from './zkLend';
3
+ export * from './zkLend';
4
+ export * from './pricer-from-api';
@@ -0,0 +1,61 @@
1
+ import { logger } from "@/global";
2
+ import { PriceInfo } from "./pricer";
3
+ import axios from "axios";
4
+ import { IConfig, TokenInfo } from "@/interfaces";
5
+ import { PricerBase } from "./pricerBase";
6
+
7
+ export class PricerFromApi extends PricerBase {
8
+ constructor(config: IConfig, tokens: TokenInfo[]) {
9
+ super(config, tokens);
10
+ }
11
+
12
+ async getPrice(tokenSymbol: string): Promise<PriceInfo> {
13
+ try {
14
+ return await this.getPriceFromMyAPI(tokenSymbol);
15
+ } catch (e) {
16
+ logger.warn('getPriceFromMyAPI error', e);
17
+ }
18
+ logger.log('getPrice coinbase', tokenSymbol);
19
+ let retry = 0;
20
+ const MAX_RETRIES = 5;
21
+ for (retry = 1; retry < MAX_RETRIES + 1; retry++) {
22
+ try {
23
+ const priceInfo = await axios.get(
24
+ `https://api.coinbase.com/v2/prices/${tokenSymbol}-USDT/spot`,
25
+ );
26
+ if (!priceInfo) {
27
+ throw new Error('Failed to fetch price');
28
+ }
29
+ const data = await priceInfo.data;
30
+ const price = Number(data.data.amount);
31
+ return {
32
+ price,
33
+ timestamp: new Date()
34
+ }
35
+ } catch (e) {
36
+ logger.warn('getPrice coinbase error', e, retry);
37
+ await new Promise((resolve) => setTimeout(resolve, retry * 1000));
38
+ }
39
+ }
40
+ throw new Error(`Failed to fetch price for ${tokenSymbol}`);
41
+ }
42
+
43
+ async getPriceFromMyAPI(tokenSymbol: string) {
44
+ logger.verbose(`getPrice from redis: ${tokenSymbol}`);
45
+ const endpoint = 'https://app.strkfarm.com'
46
+ const url = `${endpoint}/api/price/${tokenSymbol}`;
47
+ const priceInfoRes = await fetch(url);
48
+ const priceInfo = await priceInfoRes.json();
49
+ const now = new Date();
50
+ const priceTime = new Date(priceInfo.timestamp);
51
+ if (now.getTime() - priceTime.getTime() > 900000) {
52
+ // 15 mins
53
+ throw new Error('Price is stale');
54
+ }
55
+ const price = Number(priceInfo.price);
56
+ return {
57
+ price,
58
+ timestamp: new Date(priceInfo.timestamp)
59
+ }
60
+ }
61
+ }
@@ -3,22 +3,14 @@ import { FatalError, Global, logger } from "@/global";
3
3
  import { TokenInfo } from "@/interfaces/common";
4
4
  import { IConfig } from "@/interfaces/common";
5
5
  import { Web3Number } from "@/dataTypes";
6
- const CoinMarketCap = require('coinmarketcap-api')
6
+ import { PricerBase } from "./pricerBase";
7
7
 
8
8
  export interface PriceInfo {
9
9
  price: number,
10
10
  timestamp: Date
11
11
  }
12
12
 
13
- export abstract class PricerBase {
14
- async getPrice(tokenSymbol: string): Promise<PriceInfo> {
15
- throw new Error('Method not implemented');
16
- }
17
- }
18
-
19
- export class Pricer implements PricerBase{
20
- readonly config: IConfig;
21
- readonly tokens: TokenInfo[] = [];
13
+ export class Pricer extends PricerBase {
22
14
  protected prices: {
23
15
  [key: string]: PriceInfo
24
16
  } = {}
@@ -33,12 +25,8 @@ export class Pricer implements PricerBase{
33
25
  protected PRICE_API = `https://api.coinbase.com/v2/prices/{{PRICER_KEY}}/buy`;
34
26
  protected EKUBO_API = 'https://quoter-mainnet-api.ekubo.org/{{AMOUNT}}/{{TOKEN_ADDRESS}}/0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8'; // e.g. ETH/USDC
35
27
 
36
- // backup oracle001
37
- protected client = new CoinMarketCap(process.env.COINMARKETCAP_KEY!);
38
-
39
28
  constructor(config: IConfig, tokens: TokenInfo[]) {
40
- this.config = config;
41
- this.tokens = tokens;
29
+ super(config, tokens);
42
30
  }
43
31
 
44
32
  isReady() {
@@ -187,11 +175,11 @@ export class Pricer implements PricerBase{
187
175
  }
188
176
 
189
177
  async _getPriceCoinMarketCap(token: TokenInfo): Promise<number> {
190
- const result = await this.client.getQuotes({symbol: token.symbol});
191
- if (result.data)
192
- return result.data[token.symbol].quote.USD.price as number
178
+ // const result = await this.client.getQuotes({symbol: token.symbol});
179
+ // if (result.data)
180
+ // return result.data[token.symbol].quote.USD.price as number
193
181
 
194
- throw new Error(result);
182
+ throw new Error("Not implemented");
195
183
  }
196
184
 
197
185
  async _getPriceEkubo(token: TokenInfo, amountIn = new Web3Number(1, token.decimals), retry = 0): Promise<number> {
@@ -0,0 +1,15 @@
1
+ import { IConfig, TokenInfo } from "@/interfaces";
2
+ import { PriceInfo } from "./pricer";
3
+
4
+ export abstract class PricerBase {
5
+ readonly config: IConfig;
6
+ readonly tokens: TokenInfo[];
7
+ constructor(config: IConfig, tokens: TokenInfo[]) {
8
+ this.config = config;
9
+ this.tokens = tokens;
10
+ }
11
+
12
+ async getPrice(tokenSymbol: string): Promise<PriceInfo> {
13
+ throw new Error('Method not implemented');
14
+ }
15
+ }
@@ -37,6 +37,7 @@ export class ZkLend extends ILending implements ILending {
37
37
  name: pool.token.name,
38
38
  symbol: pool.token.symbol,
39
39
  address: savedTokenInfo?.address || '',
40
+ logo: '',
40
41
  decimals: pool.token.decimals,
41
42
  borrowFactor: Web3Number.fromWei(pool.borrow_factor.value, pool.borrow_factor.decimals),
42
43
  collareralFactor
@@ -6,6 +6,7 @@ import type { RedisClientType } from 'redis'
6
6
 
7
7
  export class PricerRedis extends Pricer {
8
8
  private redisClient: RedisClientType | null = null;
9
+
9
10
  constructor(config: IConfig, tokens: TokenInfo[]) {
10
11
  super(config, tokens)
11
12
  }
@@ -1,11 +1,12 @@
1
1
  import { ContractAddr, Web3Number } from "@/dataTypes";
2
- import { IConfig, IProtocol, IStrategyMetadata } from "@/interfaces";
3
- import { Pricer, PricerBase } from "@/modules";
2
+ import { IConfig, IInvestmentFlow, IProtocol, IStrategyMetadata, RiskFactor, RiskType } from "@/interfaces";
3
+ import { Pricer } from "@/modules";
4
4
  import { CairoCustomEnum, Contract, num, uint256 } from "starknet";
5
5
  import VesuRebalanceAbi from '@/data/vesu-rebalance.abi.json';
6
6
  import { Global } from "@/global";
7
7
  import { assert } from "@/utils";
8
8
  import axios from "axios";
9
+ import { PricerBase } from "@/modules/pricerBase";
9
10
 
10
11
  interface PoolProps {
11
12
  pool_id: ContractAddr;
@@ -412,10 +413,41 @@ export class VesuRebalance {
412
413
  }
413
414
  return this.contract.populate('rebalance', [actions]);
414
415
  }
416
+
417
+ async getInvestmentFlows(pools: PoolInfoFull[]) {
418
+ const netYield = this.netAPYGivenPools(pools);
419
+
420
+ const baseFlow: IInvestmentFlow = {
421
+ title: "Deposit $1000",
422
+ subItems: [`Net yield: ${(netYield * 100).toFixed(2)}%`],
423
+ linkedFlows: [],
424
+ };
425
+
426
+ pools.forEach((p) => {
427
+ if (p.amount.eq(0)) return;
428
+ const flow: IInvestmentFlow = {
429
+ title: `${p.pool_name} - $${(p.current_weight * 1000).toFixed(2)}`,
430
+ subItems: [
431
+ `APY: ${(p.APY.netApy * 100).toFixed(2)}%`,
432
+ `Weight: ${(p.current_weight * 100).toFixed(2)}% / ${(p.max_weight * 100).toFixed(2)}%`,
433
+ ],
434
+ linkedFlows: [],
435
+ };
436
+ baseFlow.linkedFlows.push(flow);
437
+ });
438
+
439
+ return [baseFlow];
440
+ }
415
441
  }
416
442
 
417
443
  const _description = 'Automatically diversify {{TOKEN}} holdings into different Vesu pools while reducing risk and maximizing yield. Defi spring STRK Rewards are auto-compounded as well.'
418
444
  const _protocol: IProtocol = {name: 'Vesu', logo: 'https://static-assets-8zct.onrender.com/integrations/vesu/logo.png'}
445
+ // need to fine tune better
446
+ const _riskFactor: RiskFactor[] = [
447
+ {type: RiskType.SMART_CONTRACT_RISK, value: 0.5, weight: 25},
448
+ {type: RiskType.TECHNICAL_RISK, value: 0.5, weight: 25},
449
+ {type: RiskType.COUNTERPARTY_RISK, value: 1, weight: 50},
450
+ ]
419
451
  /**
420
452
  * Represents the Vesu Rebalance Strategies.
421
453
  */
@@ -425,5 +457,10 @@ export const VesuRebalanceStrategies: IStrategyMetadata[] = [{
425
457
  address: ContractAddr.from('0xeeb729d554ae486387147b13a9c8871bc7991d454e8b5ff570d4bf94de71e1'),
426
458
  type: 'ERC4626',
427
459
  depositTokens: [Global.getDefaultTokens().find(t => t.symbol === 'STRK')!],
428
- protocols: [_protocol]
460
+ protocols: [_protocol],
461
+ maxTVL: Web3Number.fromWei('0', 18),
462
+ risk: {
463
+ riskFactor: _riskFactor,
464
+ netRisk: _riskFactor.reduce((acc, curr) => acc + curr.value * curr.weight, 0) / 100,
465
+ }
429
466
  }]