@strkfarm/sdk 2.0.0-dev.4 → 2.0.0-dev.41
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 +190 -36
- package/dist/cli.mjs +188 -34
- package/dist/index.browser.global.js +116250 -90801
- package/dist/index.browser.mjs +13050 -10957
- package/dist/index.d.ts +2232 -1933
- package/dist/index.js +13380 -11084
- package/dist/index.mjs +13280 -11007
- package/package.json +6 -7
- package/src/data/avnu.abi.json +840 -0
- package/src/data/ekubo-price-fethcer.abi.json +265 -0
- package/src/data/redeem-request-nft.abi.json +752 -0
- package/src/data/universal-vault.abi.json +8 -7
- package/src/dataTypes/_bignumber.ts +13 -4
- package/src/dataTypes/bignumber.browser.ts +10 -1
- package/src/dataTypes/bignumber.node.ts +10 -1
- package/src/dataTypes/index.ts +3 -2
- package/src/dataTypes/mynumber.ts +141 -0
- package/src/global.ts +93 -36
- package/src/index.browser.ts +2 -1
- package/src/interfaces/common.tsx +218 -5
- package/src/modules/apollo-client-config.ts +28 -0
- package/src/modules/avnu.ts +21 -12
- package/src/modules/ekubo-pricer.ts +79 -0
- package/src/modules/ekubo-quoter.ts +48 -30
- package/src/modules/erc20.ts +17 -0
- package/src/modules/harvests.ts +43 -29
- package/src/modules/index.ts +2 -1
- package/src/modules/pragma.ts +23 -8
- package/src/modules/pricer-avnu-api.ts +114 -0
- package/src/modules/pricer-from-api.ts +156 -15
- package/src/modules/pricer-lst.ts +1 -1
- package/src/modules/pricer.ts +94 -40
- package/src/modules/pricerBase.ts +2 -1
- package/src/node/deployer.ts +36 -1
- package/src/node/pricer-redis.ts +3 -1
- package/src/strategies/base-strategy.ts +168 -16
- package/src/strategies/constants.ts +8 -3
- package/src/strategies/ekubo-cl-vault.tsx +1047 -351
- package/src/strategies/factory.ts +199 -0
- package/src/strategies/index.ts +5 -3
- package/src/strategies/registry.ts +262 -0
- package/src/strategies/sensei.ts +353 -9
- package/src/strategies/svk-strategy.ts +283 -31
- package/src/strategies/token-boosted-xstrk-carry-strategy.tsx +1262 -0
- package/src/strategies/types.ts +4 -0
- package/src/strategies/universal-adapters/adapter-utils.ts +4 -1
- package/src/strategies/universal-adapters/avnu-adapter.ts +196 -272
- package/src/strategies/universal-adapters/baseAdapter.ts +263 -251
- package/src/strategies/universal-adapters/common-adapter.ts +206 -203
- package/src/strategies/universal-adapters/index.ts +10 -8
- package/src/strategies/universal-adapters/svk-troves-adapter.ts +511 -0
- package/src/strategies/universal-adapters/token-transfer-adapter.ts +200 -0
- package/src/strategies/universal-adapters/vesu-adapter.ts +120 -82
- package/src/strategies/universal-adapters/vesu-modify-position-adapter.ts +525 -0
- package/src/strategies/universal-adapters/vesu-multiply-adapter.ts +1098 -712
- package/src/strategies/universal-adapters/vesu-position-common.ts +258 -0
- package/src/strategies/universal-adapters/vesu-supply-only-adapter.ts +18 -3
- package/src/strategies/universal-lst-muliplier-strategy.tsx +631 -414
- package/src/strategies/universal-strategy.tsx +1331 -1173
- package/src/strategies/vesu-rebalance.tsx +252 -152
- package/src/strategies/yoloVault.ts +1087 -0
- package/src/utils/cacheClass.ts +11 -2
- package/src/utils/health-factor-math.ts +33 -1
- package/src/utils/index.ts +3 -1
- package/src/utils/logger.browser.ts +22 -4
- package/src/utils/logger.node.ts +259 -24
- package/src/utils/starknet-call-parser.ts +1036 -0
- package/src/utils/strategy-utils.ts +61 -0
- package/src/modules/ExtendedWrapperSDk/index.ts +0 -62
- package/src/modules/ExtendedWrapperSDk/types.ts +0 -311
- package/src/modules/ExtendedWrapperSDk/wrapper.ts +0 -395
- package/src/strategies/universal-adapters/extended-adapter.ts +0 -661
- package/src/strategies/universal-adapters/unused-balance-adapter.ts +0 -109
- package/src/strategies/vesu-extended-strategy/services/operationService.ts +0 -34
- package/src/strategies/vesu-extended-strategy/utils/config.runtime.ts +0 -77
- package/src/strategies/vesu-extended-strategy/utils/constants.ts +0 -49
- package/src/strategies/vesu-extended-strategy/utils/helper.ts +0 -372
- package/src/strategies/vesu-extended-strategy/vesu-extended-strategy.tsx +0 -1140
package/src/modules/pricer.ts
CHANGED
|
@@ -5,12 +5,25 @@ import { IConfig } from "@/interfaces/common";
|
|
|
5
5
|
import { Web3Number } from "@/dataTypes";
|
|
6
6
|
import { PricerBase } from "./pricerBase";
|
|
7
7
|
import { logger } from "@/utils/logger";
|
|
8
|
+
import { AvnuWrapper } from "./avnu";
|
|
9
|
+
import { BlockIdentifier } from "starknet";
|
|
10
|
+
import { PricerAvnuApi } from "./pricer-avnu-api";
|
|
8
11
|
|
|
9
12
|
export interface PriceInfo {
|
|
10
13
|
price: number,
|
|
11
14
|
timestamp: Date
|
|
12
15
|
}
|
|
13
16
|
|
|
17
|
+
type PriceMethod = 'AvnuApi' | 'Coinbase' | 'Coinmarketcap' | 'Ekubo' | 'Avnu';
|
|
18
|
+
|
|
19
|
+
const PRICE_METHOD_PRIORITY: PriceMethod[] = [
|
|
20
|
+
'AvnuApi',
|
|
21
|
+
'Coinbase',
|
|
22
|
+
'Coinmarketcap',
|
|
23
|
+
'Ekubo',
|
|
24
|
+
'Avnu',
|
|
25
|
+
];
|
|
26
|
+
|
|
14
27
|
export class Pricer extends PricerBase {
|
|
15
28
|
protected prices: {
|
|
16
29
|
[key: string]: PriceInfo
|
|
@@ -19,20 +32,24 @@ export class Pricer extends PricerBase {
|
|
|
19
32
|
refreshInterval = 30000;
|
|
20
33
|
staleTime = 60000;
|
|
21
34
|
|
|
35
|
+
protected readonly avnuApiPricer: PricerAvnuApi;
|
|
36
|
+
|
|
22
37
|
// code populates this map during runtime to determine which method to use for a given token
|
|
23
38
|
// The method set will be the first one to try after first attempt
|
|
24
|
-
protected methodToUse: {[tokenSymbol: string]:
|
|
39
|
+
protected methodToUse: {[tokenSymbol: string]: PriceMethod} = {};
|
|
25
40
|
|
|
26
41
|
/**
|
|
27
42
|
* TOKENA and TOKENB are the two token names to get price of TokenA in terms of TokenB
|
|
28
43
|
*/
|
|
44
|
+
// ! switch to USDC (new) later
|
|
29
45
|
protected PRICE_API = `https://api.coinbase.com/v2/prices/{{PRICER_KEY}}/buy`;
|
|
30
|
-
protected EKUBO_API = 'https://
|
|
46
|
+
protected EKUBO_API = 'https://prod-api-quoter.ekubo.org/23448594291968334/{{AMOUNT}}/{{TOKEN_ADDRESS}}/0x033068F6539f8e6e6b131e6B2B814e6c34A5224bC66947c47DaB9dFeE93b35fb'; // e.g. ETH/USDC
|
|
31
47
|
|
|
32
48
|
constructor(config: IConfig, tokens: TokenInfo[], refreshInterval = 30000, staleTime = 60000) {
|
|
33
49
|
super(config, tokens);
|
|
34
50
|
this.refreshInterval = refreshInterval;
|
|
35
51
|
this.staleTime = staleTime;
|
|
52
|
+
this.avnuApiPricer = new PricerAvnuApi(config, tokens);
|
|
36
53
|
}
|
|
37
54
|
|
|
38
55
|
isReady() {
|
|
@@ -66,6 +83,7 @@ export class Pricer extends PricerBase {
|
|
|
66
83
|
}
|
|
67
84
|
|
|
68
85
|
start() {
|
|
86
|
+
this.avnuApiPricer.start();
|
|
69
87
|
this._loadPrices();
|
|
70
88
|
setInterval(() => {
|
|
71
89
|
this._loadPrices();
|
|
@@ -81,7 +99,7 @@ export class Pricer extends PricerBase {
|
|
|
81
99
|
Global.assert(!this.isStale(timestamp, tokenName), `Price of ${tokenName} is stale`);
|
|
82
100
|
|
|
83
101
|
}
|
|
84
|
-
async getPrice(tokenSymbol: string) {
|
|
102
|
+
async getPrice(tokenSymbol: string, blockNumber?: BlockIdentifier) {
|
|
85
103
|
Global.assert(this.prices[tokenSymbol], `Price of ${tokenSymbol} not found`);
|
|
86
104
|
this.assertNotStale(this.prices[tokenSymbol].timestamp, tokenSymbol);
|
|
87
105
|
return this.prices[tokenSymbol];
|
|
@@ -93,7 +111,10 @@ export class Pricer extends PricerBase {
|
|
|
93
111
|
let retry = 0;
|
|
94
112
|
while (retry < MAX_RETRIES) {
|
|
95
113
|
try {
|
|
96
|
-
if (token.
|
|
114
|
+
if (token.dontPrice) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (token.symbol === 'USDT' || token.symbol === 'USDC') {
|
|
97
118
|
this.prices[token.symbol] = {
|
|
98
119
|
price: 1,
|
|
99
120
|
timestamp: new Date()
|
|
@@ -142,49 +163,58 @@ export class Pricer extends PricerBase {
|
|
|
142
163
|
}
|
|
143
164
|
}
|
|
144
165
|
|
|
145
|
-
async _getPrice(token: TokenInfo
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
// console.warn(`Coinbase: price err: message [${token.symbol}]: `, error.message);
|
|
156
|
-
// // do nothing, try next
|
|
157
|
-
// }
|
|
158
|
-
case 'Coinmarketcap':
|
|
159
|
-
try {
|
|
160
|
-
const result = await this._getPriceCoinMarketCap(token);
|
|
161
|
-
this.methodToUse[token.symbol] = 'Coinmarketcap';
|
|
162
|
-
return result;
|
|
163
|
-
} catch (error: any) {
|
|
164
|
-
console.warn(`CoinMarketCap: price err [${token.symbol}]: `, Object.keys(error));
|
|
165
|
-
console.warn(`CoinMarketCap: price err [${token.symbol}]: `, error.message);
|
|
166
|
-
}
|
|
167
|
-
case 'Ekubo':
|
|
168
|
-
try {
|
|
169
|
-
const result = await this._getPriceEkubo(token, new Web3Number(token.priceCheckAmount ? token.priceCheckAmount : 1, token.decimals));
|
|
170
|
-
this.methodToUse[token.symbol] = 'Ekubo';
|
|
171
|
-
return result;
|
|
172
|
-
} catch (error: any) {
|
|
173
|
-
console.warn(`Ekubo: price err [${token.symbol}]: `, error.message);
|
|
174
|
-
console.warn(`Ekubo: price err [${token.symbol}]: `, Object.keys(error));
|
|
175
|
-
// do nothing, try next
|
|
176
|
-
}
|
|
166
|
+
async _getPrice(token: TokenInfo): Promise<number> {
|
|
167
|
+
const pinned = this.methodToUse[token.symbol];
|
|
168
|
+
if (pinned) {
|
|
169
|
+
logger.verbose(`Fetching price of ${token.symbol} using pinned ${pinned}`);
|
|
170
|
+
try {
|
|
171
|
+
return await this._tryPriceMethod(token, pinned);
|
|
172
|
+
} catch (error: any) {
|
|
173
|
+
console.warn(`${pinned}: pinned price failed [${token.symbol}]: `, error.message);
|
|
174
|
+
delete this.methodToUse[token.symbol];
|
|
175
|
+
}
|
|
177
176
|
}
|
|
178
177
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
178
|
+
for (const method of PRICE_METHOD_PRIORITY) {
|
|
179
|
+
logger.verbose(`Fetching price of ${token.symbol} using ${method}`);
|
|
180
|
+
try {
|
|
181
|
+
const result = await this._tryPriceMethod(token, method);
|
|
182
|
+
this.methodToUse[token.symbol] = method;
|
|
183
|
+
return result;
|
|
184
|
+
} catch (error: any) {
|
|
185
|
+
console.warn(`${method}: price err [${token.symbol}]: `, error.message);
|
|
186
|
+
}
|
|
183
187
|
}
|
|
184
188
|
|
|
185
189
|
throw new FatalError(`Price not found for ${token.symbol}`);
|
|
186
190
|
}
|
|
187
191
|
|
|
192
|
+
protected async _tryPriceMethod(token: TokenInfo, method: PriceMethod): Promise<number> {
|
|
193
|
+
switch (method) {
|
|
194
|
+
case 'AvnuApi':
|
|
195
|
+
return await this._getPriceAvnuApi(token);
|
|
196
|
+
case 'Coinbase':
|
|
197
|
+
return await this._getPriceCoinbase(token);
|
|
198
|
+
case 'Coinmarketcap':
|
|
199
|
+
return await this._getPriceCoinMarketCap(token);
|
|
200
|
+
case 'Ekubo':
|
|
201
|
+
return await this._getPriceEkubo(
|
|
202
|
+
token,
|
|
203
|
+
new Web3Number(token.priceCheckAmount ? token.priceCheckAmount : 1, token.decimals),
|
|
204
|
+
);
|
|
205
|
+
case 'Avnu':
|
|
206
|
+
return await this._getAvnuPrice(
|
|
207
|
+
token,
|
|
208
|
+
new Web3Number(token.priceCheckAmount ? token.priceCheckAmount : 1, token.decimals),
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async _getPriceAvnuApi(token: TokenInfo): Promise<number> {
|
|
214
|
+
const priceInfo = await this.avnuApiPricer.getPrice(token.symbol);
|
|
215
|
+
return priceInfo.price;
|
|
216
|
+
}
|
|
217
|
+
|
|
188
218
|
async _getPriceCoinbase(token: TokenInfo) {
|
|
189
219
|
const url = this.PRICE_API.replace("{{PRICER_KEY}}", `${token.symbol}-USD`);
|
|
190
220
|
const result = await axios.get(url)
|
|
@@ -200,7 +230,31 @@ export class Pricer extends PricerBase {
|
|
|
200
230
|
throw new Error("Not implemented");
|
|
201
231
|
}
|
|
202
232
|
|
|
233
|
+
async _getAvnuPrice(token: TokenInfo, amountIn = new Web3Number(1, token.decimals), retry = 0): Promise<number> {
|
|
234
|
+
logger.verbose(`Getting price of ${token.symbol} using Ekubo, amountIn: ${amountIn.toWei()}`);
|
|
235
|
+
|
|
236
|
+
const avnuWrapper = new AvnuWrapper();
|
|
237
|
+
const usdcAddress = '0x033068F6539f8e6e6b131e6B2B814e6c34A5224bC66947c47DaB9dFeE93b35fb';
|
|
238
|
+
const quote = await avnuWrapper.getQuotes(token.address.toString(), usdcAddress, amountIn.toWei(), '0x1');
|
|
239
|
+
const multiplier = 1 / amountIn.toNumber();
|
|
240
|
+
const outputUSDC = Number(Web3Number.fromWei(quote.buyAmount.toString(), 6).toFixed(6)) * multiplier;
|
|
241
|
+
logger.verbose(`Avnu: ${token.symbol} -> USDC: ${outputUSDC}, retry: ${retry}`);
|
|
242
|
+
if (outputUSDC === 0 && retry < 3) {
|
|
243
|
+
// try again with a higher amount
|
|
244
|
+
const amountIn = new Web3Number(100, token.decimals); // 100 unit of token
|
|
245
|
+
return await this._getAvnuPrice(token, amountIn, retry + 1);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// if usdc depegs, it will not longer be 1 USD
|
|
249
|
+
// so we need to get the price of USDC in USD
|
|
250
|
+
// and then convert the outputUSDC to USD
|
|
251
|
+
const usdcPrice = 1; // (await this.getPrice('USDC')).price;
|
|
252
|
+
logger.verbose(`USDC Price: ${usdcPrice}`);
|
|
253
|
+
return outputUSDC * usdcPrice;
|
|
254
|
+
}
|
|
255
|
+
|
|
203
256
|
async _getPriceEkubo(token: TokenInfo, amountIn = new Web3Number(1, token.decimals), retry = 0): Promise<number> {
|
|
257
|
+
logger.verbose(`Getting price of ${token.symbol} using Ekubo, amountIn: ${amountIn.toWei()}`);
|
|
204
258
|
const url = this.EKUBO_API.replace("{{TOKEN_ADDRESS}}", token.address.toString()).replace("{{AMOUNT}}", amountIn.toWei());
|
|
205
259
|
const result = await axios.get(url);
|
|
206
260
|
const data: any = result.data;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { IConfig, TokenInfo } from "@/interfaces";
|
|
2
2
|
import { PriceInfo } from "./pricer";
|
|
3
|
+
import { BlockIdentifier } from "starknet";
|
|
3
4
|
|
|
4
5
|
export abstract class PricerBase {
|
|
5
6
|
readonly config: IConfig;
|
|
@@ -9,7 +10,7 @@ export abstract class PricerBase {
|
|
|
9
10
|
this.tokens = tokens;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
|
-
async getPrice(tokenSymbol: string): Promise<PriceInfo> {
|
|
13
|
+
async getPrice(tokenSymbol: string, blockNumber?: BlockIdentifier): Promise<PriceInfo> {
|
|
13
14
|
throw new Error('Method not implemented');
|
|
14
15
|
}
|
|
15
16
|
}
|
package/src/node/deployer.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { readFileSync, existsSync, writeFileSync } from 'fs'
|
|
|
4
4
|
import { IConfig } from '../interfaces';
|
|
5
5
|
import { Store, getDefaultStoreConfig } from '../utils/store';
|
|
6
6
|
import { add } from 'winston';
|
|
7
|
+
import { logger } from '@/utils';
|
|
7
8
|
|
|
8
9
|
function getContracts() {
|
|
9
10
|
const PATH = './contracts.json'
|
|
@@ -207,13 +208,47 @@ async function executeTransactions(
|
|
|
207
208
|
return tx;
|
|
208
209
|
}
|
|
209
210
|
|
|
211
|
+
async function myWaitForTransaction(
|
|
212
|
+
transaction_hash: string,
|
|
213
|
+
provider: RpcProvider,
|
|
214
|
+
retry = 0
|
|
215
|
+
) {
|
|
216
|
+
const MAX_RETRIES = 60;
|
|
217
|
+
logger.verbose(`Waiting for transaction: ${transaction_hash}, retry: ${retry}`);
|
|
218
|
+
try {
|
|
219
|
+
const status = await provider.getTransactionStatus(transaction_hash);
|
|
220
|
+
logger.verbose(`Transaction status: ${JSON.stringify(status.execution_status)}`);
|
|
221
|
+
if (status.execution_status == TransactionExecutionStatus.SUCCEEDED) {
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
if (status.execution_status == TransactionExecutionStatus.REVERTED) {
|
|
225
|
+
throw new Error(`Transaction reverted: ${transaction_hash}`);
|
|
226
|
+
}
|
|
227
|
+
if (retry > MAX_RETRIES) {
|
|
228
|
+
throw new Error(`Transaction not found: ${transaction_hash}`);
|
|
229
|
+
}
|
|
230
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
231
|
+
return myWaitForTransaction(transaction_hash, provider, retry + 1);
|
|
232
|
+
} catch (error) {
|
|
233
|
+
if (error instanceof Error && error.message.includes('Transaction reverted')) {
|
|
234
|
+
throw new Error(`Transaction reverted: ${transaction_hash}`);
|
|
235
|
+
}
|
|
236
|
+
if (retry > MAX_RETRIES) {
|
|
237
|
+
throw error;
|
|
238
|
+
}
|
|
239
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
240
|
+
return myWaitForTransaction(transaction_hash, provider, retry + 1);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
210
244
|
const Deployer = {
|
|
211
245
|
getAccount,
|
|
212
246
|
myDeclare,
|
|
213
247
|
deployContract,
|
|
214
248
|
prepareMultiDeployContracts,
|
|
215
249
|
executeDeployCalls,
|
|
216
|
-
executeTransactions
|
|
250
|
+
executeTransactions,
|
|
251
|
+
myWaitForTransaction
|
|
217
252
|
}
|
|
218
253
|
|
|
219
254
|
export default Deployer;
|
package/src/node/pricer-redis.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { PriceInfo, Pricer } from '@/modules/pricer';
|
|
|
4
4
|
import { createClient } from 'redis';
|
|
5
5
|
import type { RedisClientType } from 'redis'
|
|
6
6
|
import { logger } from "@/utils/logger";
|
|
7
|
+
import { BlockIdentifier } from "starknet";
|
|
7
8
|
|
|
8
9
|
export class PricerRedis extends Pricer {
|
|
9
10
|
private redisClient: RedisClientType | null = null;
|
|
@@ -17,6 +18,7 @@ export class PricerRedis extends Pricer {
|
|
|
17
18
|
await this.initRedis(redisUrl);
|
|
18
19
|
|
|
19
20
|
logger.info(`Starting Pricer with Redis`);
|
|
21
|
+
this.avnuApiPricer.start();
|
|
20
22
|
this._loadPrices(this._setRedisPrices.bind(this));
|
|
21
23
|
setInterval(() => {
|
|
22
24
|
this._loadPrices(this._setRedisPrices.bind(this));
|
|
@@ -51,7 +53,7 @@ export class PricerRedis extends Pricer {
|
|
|
51
53
|
}
|
|
52
54
|
|
|
53
55
|
/** Returns price from redis */
|
|
54
|
-
async getPrice(tokenSymbol: string) {
|
|
56
|
+
async getPrice(tokenSymbol: string, blockNumber?: BlockIdentifier) {
|
|
55
57
|
const STALE_TIME = 60000;
|
|
56
58
|
if (!this.redisClient) {
|
|
57
59
|
throw new FatalError(`Redis client not initialised`);
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { ContractAddr, Web3Number } from "@/dataTypes";
|
|
2
2
|
import { IConfig, TokenInfo, VaultPosition } from "@/interfaces";
|
|
3
3
|
import { CacheClass } from "@/utils/cacheClass";
|
|
4
|
-
import { Call } from "starknet";
|
|
5
4
|
import { PositionInfo } from "./universal-adapters";
|
|
5
|
+
import { Call, BlockIdentifier } from "starknet";
|
|
6
|
+
import { HarvestInfo } from "@/modules/harvests";
|
|
6
7
|
|
|
7
8
|
export interface SingleActionAmount {
|
|
8
9
|
tokenInfo: TokenInfo,
|
|
@@ -28,33 +29,87 @@ export interface DualTokenInfo {
|
|
|
28
29
|
token1: SingleTokenInfo
|
|
29
30
|
}
|
|
30
31
|
|
|
32
|
+
export type StrategyInputMode = "single" | "dual";
|
|
33
|
+
export type InputModeFromAction<T> = T extends DualActionAmount ? "dual" : "single";
|
|
34
|
+
|
|
35
|
+
export interface NetAPYSplit {
|
|
36
|
+
apy: number;
|
|
37
|
+
id: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface NetAPYDetails {
|
|
41
|
+
net: number;
|
|
42
|
+
splits: NetAPYSplit[];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type UserPositionCardSubValueColor = "default" | "positive" | "negative" | "info";
|
|
46
|
+
|
|
47
|
+
export interface UserPositionCard {
|
|
48
|
+
title: string;
|
|
49
|
+
value: string;
|
|
50
|
+
tooltip?: string;
|
|
51
|
+
subValue?: string;
|
|
52
|
+
subValueColor?: UserPositionCardSubValueColor;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface UserPositionCardsInput {
|
|
56
|
+
user: ContractAddr;
|
|
57
|
+
investmentFlows?: Array<{ amount: string; type: string; timestamp: number; tx_hash: string }>;
|
|
58
|
+
usualTimeToEarnings?: string | null;
|
|
59
|
+
usualTimeToEarningsDescription?: string | null;
|
|
60
|
+
}
|
|
61
|
+
|
|
31
62
|
interface CacheData {
|
|
32
63
|
timestamp: number;
|
|
33
64
|
ttl: number;
|
|
34
65
|
data: any;
|
|
35
66
|
}
|
|
36
|
-
export class BaseStrategy<
|
|
37
|
-
|
|
38
|
-
|
|
67
|
+
export class BaseStrategy<
|
|
68
|
+
TVLInfo,
|
|
69
|
+
DepositActionInfo,
|
|
70
|
+
WithdrawActionInfo = DepositActionInfo,
|
|
71
|
+
> extends CacheClass {
|
|
72
|
+
readonly config: IConfig;
|
|
73
|
+
readonly cache: Map<string, CacheData> = new Map();
|
|
74
|
+
private readonly _depositInputMode: InputModeFromAction<DepositActionInfo>;
|
|
75
|
+
private readonly _withdrawInputMode: InputModeFromAction<WithdrawActionInfo>;
|
|
39
76
|
|
|
40
|
-
constructor(
|
|
77
|
+
constructor(
|
|
78
|
+
config: IConfig,
|
|
79
|
+
inputModes?: {
|
|
80
|
+
depositInputMode?: InputModeFromAction<DepositActionInfo>;
|
|
81
|
+
withdrawInputMode?: InputModeFromAction<WithdrawActionInfo>;
|
|
82
|
+
}
|
|
83
|
+
) {
|
|
41
84
|
super();
|
|
42
85
|
this.config = config;
|
|
86
|
+
this._depositInputMode = (inputModes?.depositInputMode ??
|
|
87
|
+
("single" as InputModeFromAction<DepositActionInfo>));
|
|
88
|
+
this._withdrawInputMode = (inputModes?.withdrawInputMode ??
|
|
89
|
+
("single" as InputModeFromAction<WithdrawActionInfo>));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
depositInputMode(): InputModeFromAction<DepositActionInfo> {
|
|
93
|
+
return this._depositInputMode;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
withdrawInputMode(): InputModeFromAction<WithdrawActionInfo> {
|
|
97
|
+
return this._withdrawInputMode;
|
|
43
98
|
}
|
|
44
99
|
|
|
45
|
-
async getUserTVL(user: ContractAddr): Promise<TVLInfo> {
|
|
100
|
+
async getUserTVL(user: ContractAddr, blockIdentifier?: BlockIdentifier): Promise<TVLInfo> {
|
|
46
101
|
throw new Error("Not implemented");
|
|
47
102
|
}
|
|
48
103
|
|
|
49
104
|
async getTVL(): Promise<TVLInfo> {
|
|
50
105
|
throw new Error("Not implemented");
|
|
51
|
-
}
|
|
106
|
+
}
|
|
52
107
|
|
|
53
|
-
async depositCall(amountInfo:
|
|
108
|
+
async depositCall(amountInfo: DepositActionInfo, receiver: ContractAddr): Promise<Call[]> {
|
|
54
109
|
throw new Error("Not implemented");
|
|
55
110
|
}
|
|
56
111
|
|
|
57
|
-
async withdrawCall(amountInfo:
|
|
112
|
+
async withdrawCall(amountInfo: WithdrawActionInfo, receiver: ContractAddr, owner: ContractAddr): Promise<Call[]> {
|
|
58
113
|
throw new Error("Not implemented");
|
|
59
114
|
}
|
|
60
115
|
|
|
@@ -62,19 +117,116 @@ export class BaseStrategy<TVLInfo, ActionInfo> extends CacheClass {
|
|
|
62
117
|
throw new Error("Not implemented");
|
|
63
118
|
}
|
|
64
119
|
|
|
65
|
-
async
|
|
120
|
+
async netAPY(
|
|
121
|
+
blockIdentifier?: BlockIdentifier,
|
|
122
|
+
sinceBlocks?: number,
|
|
123
|
+
timeperiod?: "24h" | "7d" | "30d" | "3m"
|
|
124
|
+
): Promise<number | string | NetAPYDetails> {
|
|
66
125
|
throw new Error("Not implemented");
|
|
67
126
|
}
|
|
68
127
|
|
|
69
|
-
async
|
|
70
|
-
|
|
128
|
+
async getPendingRewards(): Promise<HarvestInfo[]> {
|
|
129
|
+
return [];
|
|
71
130
|
}
|
|
72
131
|
|
|
73
|
-
async
|
|
132
|
+
async getUserRealizedAPY(
|
|
133
|
+
blockIdentifier?: BlockIdentifier,
|
|
134
|
+
sinceBlocks?: number
|
|
135
|
+
): Promise<number> {
|
|
74
136
|
throw new Error("Not implemented");
|
|
75
137
|
}
|
|
76
138
|
|
|
77
|
-
async
|
|
78
|
-
|
|
139
|
+
async getUserPositionCards(_input: UserPositionCardsInput): Promise<UserPositionCard[]> {
|
|
140
|
+
throw new Error("Not implemented");
|
|
79
141
|
}
|
|
80
|
-
|
|
142
|
+
|
|
143
|
+
async getMaxTVL() : Promise<Web3Number> {
|
|
144
|
+
// Can throw an error as well if needed, RN returning 0
|
|
145
|
+
return new Web3Number('0', 18)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
protected formatTokenAmountForCard(amount: Web3Number, tokenInfo: TokenInfo): string {
|
|
149
|
+
const displayDecimals = tokenInfo.displayDecimals ?? 2;
|
|
150
|
+
const fixed = Number(amount.toFixed(displayDecimals));
|
|
151
|
+
const normalized = Number.isFinite(fixed) ? fixed : 0;
|
|
152
|
+
return `${normalized.toLocaleString("en-US", {
|
|
153
|
+
maximumFractionDigits: displayDecimals,
|
|
154
|
+
minimumFractionDigits: 0,
|
|
155
|
+
})} ${tokenInfo.symbol}`;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
protected formatPercentForCard(value: number): string {
|
|
159
|
+
if (!Number.isFinite(value)) return "N/A";
|
|
160
|
+
return `${(value * 100).toFixed(2)}%`;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
protected formatUSDForCard(value: number): string {
|
|
164
|
+
if (!Number.isFinite(value)) return "$0.00";
|
|
165
|
+
return new Intl.NumberFormat("en-US", {
|
|
166
|
+
style: "currency",
|
|
167
|
+
currency: "USD",
|
|
168
|
+
maximumFractionDigits: 2,
|
|
169
|
+
}).format(value);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
protected getSubValueColorFromSignedNumber(value: number): UserPositionCardSubValueColor {
|
|
173
|
+
if (!Number.isFinite(value)) return "default";
|
|
174
|
+
if (value > 0) return "positive";
|
|
175
|
+
if (value < 0) return "negative";
|
|
176
|
+
return "default";
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Calculate lifetime earnings for a user based on provided data from client
|
|
181
|
+
* Formula: lifetimeEarnings = currentValue + totalWithdrawals - totalDeposits
|
|
182
|
+
*
|
|
183
|
+
* @param userTVL - The user's current TVL (SingleTokenInfo with amount, usdValue, tokenInfo)
|
|
184
|
+
* @param investmentFlows - Array of investment flow transactions from client
|
|
185
|
+
* @returns Object containing lifetime earnings, current value, and total deposits/withdrawals
|
|
186
|
+
*/
|
|
187
|
+
getLifetimeEarnings(
|
|
188
|
+
userTVL: SingleTokenInfo,
|
|
189
|
+
investmentFlows: Array<{ amount: string; type: string; timestamp: number; tx_hash: string }>
|
|
190
|
+
): {
|
|
191
|
+
tokenInfo: SingleTokenInfo;
|
|
192
|
+
lifetimeEarnings: Web3Number;
|
|
193
|
+
currentValue: Web3Number;
|
|
194
|
+
totalDeposits: Web3Number;
|
|
195
|
+
totalWithdrawals: Web3Number;
|
|
196
|
+
} {
|
|
197
|
+
// Get token decimals from userTVL
|
|
198
|
+
const tokenDecimals = userTVL.tokenInfo.decimals;
|
|
199
|
+
|
|
200
|
+
// Initialize totals
|
|
201
|
+
let totalDeposits = Web3Number.fromWei("0", tokenDecimals);
|
|
202
|
+
let totalWithdrawals = Web3Number.fromWei("0", tokenDecimals);
|
|
203
|
+
|
|
204
|
+
// Process investment flows
|
|
205
|
+
for (const flow of investmentFlows) {
|
|
206
|
+
const amount = Web3Number.fromWei(flow.amount, tokenDecimals);
|
|
207
|
+
|
|
208
|
+
if (flow.type === 'deposit') {
|
|
209
|
+
totalDeposits = totalDeposits.plus(amount);
|
|
210
|
+
} else if (flow.type === 'withdraw' || flow.type === 'redeem') {
|
|
211
|
+
totalWithdrawals = totalWithdrawals.plus(amount);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Calculate lifetime earnings: current value + withdrawals - deposits
|
|
216
|
+
const lifetimeEarnings = userTVL.amount
|
|
217
|
+
.plus(totalWithdrawals)
|
|
218
|
+
.minus(totalDeposits);
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
tokenInfo: {
|
|
222
|
+
tokenInfo: userTVL.tokenInfo,
|
|
223
|
+
amount: lifetimeEarnings,
|
|
224
|
+
usdValue: 0, // Lifetime earnings are not converted to USD
|
|
225
|
+
},
|
|
226
|
+
lifetimeEarnings,
|
|
227
|
+
currentValue: userTVL.amount,
|
|
228
|
+
totalDeposits,
|
|
229
|
+
totalWithdrawals,
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
}
|
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import { ContractAddr } from "@/dataTypes";
|
|
2
2
|
|
|
3
|
-
export const
|
|
3
|
+
export const MY_ACCESS_CONTROL = {
|
|
4
4
|
address: ContractAddr.from("0x0636a3f51cc37f5729e4da4b1de6a8549a28f3c0d5bf3b17f150971e451ff9c2"),
|
|
5
5
|
name: "Access Controller",
|
|
6
6
|
sourceCodeUrl: "https://github.com/strkfarm/strkfarm-contracts/blob/main/src/components/accessControl.cairo",
|
|
7
|
-
}
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export const COMMON_CONTRACTS = [MY_ACCESS_CONTROL];
|
|
8
10
|
|
|
9
11
|
export const ENDPOINTS = {
|
|
10
12
|
VESU_BASE: "https://proxy.api.troves.fi/vesu",
|
|
11
13
|
VESU_BASE_STAGING: "https://proxy.api.troves.fi/vesu-staging"
|
|
12
|
-
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
export const MAX_AVNU_RETRY_DELAY = Number(process.env.MAX_AVNU_RETRY_DELAY ?? 100);
|