@strkfarm/sdk 2.0.0-staging.7 → 2.0.0-staging.70
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/index.browser.global.js +3117 -1030
- package/dist/index.browser.mjs +2806 -711
- package/dist/index.d.ts +345 -50
- package/dist/index.js +2901 -794
- package/dist/index.mjs +2809 -711
- package/package.json +4 -4
- package/src/data/universal-vault.abi.json +143 -27
- package/src/dataTypes/_bignumber.ts +5 -0
- package/src/dataTypes/bignumber.browser.ts +5 -0
- package/src/dataTypes/bignumber.node.ts +5 -0
- package/src/global.ts +53 -1
- package/src/interfaces/common.tsx +77 -27
- package/src/modules/avnu.ts +1 -1
- package/src/modules/erc20.ts +18 -2
- package/src/modules/index.ts +3 -1
- package/src/modules/pricer-avnu-api.ts +114 -0
- package/src/modules/pricer.ts +63 -45
- package/src/node/pricer-redis.ts +1 -0
- package/src/strategies/base-strategy.ts +153 -8
- package/src/strategies/constants.ts +2 -2
- package/src/strategies/ekubo-cl-vault.tsx +254 -91
- package/src/strategies/factory.ts +21 -1
- package/src/strategies/index.ts +2 -0
- package/src/strategies/registry.ts +15 -30
- package/src/strategies/sensei.ts +52 -13
- package/src/strategies/types.ts +4 -0
- package/src/strategies/universal-adapters/vesu-adapter.ts +46 -25
- package/src/strategies/universal-lst-muliplier-strategy.tsx +1461 -584
- package/src/strategies/universal-strategy.tsx +157 -81
- package/src/strategies/vesu-rebalance.tsx +22 -12
- package/src/strategies/yoloVault.ts +1081 -0
- package/src/utils/strategy-utils.ts +6 -2
package/src/modules/erc20.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { ContractAddr, Web3Number } from "@/dataTypes";
|
|
|
2
2
|
import { IConfig } from "@/interfaces";
|
|
3
3
|
import { Contract } from "starknet";
|
|
4
4
|
import ERC20Abi from '@/data/erc20.abi.json';
|
|
5
|
-
|
|
5
|
+
import { uint256 } from "starknet";
|
|
6
6
|
export class ERC20 {
|
|
7
7
|
readonly config: IConfig;
|
|
8
8
|
|
|
@@ -12,7 +12,7 @@ export class ERC20 {
|
|
|
12
12
|
|
|
13
13
|
contract(addr: string | ContractAddr) {
|
|
14
14
|
const _addr = typeof addr === 'string' ? addr : addr.address;
|
|
15
|
-
return new Contract({abi: ERC20Abi, address: _addr, providerOrAccount: this.config.provider});
|
|
15
|
+
return new Contract({ abi: ERC20Abi, address: _addr, providerOrAccount: this.config.provider });
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
async balanceOf(token: string | ContractAddr, address: string | ContractAddr, tokenDecimals: number) {
|
|
@@ -26,4 +26,20 @@ export class ERC20 {
|
|
|
26
26
|
const allowance = await contract.call('allowance', [owner.toString(), spender.toString()]);
|
|
27
27
|
return Web3Number.fromWei(allowance.toString(), tokenDecimals);
|
|
28
28
|
}
|
|
29
|
+
|
|
30
|
+
approve(
|
|
31
|
+
token: string | ContractAddr,
|
|
32
|
+
spender: string | ContractAddr,
|
|
33
|
+
amount: Web3Number
|
|
34
|
+
) {
|
|
35
|
+
const contract = this.contract(token);
|
|
36
|
+
const amountUint256 = uint256.bnToUint256(amount.toWei());
|
|
37
|
+
const approveCall = contract.populate("approve", [
|
|
38
|
+
spender.toString(),
|
|
39
|
+
amountUint256.low.toString(),
|
|
40
|
+
amountUint256.high.toString(),
|
|
41
|
+
]);
|
|
42
|
+
return approveCall;
|
|
43
|
+
}
|
|
44
|
+
|
|
29
45
|
}
|
package/src/modules/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export * from './pricer';
|
|
2
|
+
export * from './pricer-avnu-api';
|
|
2
3
|
export * from './pragma';
|
|
3
4
|
export * from './zkLend';
|
|
4
5
|
export * from './pricer-from-api';
|
|
@@ -6,4 +7,5 @@ export * from './erc20';
|
|
|
6
7
|
export * from './avnu';
|
|
7
8
|
export * from './ekubo-quoter';
|
|
8
9
|
export * from './pricer-lst';
|
|
9
|
-
export * from './lst-apr';
|
|
10
|
+
export * from './lst-apr';
|
|
11
|
+
export * from './ekubo-pricer'
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import { TokenInfo } from "@/interfaces/common";
|
|
3
|
+
import { IConfig } from "@/interfaces/common";
|
|
4
|
+
import { PricerBase } from "./pricerBase";
|
|
5
|
+
import { PriceInfo } from "./pricer";
|
|
6
|
+
import { logger } from "@/utils/logger";
|
|
7
|
+
import { ContractAddr } from "@/dataTypes";
|
|
8
|
+
|
|
9
|
+
const AVNU_TOKENS_API = "https://starknet.impulse.avnu.fi/v3/tokens";
|
|
10
|
+
|
|
11
|
+
interface AvnuTokenApiEntry {
|
|
12
|
+
address: string;
|
|
13
|
+
symbol: string;
|
|
14
|
+
starknet?: {
|
|
15
|
+
usd?: number;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Polls Avnu impulse tokens API and keeps USD prices in memory for configured tokens.
|
|
21
|
+
* Price timestamp is set when each poll request completes.
|
|
22
|
+
*/
|
|
23
|
+
export class PricerAvnuApi extends PricerBase {
|
|
24
|
+
protected prices: { [key: string]: PriceInfo } = {};
|
|
25
|
+
|
|
26
|
+
readonly refreshInterval = 15_000;
|
|
27
|
+
readonly staleTime = 5 * 60 * 1000;
|
|
28
|
+
|
|
29
|
+
private pollTimer: ReturnType<typeof setInterval> | null = null;
|
|
30
|
+
private loading = false;
|
|
31
|
+
|
|
32
|
+
constructor(config: IConfig, tokens: TokenInfo[]) {
|
|
33
|
+
super(config, tokens);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
start() {
|
|
37
|
+
this._loadPrices();
|
|
38
|
+
this.pollTimer = setInterval(() => {
|
|
39
|
+
this._loadPrices();
|
|
40
|
+
}, this.refreshInterval);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
stop() {
|
|
44
|
+
if (this.pollTimer) {
|
|
45
|
+
clearInterval(this.pollTimer);
|
|
46
|
+
this.pollTimer = null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
isStale(timestamp: Date) {
|
|
51
|
+
return Date.now() - timestamp.getTime() > this.staleTime;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
hasPrice(tokenSymbol: string) {
|
|
55
|
+
const info = this.prices[tokenSymbol];
|
|
56
|
+
return !!info && !this.isStale(info.timestamp);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async getPrice(tokenSymbol: string): Promise<PriceInfo> {
|
|
60
|
+
const info = this.prices[tokenSymbol];
|
|
61
|
+
if (!info) {
|
|
62
|
+
throw new Error(`AvnuApi: price of ${tokenSymbol} not found`);
|
|
63
|
+
}
|
|
64
|
+
if (this.isStale(info.timestamp)) {
|
|
65
|
+
throw new Error(`AvnuApi: price of ${tokenSymbol} is stale`);
|
|
66
|
+
}
|
|
67
|
+
return info;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
protected async _loadPrices() {
|
|
71
|
+
if (this.loading) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
this.loading = true;
|
|
75
|
+
const timestamp = new Date();
|
|
76
|
+
try {
|
|
77
|
+
const result = await axios.get<AvnuTokenApiEntry[]>(AVNU_TOKENS_API);
|
|
78
|
+
const priceByAddress = new Map<string, number>();
|
|
79
|
+
for (const entry of result.data) {
|
|
80
|
+
const usd = entry.starknet?.usd;
|
|
81
|
+
if (usd != null && usd > 0) {
|
|
82
|
+
priceByAddress.set(ContractAddr.standardise(entry.address), usd);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
for (const token of this.tokens) {
|
|
87
|
+
if (token.symbol === "USDT" || token.symbol === "USDC") {
|
|
88
|
+
this.prices[token.symbol] = { price: 1, timestamp };
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const targetToken = token.priceProxySymbol
|
|
93
|
+
? this.tokens.find((t) => t.symbol === token.priceProxySymbol)
|
|
94
|
+
: token;
|
|
95
|
+
if (!targetToken) {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const addr = targetToken.address.address;
|
|
100
|
+
const price = priceByAddress.get(addr);
|
|
101
|
+
if (price != null) {
|
|
102
|
+
this.prices[token.symbol] = { price, timestamp };
|
|
103
|
+
logger.verbose(
|
|
104
|
+
`AvnuApi: ${token.symbol} -> $${price}`,
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
} catch (error: any) {
|
|
109
|
+
logger.warn(`AvnuApi: failed to fetch tokens: ${error?.message ?? error}`);
|
|
110
|
+
} finally {
|
|
111
|
+
this.loading = false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
package/src/modules/pricer.ts
CHANGED
|
@@ -7,12 +7,23 @@ import { PricerBase } from "./pricerBase";
|
|
|
7
7
|
import { logger } from "@/utils/logger";
|
|
8
8
|
import { AvnuWrapper } from "./avnu";
|
|
9
9
|
import { BlockIdentifier } from "starknet";
|
|
10
|
+
import { PricerAvnuApi } from "./pricer-avnu-api";
|
|
10
11
|
|
|
11
12
|
export interface PriceInfo {
|
|
12
13
|
price: number,
|
|
13
14
|
timestamp: Date
|
|
14
15
|
}
|
|
15
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
|
+
|
|
16
27
|
export class Pricer extends PricerBase {
|
|
17
28
|
protected prices: {
|
|
18
29
|
[key: string]: PriceInfo
|
|
@@ -21,9 +32,11 @@ export class Pricer extends PricerBase {
|
|
|
21
32
|
refreshInterval = 30000;
|
|
22
33
|
staleTime = 60000;
|
|
23
34
|
|
|
35
|
+
protected readonly avnuApiPricer: PricerAvnuApi;
|
|
36
|
+
|
|
24
37
|
// code populates this map during runtime to determine which method to use for a given token
|
|
25
38
|
// The method set will be the first one to try after first attempt
|
|
26
|
-
protected methodToUse: {[tokenSymbol: string]:
|
|
39
|
+
protected methodToUse: {[tokenSymbol: string]: PriceMethod} = {};
|
|
27
40
|
|
|
28
41
|
/**
|
|
29
42
|
* TOKENA and TOKENB are the two token names to get price of TokenA in terms of TokenB
|
|
@@ -36,6 +49,7 @@ export class Pricer extends PricerBase {
|
|
|
36
49
|
super(config, tokens);
|
|
37
50
|
this.refreshInterval = refreshInterval;
|
|
38
51
|
this.staleTime = staleTime;
|
|
52
|
+
this.avnuApiPricer = new PricerAvnuApi(config, tokens);
|
|
39
53
|
}
|
|
40
54
|
|
|
41
55
|
isReady() {
|
|
@@ -69,6 +83,7 @@ export class Pricer extends PricerBase {
|
|
|
69
83
|
}
|
|
70
84
|
|
|
71
85
|
start() {
|
|
86
|
+
this.avnuApiPricer.start();
|
|
72
87
|
this._loadPrices();
|
|
73
88
|
setInterval(() => {
|
|
74
89
|
this._loadPrices();
|
|
@@ -96,6 +111,9 @@ export class Pricer extends PricerBase {
|
|
|
96
111
|
let retry = 0;
|
|
97
112
|
while (retry < MAX_RETRIES) {
|
|
98
113
|
try {
|
|
114
|
+
if (token.dontPrice) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
99
117
|
if (token.symbol === 'USDT' || token.symbol === 'USDC') {
|
|
100
118
|
this.prices[token.symbol] = {
|
|
101
119
|
price: 1,
|
|
@@ -145,56 +163,56 @@ export class Pricer extends PricerBase {
|
|
|
145
163
|
}
|
|
146
164
|
}
|
|
147
165
|
|
|
148
|
-
async _getPrice(token: TokenInfo
|
|
149
|
-
const
|
|
150
|
-
|
|
151
|
-
|
|
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
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
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
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
throw new FatalError(`Price not found for ${token.symbol}`);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
protected async _tryPriceMethod(token: TokenInfo, method: PriceMethod): Promise<number> {
|
|
193
|
+
switch (method) {
|
|
194
|
+
case 'AvnuApi':
|
|
195
|
+
return await this._getPriceAvnuApi(token);
|
|
152
196
|
case 'Coinbase':
|
|
153
|
-
|
|
154
|
-
// const result = await this._getPriceCoinbase(token);
|
|
155
|
-
// this.methodToUse[token.symbol] = 'Coinbase';
|
|
156
|
-
// return result;
|
|
157
|
-
// } catch (error: any) {
|
|
158
|
-
// console.warn(`Coinbase: price err: message [${token.symbol}]: `, error.message);
|
|
159
|
-
// // do nothing, try next
|
|
160
|
-
// }
|
|
197
|
+
return await this._getPriceCoinbase(token);
|
|
161
198
|
case 'Coinmarketcap':
|
|
162
|
-
|
|
163
|
-
const result = await this._getPriceCoinMarketCap(token);
|
|
164
|
-
this.methodToUse[token.symbol] = 'Coinmarketcap';
|
|
165
|
-
return result;
|
|
166
|
-
} catch (error: any) {
|
|
167
|
-
console.warn(`CoinMarketCap: price err [${token.symbol}]: `, Object.keys(error));
|
|
168
|
-
console.warn(`CoinMarketCap: price err [${token.symbol}]: `, error.message);
|
|
169
|
-
}
|
|
199
|
+
return await this._getPriceCoinMarketCap(token);
|
|
170
200
|
case 'Ekubo':
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
} catch (error: any) {
|
|
176
|
-
console.warn(`Ekubo: price err [${token.symbol}]: `, error.message);
|
|
177
|
-
console.warn(`Ekubo: price err [${token.symbol}]: `, Object.keys(error));
|
|
178
|
-
// do nothing, try next
|
|
179
|
-
}
|
|
201
|
+
return await this._getPriceEkubo(
|
|
202
|
+
token,
|
|
203
|
+
new Web3Number(token.priceCheckAmount ? token.priceCheckAmount : 1, token.decimals),
|
|
204
|
+
);
|
|
180
205
|
case 'Avnu':
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
} catch (error: any) {
|
|
186
|
-
console.warn(`Avnu: price err [${token.symbol}]: `, error.message);
|
|
187
|
-
console.warn(`Avnu: price err [${token.symbol}]: `, Object.keys(error));
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// if methodToUse is the default one, pass Coinbase to try all from start
|
|
192
|
-
if (defaultMethod == 'all') {
|
|
193
|
-
// try again with coinbase
|
|
194
|
-
return await this._getPrice(token, 'Coinbase');
|
|
206
|
+
return await this._getAvnuPrice(
|
|
207
|
+
token,
|
|
208
|
+
new Web3Number(token.priceCheckAmount ? token.priceCheckAmount : 1, token.decimals),
|
|
209
|
+
);
|
|
195
210
|
}
|
|
211
|
+
}
|
|
196
212
|
|
|
197
|
-
|
|
213
|
+
async _getPriceAvnuApi(token: TokenInfo): Promise<number> {
|
|
214
|
+
const priceInfo = await this.avnuApiPricer.getPrice(token.symbol);
|
|
215
|
+
return priceInfo.price;
|
|
198
216
|
}
|
|
199
217
|
|
|
200
218
|
async _getPriceCoinbase(token: TokenInfo) {
|
package/src/node/pricer-redis.ts
CHANGED
|
@@ -18,6 +18,7 @@ export class PricerRedis extends Pricer {
|
|
|
18
18
|
await this.initRedis(redisUrl);
|
|
19
19
|
|
|
20
20
|
logger.info(`Starting Pricer with Redis`);
|
|
21
|
+
this.avnuApiPricer.start();
|
|
21
22
|
this._loadPrices(this._setRedisPrices.bind(this));
|
|
22
23
|
setInterval(() => {
|
|
23
24
|
this._loadPrices(this._setRedisPrices.bind(this));
|
|
@@ -23,6 +23,9 @@ export interface DualTokenInfo {
|
|
|
23
23
|
token1: SingleTokenInfo
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
export type StrategyInputMode = "single" | "dual";
|
|
27
|
+
export type InputModeFromAction<T> = T extends DualActionAmount ? "dual" : "single";
|
|
28
|
+
|
|
26
29
|
export interface NetAPYSplit {
|
|
27
30
|
apy: number;
|
|
28
31
|
id: string;
|
|
@@ -33,18 +36,59 @@ export interface NetAPYDetails {
|
|
|
33
36
|
splits: NetAPYSplit[];
|
|
34
37
|
}
|
|
35
38
|
|
|
39
|
+
export type UserPositionCardSubValueColor = "default" | "positive" | "negative" | "info";
|
|
40
|
+
|
|
41
|
+
export interface UserPositionCard {
|
|
42
|
+
title: string;
|
|
43
|
+
value: string;
|
|
44
|
+
tooltip?: string;
|
|
45
|
+
subValue?: string;
|
|
46
|
+
subValueColor?: UserPositionCardSubValueColor;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface UserPositionCardsInput {
|
|
50
|
+
user: ContractAddr;
|
|
51
|
+
investmentFlows?: Array<{ amount: string; type: string; timestamp: number; tx_hash: string }>;
|
|
52
|
+
usualTimeToEarnings?: string | null;
|
|
53
|
+
usualTimeToEarningsDescription?: string | null;
|
|
54
|
+
}
|
|
55
|
+
|
|
36
56
|
interface CacheData {
|
|
37
57
|
timestamp: number;
|
|
38
58
|
ttl: number;
|
|
39
59
|
data: any;
|
|
40
60
|
}
|
|
41
|
-
export class BaseStrategy<
|
|
42
|
-
|
|
43
|
-
|
|
61
|
+
export class BaseStrategy<
|
|
62
|
+
TVLInfo,
|
|
63
|
+
DepositActionInfo,
|
|
64
|
+
WithdrawActionInfo = DepositActionInfo,
|
|
65
|
+
> extends CacheClass {
|
|
66
|
+
readonly config: IConfig;
|
|
67
|
+
readonly cache: Map<string, CacheData> = new Map();
|
|
68
|
+
private readonly _depositInputMode: InputModeFromAction<DepositActionInfo>;
|
|
69
|
+
private readonly _withdrawInputMode: InputModeFromAction<WithdrawActionInfo>;
|
|
44
70
|
|
|
45
|
-
constructor(
|
|
71
|
+
constructor(
|
|
72
|
+
config: IConfig,
|
|
73
|
+
inputModes?: {
|
|
74
|
+
depositInputMode?: InputModeFromAction<DepositActionInfo>;
|
|
75
|
+
withdrawInputMode?: InputModeFromAction<WithdrawActionInfo>;
|
|
76
|
+
}
|
|
77
|
+
) {
|
|
46
78
|
super();
|
|
47
79
|
this.config = config;
|
|
80
|
+
this._depositInputMode = (inputModes?.depositInputMode ??
|
|
81
|
+
("single" as InputModeFromAction<DepositActionInfo>));
|
|
82
|
+
this._withdrawInputMode = (inputModes?.withdrawInputMode ??
|
|
83
|
+
("single" as InputModeFromAction<WithdrawActionInfo>));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
depositInputMode(): InputModeFromAction<DepositActionInfo> {
|
|
87
|
+
return this._depositInputMode;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
withdrawInputMode(): InputModeFromAction<WithdrawActionInfo> {
|
|
91
|
+
return this._withdrawInputMode;
|
|
48
92
|
}
|
|
49
93
|
|
|
50
94
|
async getUserTVL(user: ContractAddr, blockIdentifier?: BlockIdentifier): Promise<TVLInfo> {
|
|
@@ -55,11 +99,11 @@ export class BaseStrategy<TVLInfo, ActionInfo> extends CacheClass {
|
|
|
55
99
|
throw new Error("Not implemented");
|
|
56
100
|
}
|
|
57
101
|
|
|
58
|
-
async depositCall(amountInfo:
|
|
102
|
+
async depositCall(amountInfo: DepositActionInfo, receiver: ContractAddr): Promise<Call[]> {
|
|
59
103
|
throw new Error("Not implemented");
|
|
60
104
|
}
|
|
61
105
|
|
|
62
|
-
async withdrawCall(amountInfo:
|
|
106
|
+
async withdrawCall(amountInfo: WithdrawActionInfo, receiver: ContractAddr, owner: ContractAddr): Promise<Call[]> {
|
|
63
107
|
throw new Error("Not implemented");
|
|
64
108
|
}
|
|
65
109
|
|
|
@@ -71,11 +115,112 @@ export class BaseStrategy<TVLInfo, ActionInfo> extends CacheClass {
|
|
|
71
115
|
blockIdentifier?: BlockIdentifier,
|
|
72
116
|
sinceBlocks?: number,
|
|
73
117
|
timeperiod?: "24h" | "7d" | "30d" | "3m"
|
|
74
|
-
): Promise<number | NetAPYDetails> {
|
|
118
|
+
): Promise<number | string | NetAPYDetails> {
|
|
75
119
|
throw new Error("Not implemented");
|
|
76
120
|
}
|
|
77
121
|
|
|
78
122
|
async getPendingRewards(): Promise<HarvestInfo[]> {
|
|
79
123
|
return [];
|
|
80
124
|
}
|
|
81
|
-
|
|
125
|
+
|
|
126
|
+
async getUserRealizedAPY(
|
|
127
|
+
blockIdentifier?: BlockIdentifier,
|
|
128
|
+
sinceBlocks?: number
|
|
129
|
+
): Promise<number> {
|
|
130
|
+
throw new Error("Not implemented");
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async getUserPositionCards(_input: UserPositionCardsInput): Promise<UserPositionCard[]> {
|
|
134
|
+
throw new Error("Not implemented");
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async getMaxTVL() : Promise<Web3Number> {
|
|
138
|
+
// Can throw an error as well if needed, RN returning 0
|
|
139
|
+
return new Web3Number('0', 18)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
protected formatTokenAmountForCard(amount: Web3Number, tokenInfo: TokenInfo): string {
|
|
143
|
+
const displayDecimals = tokenInfo.displayDecimals ?? 2;
|
|
144
|
+
const fixed = Number(amount.toFixed(displayDecimals));
|
|
145
|
+
const normalized = Number.isFinite(fixed) ? fixed : 0;
|
|
146
|
+
return `${normalized.toLocaleString("en-US", {
|
|
147
|
+
maximumFractionDigits: displayDecimals,
|
|
148
|
+
minimumFractionDigits: 0,
|
|
149
|
+
})} ${tokenInfo.symbol}`;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
protected formatPercentForCard(value: number): string {
|
|
153
|
+
if (!Number.isFinite(value)) return "N/A";
|
|
154
|
+
return `${(value * 100).toFixed(2)}%`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
protected formatUSDForCard(value: number): string {
|
|
158
|
+
if (!Number.isFinite(value)) return "$0.00";
|
|
159
|
+
return new Intl.NumberFormat("en-US", {
|
|
160
|
+
style: "currency",
|
|
161
|
+
currency: "USD",
|
|
162
|
+
maximumFractionDigits: 2,
|
|
163
|
+
}).format(value);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
protected getSubValueColorFromSignedNumber(value: number): UserPositionCardSubValueColor {
|
|
167
|
+
if (!Number.isFinite(value)) return "default";
|
|
168
|
+
if (value > 0) return "positive";
|
|
169
|
+
if (value < 0) return "negative";
|
|
170
|
+
return "default";
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Calculate lifetime earnings for a user based on provided data from client
|
|
175
|
+
* Formula: lifetimeEarnings = currentValue + totalWithdrawals - totalDeposits
|
|
176
|
+
*
|
|
177
|
+
* @param userTVL - The user's current TVL (SingleTokenInfo with amount, usdValue, tokenInfo)
|
|
178
|
+
* @param investmentFlows - Array of investment flow transactions from client
|
|
179
|
+
* @returns Object containing lifetime earnings, current value, and total deposits/withdrawals
|
|
180
|
+
*/
|
|
181
|
+
getLifetimeEarnings(
|
|
182
|
+
userTVL: SingleTokenInfo,
|
|
183
|
+
investmentFlows: Array<{ amount: string; type: string; timestamp: number; tx_hash: string }>
|
|
184
|
+
): {
|
|
185
|
+
tokenInfo: SingleTokenInfo;
|
|
186
|
+
lifetimeEarnings: Web3Number;
|
|
187
|
+
currentValue: Web3Number;
|
|
188
|
+
totalDeposits: Web3Number;
|
|
189
|
+
totalWithdrawals: Web3Number;
|
|
190
|
+
} {
|
|
191
|
+
// Get token decimals from userTVL
|
|
192
|
+
const tokenDecimals = userTVL.tokenInfo.decimals;
|
|
193
|
+
|
|
194
|
+
// Initialize totals
|
|
195
|
+
let totalDeposits = Web3Number.fromWei("0", tokenDecimals);
|
|
196
|
+
let totalWithdrawals = Web3Number.fromWei("0", tokenDecimals);
|
|
197
|
+
|
|
198
|
+
// Process investment flows
|
|
199
|
+
for (const flow of investmentFlows) {
|
|
200
|
+
const amount = Web3Number.fromWei(flow.amount, tokenDecimals);
|
|
201
|
+
|
|
202
|
+
if (flow.type === 'deposit') {
|
|
203
|
+
totalDeposits = totalDeposits.plus(amount);
|
|
204
|
+
} else if (flow.type === 'withdraw' || flow.type === 'redeem') {
|
|
205
|
+
totalWithdrawals = totalWithdrawals.plus(amount);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Calculate lifetime earnings: current value + withdrawals - deposits
|
|
210
|
+
const lifetimeEarnings = userTVL.amount
|
|
211
|
+
.plus(totalWithdrawals)
|
|
212
|
+
.minus(totalDeposits);
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
tokenInfo: {
|
|
216
|
+
tokenInfo: userTVL.tokenInfo,
|
|
217
|
+
amount: lifetimeEarnings,
|
|
218
|
+
usdValue: 0, // Lifetime earnings are not converted to USD
|
|
219
|
+
},
|
|
220
|
+
lifetimeEarnings,
|
|
221
|
+
currentValue: userTVL.amount,
|
|
222
|
+
totalDeposits,
|
|
223
|
+
totalWithdrawals,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
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
8
|
|
|
9
9
|
export const ENDPOINTS = {
|
|
10
10
|
VESU_BASE: "https://proxy.api.troves.fi/vesu",
|