@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/dist/cli.js +18 -5
- package/dist/cli.mjs +18 -11
- package/dist/index.browser.global.js +111 -2348
- package/dist/index.browser.mjs +2582 -0
- package/dist/index.d.ts +82 -42
- package/dist/index.js +117 -19
- package/dist/index.mjs +115 -25
- package/package.json +14 -8
- package/src/global.ts +2 -0
- package/src/interfaces/common.ts +34 -2
- package/src/modules/index.ts +2 -1
- package/src/modules/pricer-from-api.ts +61 -0
- package/src/modules/pricer.ts +7 -19
- package/src/modules/pricerBase.ts +15 -0
- package/src/modules/zkLend.ts +1 -0
- package/src/node/pricer-redis.ts +1 -0
- package/src/strategies/vesu-rebalance.ts +40 -3
package/package.json
CHANGED
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@strkfarm/sdk",
|
|
3
|
-
"version": "1.0.
|
|
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":
|
|
14
|
-
|
|
15
|
-
|
|
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
|
});
|
package/src/interfaces/common.ts
CHANGED
|
@@ -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 {
|
package/src/modules/index.ts
CHANGED
|
@@ -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
|
+
}
|
package/src/modules/pricer.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
+
}
|
package/src/modules/zkLend.ts
CHANGED
|
@@ -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
|
package/src/node/pricer-redis.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { ContractAddr, Web3Number } from "@/dataTypes";
|
|
2
|
-
import { IConfig, IProtocol, IStrategyMetadata } from "@/interfaces";
|
|
3
|
-
import { Pricer
|
|
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
|
}]
|