@strkfarm/sdk 1.0.16 → 1.0.18
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 +122 -2349
- package/dist/index.browser.mjs +2592 -0
- package/dist/index.d.ts +91 -42
- package/dist/index.js +129 -20
- package/dist/index.mjs +126 -26
- package/package.json +15 -8
- package/src/global.ts +2 -0
- package/src/interfaces/common.ts +41 -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 +43 -4
package/dist/index.mjs
CHANGED
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
-
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
-
}) : x)(function(x) {
|
|
4
|
-
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
5
|
-
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
6
|
-
});
|
|
7
|
-
|
|
8
1
|
// src/modules/pricer.ts
|
|
9
2
|
import axios2 from "axios";
|
|
10
3
|
|
|
@@ -28,6 +21,7 @@ var FatalError = class extends Error {
|
|
|
28
21
|
var tokens = [{
|
|
29
22
|
name: "Starknet",
|
|
30
23
|
symbol: "STRK",
|
|
24
|
+
logo: "https://assets.coingecko.com/coins/images/26433/small/starknet.png",
|
|
31
25
|
address: "0x4718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d",
|
|
32
26
|
decimals: 18,
|
|
33
27
|
coingeckId: "starknet"
|
|
@@ -60,6 +54,7 @@ var Global = class {
|
|
|
60
54
|
symbol: token.symbol,
|
|
61
55
|
address: token.address,
|
|
62
56
|
decimals: token.decimals,
|
|
57
|
+
logo: token.logoUri,
|
|
63
58
|
coingeckId: token.extensions.coingeckoId
|
|
64
59
|
});
|
|
65
60
|
});
|
|
@@ -139,16 +134,22 @@ var ContractAddr = class _ContractAddr {
|
|
|
139
134
|
}
|
|
140
135
|
};
|
|
141
136
|
|
|
142
|
-
// src/modules/
|
|
143
|
-
var CoinMarketCap = __require("coinmarketcap-api");
|
|
137
|
+
// src/modules/pricerBase.ts
|
|
144
138
|
var PricerBase = class {
|
|
139
|
+
constructor(config, tokens2) {
|
|
140
|
+
this.config = config;
|
|
141
|
+
this.tokens = tokens2;
|
|
142
|
+
}
|
|
145
143
|
async getPrice(tokenSymbol) {
|
|
146
144
|
throw new Error("Method not implemented");
|
|
147
145
|
}
|
|
148
146
|
};
|
|
149
|
-
|
|
147
|
+
|
|
148
|
+
// src/modules/pricer.ts
|
|
149
|
+
var Pricer = class extends PricerBase {
|
|
150
|
+
// e.g. ETH/USDC
|
|
150
151
|
constructor(config, tokens2) {
|
|
151
|
-
|
|
152
|
+
super(config, tokens2);
|
|
152
153
|
this.prices = {};
|
|
153
154
|
// code populates this map during runtime to determine which method to use for a given token
|
|
154
155
|
// The method set will be the first one to try after first attempt
|
|
@@ -158,11 +159,6 @@ var Pricer = class {
|
|
|
158
159
|
*/
|
|
159
160
|
this.PRICE_API = `https://api.coinbase.com/v2/prices/{{PRICER_KEY}}/buy`;
|
|
160
161
|
this.EKUBO_API = "https://quoter-mainnet-api.ekubo.org/{{AMOUNT}}/{{TOKEN_ADDRESS}}/0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8";
|
|
161
|
-
// e.g. ETH/USDC
|
|
162
|
-
// backup oracle001
|
|
163
|
-
this.client = new CoinMarketCap(process.env.COINMARKETCAP_KEY);
|
|
164
|
-
this.config = config;
|
|
165
|
-
this.tokens = tokens2;
|
|
166
162
|
}
|
|
167
163
|
isReady() {
|
|
168
164
|
const allPricesExist = Object.keys(this.prices).length === this.tokens.length;
|
|
@@ -294,10 +290,7 @@ var Pricer = class {
|
|
|
294
290
|
return Number(data.data.amount);
|
|
295
291
|
}
|
|
296
292
|
async _getPriceCoinMarketCap(token) {
|
|
297
|
-
|
|
298
|
-
if (result.data)
|
|
299
|
-
return result.data[token.symbol].quote.USD.price;
|
|
300
|
-
throw new Error(result);
|
|
293
|
+
throw new Error("Not implemented");
|
|
301
294
|
}
|
|
302
295
|
async _getPriceEkubo(token, amountIn = new Web3Number(1, token.decimals), retry = 0) {
|
|
303
296
|
const url = this.EKUBO_API.replace("{{TOKEN_ADDRESS}}", token.address).replace("{{AMOUNT}}", amountIn.toWei());
|
|
@@ -491,6 +484,7 @@ var _ZkLend = class _ZkLend extends ILending {
|
|
|
491
484
|
name: pool.token.name,
|
|
492
485
|
symbol: pool.token.symbol,
|
|
493
486
|
address: savedTokenInfo?.address || "",
|
|
487
|
+
logo: "",
|
|
494
488
|
decimals: pool.token.decimals,
|
|
495
489
|
borrowFactor: Web3Number.fromWei(pool.borrow_factor.value, pool.borrow_factor.decimals),
|
|
496
490
|
collareralFactor
|
|
@@ -600,14 +594,84 @@ var _ZkLend = class _ZkLend extends ILending {
|
|
|
600
594
|
_ZkLend.POOLS_URL = "https://app.zklend.com/api/pools";
|
|
601
595
|
var ZkLend = _ZkLend;
|
|
602
596
|
|
|
597
|
+
// src/modules/pricer-from-api.ts
|
|
598
|
+
import axios4 from "axios";
|
|
599
|
+
var PricerFromApi = class extends PricerBase {
|
|
600
|
+
constructor(config, tokens2) {
|
|
601
|
+
super(config, tokens2);
|
|
602
|
+
}
|
|
603
|
+
async getPrice(tokenSymbol) {
|
|
604
|
+
try {
|
|
605
|
+
return await this.getPriceFromMyAPI(tokenSymbol);
|
|
606
|
+
} catch (e) {
|
|
607
|
+
logger.warn("getPriceFromMyAPI error", e);
|
|
608
|
+
}
|
|
609
|
+
logger.log("getPrice coinbase", tokenSymbol);
|
|
610
|
+
let retry = 0;
|
|
611
|
+
const MAX_RETRIES = 5;
|
|
612
|
+
for (retry = 1; retry < MAX_RETRIES + 1; retry++) {
|
|
613
|
+
try {
|
|
614
|
+
const priceInfo = await axios4.get(
|
|
615
|
+
`https://api.coinbase.com/v2/prices/${tokenSymbol}-USDT/spot`
|
|
616
|
+
);
|
|
617
|
+
if (!priceInfo) {
|
|
618
|
+
throw new Error("Failed to fetch price");
|
|
619
|
+
}
|
|
620
|
+
const data = await priceInfo.data;
|
|
621
|
+
const price = Number(data.data.amount);
|
|
622
|
+
return {
|
|
623
|
+
price,
|
|
624
|
+
timestamp: /* @__PURE__ */ new Date()
|
|
625
|
+
};
|
|
626
|
+
} catch (e) {
|
|
627
|
+
logger.warn("getPrice coinbase error", e, retry);
|
|
628
|
+
await new Promise((resolve) => setTimeout(resolve, retry * 1e3));
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
throw new Error(`Failed to fetch price for ${tokenSymbol}`);
|
|
632
|
+
}
|
|
633
|
+
async getPriceFromMyAPI(tokenSymbol) {
|
|
634
|
+
logger.verbose(`getPrice from redis: ${tokenSymbol}`);
|
|
635
|
+
const endpoint = "https://app.strkfarm.com";
|
|
636
|
+
const url = `${endpoint}/api/price/${tokenSymbol}`;
|
|
637
|
+
const priceInfoRes = await fetch(url);
|
|
638
|
+
const priceInfo = await priceInfoRes.json();
|
|
639
|
+
const now = /* @__PURE__ */ new Date();
|
|
640
|
+
const priceTime = new Date(priceInfo.timestamp);
|
|
641
|
+
if (now.getTime() - priceTime.getTime() > 9e5) {
|
|
642
|
+
throw new Error("Price is stale");
|
|
643
|
+
}
|
|
644
|
+
const price = Number(priceInfo.price);
|
|
645
|
+
return {
|
|
646
|
+
price,
|
|
647
|
+
timestamp: new Date(priceInfo.timestamp)
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
};
|
|
651
|
+
|
|
603
652
|
// src/interfaces/common.ts
|
|
604
653
|
import { RpcProvider as RpcProvider2 } from "starknet";
|
|
654
|
+
var RiskType = /* @__PURE__ */ ((RiskType2) => {
|
|
655
|
+
RiskType2["MARKET_RISK"] = "MARKET_RISK";
|
|
656
|
+
RiskType2["IMPERMANENT_LOSS"] = "IMPERMANENT_LOSS";
|
|
657
|
+
RiskType2["LIQUIDITY_RISK"] = "LIQUIDITY_RISK";
|
|
658
|
+
RiskType2["SMART_CONTRACT_RISK"] = "SMART_CONTRACT_RISK";
|
|
659
|
+
RiskType2["TECHNICAL_RISK"] = "TECHNICAL_RISK";
|
|
660
|
+
RiskType2["COUNTERPARTY_RISK"] = "COUNTERPARTY_RISK";
|
|
661
|
+
return RiskType2;
|
|
662
|
+
})(RiskType || {});
|
|
605
663
|
var Network = /* @__PURE__ */ ((Network2) => {
|
|
606
664
|
Network2["mainnet"] = "mainnet";
|
|
607
665
|
Network2["sepolia"] = "sepolia";
|
|
608
666
|
Network2["devnet"] = "devnet";
|
|
609
667
|
return Network2;
|
|
610
668
|
})(Network || {});
|
|
669
|
+
var FlowChartColors = /* @__PURE__ */ ((FlowChartColors2) => {
|
|
670
|
+
FlowChartColors2["Green"] = "purple";
|
|
671
|
+
FlowChartColors2["Blue"] = "#35484f";
|
|
672
|
+
FlowChartColors2["Purple"] = "#6e53dc";
|
|
673
|
+
return FlowChartColors2;
|
|
674
|
+
})(FlowChartColors || {});
|
|
611
675
|
function getMainnetConfig(rpcUrl = "https://starknet-mainnet.public.blastapi.io", blockIdentifier = "pending") {
|
|
612
676
|
return {
|
|
613
677
|
provider: new RpcProvider2({
|
|
@@ -2183,7 +2247,7 @@ function assert(condition, message) {
|
|
|
2183
2247
|
}
|
|
2184
2248
|
|
|
2185
2249
|
// src/strategies/vesu-rebalance.ts
|
|
2186
|
-
import
|
|
2250
|
+
import axios5 from "axios";
|
|
2187
2251
|
var VesuRebalance = class _VesuRebalance {
|
|
2188
2252
|
// 10000 bps = 100%
|
|
2189
2253
|
/**
|
|
@@ -2290,7 +2354,7 @@ var VesuRebalance = class _VesuRebalance {
|
|
|
2290
2354
|
let isErrorPositionsAPI = false;
|
|
2291
2355
|
let vesuPositions = [];
|
|
2292
2356
|
try {
|
|
2293
|
-
const res = await
|
|
2357
|
+
const res = await axios5.get(`https://api.vesu.xyz/positions?walletAddress=${this.address.address}`);
|
|
2294
2358
|
const data2 = await res.data;
|
|
2295
2359
|
vesuPositions = data2.data;
|
|
2296
2360
|
} catch (e) {
|
|
@@ -2300,7 +2364,7 @@ var VesuRebalance = class _VesuRebalance {
|
|
|
2300
2364
|
let isErrorPoolsAPI = false;
|
|
2301
2365
|
let pools = [];
|
|
2302
2366
|
try {
|
|
2303
|
-
const res = await
|
|
2367
|
+
const res = await axios5.get(`https://api.vesu.xyz/pools`);
|
|
2304
2368
|
const data2 = await res.data;
|
|
2305
2369
|
pools = data2.data;
|
|
2306
2370
|
} catch (e) {
|
|
@@ -2317,7 +2381,7 @@ var VesuRebalance = class _VesuRebalance {
|
|
|
2317
2381
|
const assets = await vTokenContract.convert_to_assets(uint2562.bnToUint256(bal.toString()));
|
|
2318
2382
|
const item = {
|
|
2319
2383
|
pool_id: p.pool_id,
|
|
2320
|
-
pool_name:
|
|
2384
|
+
pool_name: pool.name,
|
|
2321
2385
|
max_weight: p.max_weight,
|
|
2322
2386
|
current_weight: isErrorPositionsAPI || !vesuPosition ? 0 : Number(Web3Number.fromWei(vesuPosition.collateral.value, this.decimals()).dividedBy(totalAssets.toString()).toFixed(6)),
|
|
2323
2387
|
v_token: p.v_token,
|
|
@@ -2460,16 +2524,50 @@ var VesuRebalance = class _VesuRebalance {
|
|
|
2460
2524
|
}
|
|
2461
2525
|
return this.contract.populate("rebalance", [actions]);
|
|
2462
2526
|
}
|
|
2527
|
+
async getInvestmentFlows(pools) {
|
|
2528
|
+
const netYield = this.netAPYGivenPools(pools);
|
|
2529
|
+
const baseFlow = {
|
|
2530
|
+
title: "Your Deposit",
|
|
2531
|
+
subItems: [{ key: `Net yield`, value: `${(netYield * 100).toFixed(2)}%` }],
|
|
2532
|
+
linkedFlows: [],
|
|
2533
|
+
style: { backgroundColor: "#6e53dc" /* Purple */.valueOf() }
|
|
2534
|
+
};
|
|
2535
|
+
let _pools = [...pools];
|
|
2536
|
+
_pools = _pools.sort((a, b) => Number(b.amount.toString()) - Number(a.amount.toString()));
|
|
2537
|
+
_pools.forEach((p) => {
|
|
2538
|
+
const flow = {
|
|
2539
|
+
title: `Pool name: ${p.pool_name}`,
|
|
2540
|
+
subItems: [
|
|
2541
|
+
{ key: `APY`, value: `${(p.APY.netApy * 100).toFixed(2)}%` },
|
|
2542
|
+
{ key: "Weight", value: `${(p.current_weight * 100).toFixed(2)} / ${(p.max_weight * 100).toFixed(2)}%` }
|
|
2543
|
+
],
|
|
2544
|
+
linkedFlows: [],
|
|
2545
|
+
style: p.amount.greaterThan(0) ? { backgroundColor: "#35484f" /* Blue */.valueOf() } : { color: "gray" }
|
|
2546
|
+
};
|
|
2547
|
+
baseFlow.linkedFlows.push(flow);
|
|
2548
|
+
});
|
|
2549
|
+
return [baseFlow];
|
|
2550
|
+
}
|
|
2463
2551
|
};
|
|
2464
2552
|
var _description = "Automatically diversify {{TOKEN}} holdings into different Vesu pools while reducing risk and maximizing yield. Defi spring STRK Rewards are auto-compounded as well.";
|
|
2465
2553
|
var _protocol = { name: "Vesu", logo: "https://static-assets-8zct.onrender.com/integrations/vesu/logo.png" };
|
|
2554
|
+
var _riskFactor = [
|
|
2555
|
+
{ type: "SMART_CONTRACT_RISK" /* SMART_CONTRACT_RISK */, value: 0.5, weight: 25 },
|
|
2556
|
+
{ type: "TECHNICAL_RISK" /* TECHNICAL_RISK */, value: 0.5, weight: 25 },
|
|
2557
|
+
{ type: "COUNTERPARTY_RISK" /* COUNTERPARTY_RISK */, value: 1, weight: 50 }
|
|
2558
|
+
];
|
|
2466
2559
|
var VesuRebalanceStrategies = [{
|
|
2467
2560
|
name: "Vesu STRK",
|
|
2468
2561
|
description: _description.replace("{{TOKEN}}", "STRK"),
|
|
2469
2562
|
address: ContractAddr.from("0xeeb729d554ae486387147b13a9c8871bc7991d454e8b5ff570d4bf94de71e1"),
|
|
2470
2563
|
type: "ERC4626",
|
|
2471
2564
|
depositTokens: [Global.getDefaultTokens().find((t) => t.symbol === "STRK")],
|
|
2472
|
-
protocols: [_protocol]
|
|
2565
|
+
protocols: [_protocol],
|
|
2566
|
+
maxTVL: Web3Number.fromWei("0", 18),
|
|
2567
|
+
risk: {
|
|
2568
|
+
riskFactor: _riskFactor,
|
|
2569
|
+
netRisk: _riskFactor.reduce((acc, curr) => acc + curr.value * curr.weight, 0) / 100
|
|
2570
|
+
}
|
|
2473
2571
|
}];
|
|
2474
2572
|
|
|
2475
2573
|
// src/notifs/telegram.ts
|
|
@@ -2737,6 +2835,7 @@ export {
|
|
|
2737
2835
|
AutoCompounderSTRK,
|
|
2738
2836
|
ContractAddr,
|
|
2739
2837
|
FatalError,
|
|
2838
|
+
FlowChartColors,
|
|
2740
2839
|
Global,
|
|
2741
2840
|
ILending,
|
|
2742
2841
|
Initializable,
|
|
@@ -2745,8 +2844,9 @@ export {
|
|
|
2745
2844
|
PasswordJsonCryptoUtil,
|
|
2746
2845
|
Pragma,
|
|
2747
2846
|
Pricer,
|
|
2748
|
-
|
|
2847
|
+
PricerFromApi,
|
|
2749
2848
|
PricerRedis,
|
|
2849
|
+
RiskType,
|
|
2750
2850
|
Store,
|
|
2751
2851
|
TelegramNotif,
|
|
2752
2852
|
VesuRebalance,
|
package/package.json
CHANGED
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@strkfarm/sdk",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.18",
|
|
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,11 @@
|
|
|
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",
|
|
58
|
+
"form-data": "^4.0.2",
|
|
53
59
|
"inquirer": "^10.1.2",
|
|
54
60
|
"node-telegram-bot-api": "^0.66.0",
|
|
61
|
+
"proxy-from-env": "^1.1.0",
|
|
55
62
|
"redis": "^4.7.0",
|
|
56
63
|
"stacktrace-js": "^2.0.2",
|
|
57
64
|
"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,36 @@ export interface IProtocol {
|
|
|
27
43
|
logo: string,
|
|
28
44
|
}
|
|
29
45
|
|
|
46
|
+
export enum FlowChartColors {
|
|
47
|
+
Green = 'purple',
|
|
48
|
+
Blue = '#35484f',
|
|
49
|
+
Purple = '#6e53dc',
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @property risk.riskFactor.factor - The risk factors that are considered for the strategy.
|
|
54
|
+
* @property risk.riskFactor.factor - The value of the risk factor from 0 to 10, 0 being the lowest and 10 being the highest.
|
|
55
|
+
*/
|
|
30
56
|
export interface IStrategyMetadata {
|
|
31
57
|
name: string,
|
|
32
58
|
description: string,
|
|
33
59
|
address: ContractAddr,
|
|
34
60
|
type: 'ERC4626' | 'ERC721' | 'Other',
|
|
35
61
|
depositTokens: TokenInfo[],
|
|
36
|
-
protocols: IProtocol[]
|
|
62
|
+
protocols: IProtocol[],
|
|
63
|
+
auditUrl?: string,
|
|
64
|
+
maxTVL: Web3Number,
|
|
65
|
+
risk: {
|
|
66
|
+
riskFactor: RiskFactor[],
|
|
67
|
+
netRisk: number
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface IInvestmentFlow {
|
|
72
|
+
title: string,
|
|
73
|
+
subItems: {key: string, value: string}[],
|
|
74
|
+
linkedFlows: IInvestmentFlow[],
|
|
75
|
+
style?: any
|
|
37
76
|
}
|
|
38
77
|
|
|
39
78
|
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 { FlowChartColors, 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;
|
|
@@ -247,7 +248,7 @@ export class VesuRebalance {
|
|
|
247
248
|
const assets = await vTokenContract.convert_to_assets(uint256.bnToUint256(bal.toString()));
|
|
248
249
|
const item = {
|
|
249
250
|
pool_id: p.pool_id,
|
|
250
|
-
pool_name:
|
|
251
|
+
pool_name: pool.name,
|
|
251
252
|
max_weight: p.max_weight,
|
|
252
253
|
current_weight: isErrorPositionsAPI || !vesuPosition ? 0 : Number(Web3Number.fromWei(vesuPosition.collateral.value, this.decimals()).dividedBy(totalAssets.toString()).toFixed(6)),
|
|
253
254
|
v_token: p.v_token,
|
|
@@ -412,10 +413,43 @@ 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: "Your Deposit",
|
|
422
|
+
subItems: [{key: `Net yield`, value: `${(netYield * 100).toFixed(2)}%`}],
|
|
423
|
+
linkedFlows: [],
|
|
424
|
+
style: {backgroundColor: FlowChartColors.Purple.valueOf()},
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
let _pools = [...pools];
|
|
428
|
+
_pools = _pools.sort((a, b) => Number(b.amount.toString()) - Number(a.amount.toString()));
|
|
429
|
+
_pools.forEach((p) => {
|
|
430
|
+
const flow: IInvestmentFlow = {
|
|
431
|
+
title: `Pool name: ${p.pool_name}`,
|
|
432
|
+
subItems: [
|
|
433
|
+
{key: `APY`, value: `${(p.APY.netApy * 100).toFixed(2)}%`},
|
|
434
|
+
{key: 'Weight', value: `${(p.current_weight * 100).toFixed(2)} / ${(p.max_weight * 100).toFixed(2)}%`}
|
|
435
|
+
],
|
|
436
|
+
linkedFlows: [],
|
|
437
|
+
style: p.amount.greaterThan(0) ? {backgroundColor: FlowChartColors.Blue.valueOf()} : {color: 'gray'},
|
|
438
|
+
};
|
|
439
|
+
baseFlow.linkedFlows.push(flow);
|
|
440
|
+
});
|
|
441
|
+
return [baseFlow];
|
|
442
|
+
}
|
|
415
443
|
}
|
|
416
444
|
|
|
417
445
|
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
446
|
const _protocol: IProtocol = {name: 'Vesu', logo: 'https://static-assets-8zct.onrender.com/integrations/vesu/logo.png'}
|
|
447
|
+
// need to fine tune better
|
|
448
|
+
const _riskFactor: RiskFactor[] = [
|
|
449
|
+
{type: RiskType.SMART_CONTRACT_RISK, value: 0.5, weight: 25},
|
|
450
|
+
{type: RiskType.TECHNICAL_RISK, value: 0.5, weight: 25},
|
|
451
|
+
{type: RiskType.COUNTERPARTY_RISK, value: 1, weight: 50},
|
|
452
|
+
]
|
|
419
453
|
/**
|
|
420
454
|
* Represents the Vesu Rebalance Strategies.
|
|
421
455
|
*/
|
|
@@ -425,5 +459,10 @@ export const VesuRebalanceStrategies: IStrategyMetadata[] = [{
|
|
|
425
459
|
address: ContractAddr.from('0xeeb729d554ae486387147b13a9c8871bc7991d454e8b5ff570d4bf94de71e1'),
|
|
426
460
|
type: 'ERC4626',
|
|
427
461
|
depositTokens: [Global.getDefaultTokens().find(t => t.symbol === 'STRK')!],
|
|
428
|
-
protocols: [_protocol]
|
|
462
|
+
protocols: [_protocol],
|
|
463
|
+
maxTVL: Web3Number.fromWei('0', 18),
|
|
464
|
+
risk: {
|
|
465
|
+
riskFactor: _riskFactor,
|
|
466
|
+
netRisk: _riskFactor.reduce((acc, curr) => acc + curr.value * curr.weight, 0) / 100,
|
|
467
|
+
}
|
|
429
468
|
}]
|