@strkfarm/sdk 1.0.28 → 1.0.30

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.
@@ -1,3 +1,4 @@
1
+ import { logger } from "@/global";
1
2
  import BigNumber from "bignumber.js";
2
3
 
3
4
  export class _Web3Number<T extends _Web3Number<T>> extends BigNumber {
@@ -13,22 +14,22 @@ export class _Web3Number<T extends _Web3Number<T>> extends BigNumber {
13
14
  }
14
15
 
15
16
  multipliedBy(value: string | number | T): T {
16
- let _value = Number(value).toFixed(this.maxToFixedDecimals());
17
+ const _value = this.getStandardString(value);
17
18
  return this.construct(this.mul(_value).toString(), this.decimals);
18
19
  }
19
20
 
20
21
  dividedBy(value: string | number | T): T {
21
- let _value = Number(value).toFixed(this.maxToFixedDecimals());
22
+ const _value = this.getStandardString(value);
22
23
  return this.construct(this.div(_value).toString(), this.decimals);
23
24
  }
24
25
 
25
26
  plus(value: string | number | T): T {
26
- const _value = Number(value).toFixed(this.maxToFixedDecimals());
27
+ const _value = this.getStandardString(value);
27
28
  return this.construct(this.add(_value).toString(), this.decimals);
28
29
  }
29
30
 
30
31
  minus(n: number | string | T, base?: number): T {
31
- const _value = Number(n).toFixed(this.maxToFixedDecimals());
32
+ const _value = this.getStandardString(n);
32
33
  return this.construct(super.minus(_value, base).toString(), this.decimals);
33
34
  }
34
35
 
@@ -49,9 +50,16 @@ export class _Web3Number<T extends _Web3Number<T>> extends BigNumber {
49
50
  }
50
51
 
51
52
  private maxToFixedDecimals() {
52
- return Math.min(this.decimals, 13);
53
+ return Math.min(this.decimals, 18);
54
+ }
55
+
56
+ private getStandardString(value: string | number | T): string {
57
+ if (typeof value == 'string') {
58
+ return value
59
+ }
60
+ return value.toFixed(this.maxToFixedDecimals())
53
61
  }
54
62
  }
55
63
 
56
- BigNumber.config({ DECIMAL_PLACES: 18 })
57
- _Web3Number.config({ DECIMAL_PLACES: 18 })
64
+ BigNumber.config({ DECIMAL_PLACES: 18, ROUNDING_MODE: BigNumber.ROUND_DOWN })
65
+ _Web3Number.config({ DECIMAL_PLACES: 18, ROUNDING_MODE: BigNumber.ROUND_DOWN })
package/src/global.ts CHANGED
@@ -52,42 +52,48 @@ const defaultTokens: TokenInfo[] = [{
52
52
  logo: 'https://assets.coingecko.com/coins/images/26433/small/starknet.png',
53
53
  address: ContractAddr.from('0x4718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d'),
54
54
  decimals: 18,
55
- coingeckId: 'starknet'
55
+ coingeckId: 'starknet',
56
+ displayDecimals: 2,
56
57
  }, {
57
58
  name: 'xSTRK',
58
59
  symbol: 'xSTRK',
59
60
  logo: 'https://dashboard.endur.fi/endur-fi.svg',
60
61
  address: ContractAddr.from('0x028d709c875c0ceac3dce7065bec5328186dc89fe254527084d1689910954b0a'),
61
62
  decimals: 18,
62
- coingeckId: undefined
63
+ coingeckId: undefined,
64
+ displayDecimals: 2,
63
65
  }, {
64
66
  name: 'ETH',
65
67
  symbol: 'ETH',
66
68
  logo: 'https://opbnb.bscscan.com/token/images/ether.svg',
67
69
  address: ContractAddr.from('0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7'),
68
70
  decimals: 18,
69
- coingeckId: undefined
71
+ coingeckId: undefined,
72
+ displayDecimals: 4,
70
73
  }, {
71
74
  name: 'USDC',
72
75
  symbol: 'USDC',
73
76
  logo: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png',
74
77
  address: ContractAddr.from('0x53c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8'),
75
78
  decimals: 6,
76
- coingeckId: undefined
79
+ coingeckId: undefined,
80
+ displayDecimals: 2,
77
81
  }, {
78
82
  name: 'USDT',
79
83
  symbol: 'USDT',
80
84
  logo: 'https://assets.coingecko.com/coins/images/325/small/Tether.png',
81
85
  address: ContractAddr.from('0x68f5c6a61780768455de69077e07e89787839bf8166decfbf92b645209c0fb8'),
82
86
  decimals: 6,
83
- coingeckId: undefined
87
+ coingeckId: undefined,
88
+ displayDecimals: 2,
84
89
  }, {
85
90
  name: 'WBTC',
86
91
  symbol: 'WBTC',
87
92
  logo: 'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599/logo.png',
88
93
  address: ContractAddr.from('0x3fe2b97c1fd336e750087d68b9b867997fd64a2661ff3ca5a7c771641e8e7ac'),
89
94
  decimals: 8,
90
- coingeckId: undefined
95
+ coingeckId: undefined,
96
+ displayDecimals: 6,
91
97
  }]
92
98
  const tokens: TokenInfo[] = defaultTokens;
93
99
 
@@ -150,6 +156,7 @@ export class Global {
150
156
  decimals: token.decimals,
151
157
  logo: token.logoUri,
152
158
  coingeckId: token.extensions.coingeckoId,
159
+ displayDecimals: 2
153
160
  });
154
161
  });
155
162
  console.log(tokens);
@@ -29,6 +29,7 @@ export interface TokenInfo {
29
29
  decimals: number,
30
30
  logo: string,
31
31
  coingeckId?: string,
32
+ displayDecimals: number
32
33
  }
33
34
 
34
35
  export enum Network {
@@ -73,10 +74,12 @@ export interface IStrategyMetadata<T> {
73
74
  netRisk: number,
74
75
  notARisks: string[]
75
76
  },
77
+ apyMethodology?: string,
76
78
  additionalInfo: T
77
79
  }
78
80
 
79
81
  export interface IInvestmentFlow {
82
+ id?: string, // used to link flows
80
83
  title: string,
81
84
  subItems: {key: string, value: string}[],
82
85
  linkedFlows: IInvestmentFlow[],
@@ -3,6 +3,7 @@ import { uint256 } from "starknet";
3
3
  import { Call, Uint256 } from "starknet";
4
4
  import { fetchBuildExecuteTransaction, fetchQuotes, Quote } from "@avnu/avnu-sdk";
5
5
  import { assert } from "../utils";
6
+ import { logger } from "@/global";
6
7
 
7
8
  export interface Route {
8
9
  token_from: string,
@@ -26,68 +27,86 @@ export interface SwapInfo {
26
27
 
27
28
 
28
29
  export class AvnuWrapper {
29
- async getQuotes(
30
- fromToken: string,
31
- toToken: string,
32
- amountWei: string,
33
- taker: string,
34
- ) {
35
- const params: any = {
36
- sellTokenAddress: fromToken,
37
- buyTokenAddress: toToken,
38
- sellAmount: amountWei,
39
- takerAddress: taker,
40
- };
41
- assert(fromToken != toToken, 'From and to tokens are the same');
30
+ async getQuotes(
31
+ fromToken: string,
32
+ toToken: string,
33
+ amountWei: string,
34
+ taker: string,
35
+ retry = 0
36
+ ): Promise<Quote> {
37
+ const MAX_RETRY = 5;
38
+ logger.verbose(`${AvnuWrapper.name}: getQuotes => Getting quotes for ${fromToken} -> ${toToken}, amount: ${amountWei}, taker: ${taker}, retry: ${retry}`);
39
+ const params: any = {
40
+ sellTokenAddress: fromToken,
41
+ buyTokenAddress: toToken,
42
+ sellAmount: amountWei,
43
+ takerAddress: taker,
44
+ // excludeSources: ['Nostra', 'Haiko(Solvers)']
45
+ excludeSources: ['Haiko(Solvers)'] // to resolve InvalidOraclePrice error
46
+ };
47
+ assert(fromToken != toToken, "From and to tokens are the same");
42
48
 
43
- const quotes = await fetchQuotes(params);
44
- assert(quotes.length > 0, 'No quotes found');
45
- return quotes[0];
49
+ const quotes = await fetchQuotes(params);
50
+ if (quotes.length == 0) {
51
+ if (retry < MAX_RETRY) {
52
+ await new Promise((res) => setTimeout(res, 3000))
53
+ return await this.getQuotes(fromToken, toToken, amountWei, taker, retry + 1);
54
+ }
55
+ throw new Error('no quotes found')
46
56
  }
57
+ return quotes[0];
58
+ }
47
59
 
48
- async getSwapInfo(
49
- quote: Quote,
50
- taker: string,
51
- integratorFeeBps: number,
52
- integratorFeeRecipient: string,
53
- minAmount: string
54
- ) {
55
- const calldata = await fetchBuildExecuteTransaction(quote.quoteId);
56
- // its the multi swap function call
57
- const call: Call = calldata.calls[1];
58
- const callData: string[] = call.calldata as string[];
59
- const routesLen: number = Number(callData[11]);
60
- assert(routesLen > 0, 'No routes found');
61
-
62
- // use call data to re-construct routes
63
- let startIndex = 12;
64
- const routes: Route[] = [];
65
- for(let i=0; i<routesLen; ++i) {
66
- const swap_params_len = Number(callData[startIndex + 4]);
67
- const route: Route = {
68
- token_from: callData[startIndex],
69
- token_to: callData[startIndex + 1],
70
- exchange_address: callData[startIndex + 2],
71
- percent: Number(callData[startIndex + 3]),
72
- additional_swap_params: swap_params_len > 0 ? callData.slice(startIndex + 5, startIndex + 5 + swap_params_len): []
73
- }
74
- routes.push(route);
75
- startIndex += 5 + swap_params_len;
76
- }
60
+ async getSwapInfo(
61
+ quote: Quote,
62
+ taker: string,
63
+ integratorFeeBps: number,
64
+ integratorFeeRecipient: string,
65
+ minAmount?: string
66
+ ) {
67
+ const calldata = await fetchBuildExecuteTransaction(quote.quoteId);
68
+ // its the multi swap function call
69
+ const call: Call = calldata.calls[1];
70
+ const callData: string[] = call.calldata as string[];
71
+ const routesLen: number = Number(callData[11]);
72
+ assert(routesLen > 0, "No routes found");
77
73
 
78
- // swapInfo as expected by the strategy
79
- const swapInfo: SwapInfo = {
80
- token_from_address: quote.sellTokenAddress,
81
- token_from_amount: uint256.bnToUint256(quote.sellAmount),
82
- token_to_address: quote.buyTokenAddress,
83
- token_to_amount: uint256.bnToUint256(quote.buyAmount),
84
- token_to_min_amount: uint256.bnToUint256(minAmount),
85
- beneficiary: taker,
86
- integrator_fee_amount_bps: integratorFeeBps,
87
- integrator_fee_recipient: integratorFeeRecipient,
88
- routes
89
- };
90
-
91
- return swapInfo;
74
+ // use call data to re-construct routes
75
+ let startIndex = 12;
76
+ const routes: Route[] = [];
77
+ for (let i = 0; i < routesLen; ++i) {
78
+ const swap_params_len = Number(callData[startIndex + 4]);
79
+ const route: Route = {
80
+ token_from: callData[startIndex],
81
+ token_to: callData[startIndex + 1],
82
+ exchange_address: callData[startIndex + 2],
83
+ percent: Number(callData[startIndex + 3]),
84
+ additional_swap_params:
85
+ swap_params_len > 0
86
+ ? callData.slice(startIndex + 5, startIndex + 5 + swap_params_len)
87
+ : [],
88
+ };
89
+ routes.push(route);
90
+ startIndex += 5 + swap_params_len;
92
91
  }
92
+
93
+ // swapInfo as expected by the strategy
94
+ // fallback, max 1% slippage
95
+ const _minAmount = minAmount || (quote.buyAmount * 95n / 100n).toString();
96
+ logger.verbose(`${AvnuWrapper.name}: getSwapInfo => buyToken: ${quote.buyTokenAddress}`);
97
+ logger.verbose(`${AvnuWrapper.name}: getSwapInfo => buyAmount: ${quote.buyAmount}, minAmount: ${_minAmount}`);
98
+ const swapInfo: SwapInfo = {
99
+ token_from_address: quote.sellTokenAddress,
100
+ token_from_amount: uint256.bnToUint256(quote.sellAmount),
101
+ token_to_address: quote.buyTokenAddress,
102
+ token_to_amount: uint256.bnToUint256(_minAmount),
103
+ token_to_min_amount: uint256.bnToUint256(_minAmount),
104
+ beneficiary: taker,
105
+ integrator_fee_amount_bps: integratorFeeBps,
106
+ integrator_fee_recipient: integratorFeeRecipient,
107
+ routes,
108
+ };
109
+
110
+ return swapInfo;
111
+ }
93
112
  }
@@ -0,0 +1,111 @@
1
+ import { ContractAddr, Web3Number } from "@/dataTypes";
2
+ import { logger } from "@/global";
3
+ import { IConfig } from "@/interfaces";
4
+ import { assert } from "@/utils";
5
+ import { Contract, num } from "starknet";
6
+
7
+ export interface HarvestInfo {
8
+ rewardsContract: ContractAddr,
9
+ token: ContractAddr,
10
+ startDate: Date,
11
+ endDate: Date,
12
+ claim: {
13
+ id: number,
14
+ amount: Web3Number,
15
+ claimee: ContractAddr
16
+ },
17
+ actualReward: Web3Number,
18
+ proof: string[]
19
+ }
20
+
21
+ export class Harvests {
22
+ constructor(protected readonly config: IConfig) {}
23
+
24
+ getHarvests(addr: ContractAddr): Promise<HarvestInfo[]> {
25
+ throw new Error("Not implemented");
26
+ }
27
+
28
+ async getUnHarvestedRewards(addr: ContractAddr) {
29
+ const rewards = await this.getHarvests(addr);
30
+ if (rewards.length == 0) return [];
31
+
32
+ const unClaimed: HarvestInfo[] = [];
33
+
34
+ const cls = await this.config.provider.getClassAt(rewards[0].rewardsContract.address);
35
+ for (let reward of rewards) {
36
+ const contract = new Contract(cls.abi, reward.rewardsContract.address, this.config.provider);
37
+ const isClaimed = await contract.call('is_claimed', [reward.claim.id]);
38
+ logger.verbose(`${Harvests.name}: isClaimed: ${isClaimed}`);
39
+ if (isClaimed)
40
+ return unClaimed;
41
+ unClaimed.unshift(reward); // to ensure older harvest is first
42
+ }
43
+ return unClaimed;
44
+ }
45
+ }
46
+
47
+ const STRK = '0x4718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d';
48
+
49
+ export class EkuboHarvests extends Harvests {
50
+ async getHarvests(addr: ContractAddr) {
51
+ const EKUBO_API = `https://starknet-mainnet-api.ekubo.org/airdrops/${addr.address}?token=${STRK}`
52
+ const resultEkubo = await fetch(EKUBO_API);
53
+ const items = (await resultEkubo.json());
54
+
55
+ const rewards: HarvestInfo[] = [];
56
+ for (let i=0; i<items.length; ++i) {
57
+ const info = items[i];
58
+ assert(info.token == STRK, 'expected strk token only')
59
+ rewards.push({
60
+ rewardsContract: ContractAddr.from(info.contract_address),
61
+ token: ContractAddr.from(STRK),
62
+ startDate: new Date(info.start_date),
63
+ endDate: new Date(info.end_date),
64
+ claim: {
65
+ id: info.claim.id,
66
+ amount: Web3Number.fromWei(info.claim.amount, 18),
67
+ claimee: ContractAddr.from(info.claim.claimee)
68
+ },
69
+ actualReward: Web3Number.fromWei(info.claim.amount, 18),
70
+ proof: info.proof
71
+ });
72
+ }
73
+ return rewards.sort((a, b) => b.endDate.getTime() - a.endDate.getTime());
74
+ }
75
+ }
76
+
77
+ export class VesuHarvests extends Harvests {
78
+ async getHarvests(addr: ContractAddr): Promise<HarvestInfo[]> {
79
+ const result = await fetch(`https://api.vesu.xyz/users/${addr.address}/strk-rewards/calldata`);
80
+ const data = await result.json();
81
+ const rewardsContract = ContractAddr.from('0x0387f3eb1d98632fbe3440a9f1385Aec9d87b6172491d3Dd81f1c35A7c61048F');
82
+
83
+ // get already claimed amount
84
+ const cls = await this.config.provider.getClassAt(rewardsContract.address);
85
+ const contract = new Contract(cls.abi, rewardsContract.address, this.config.provider);
86
+ const _claimed_amount: any = await contract.call('amount_already_claimed', [addr.address]);
87
+ const claimed_amount = Web3Number.fromWei(_claimed_amount.toString(), 18);
88
+ logger.verbose(`${VesuHarvests.name}: claimed_amount: ${claimed_amount.toString()}`);
89
+
90
+ // get the actual reward
91
+ const actualReward = Web3Number.fromWei(data.data.amount, 18).minus(claimed_amount);
92
+ logger.verbose(`${VesuHarvests.name}: actualReward: ${actualReward.toString()}`);
93
+ return [{
94
+ rewardsContract,
95
+ token: ContractAddr.from(STRK),
96
+ startDate: new Date(0),
97
+ endDate: new Date(0),
98
+ claim: {
99
+ id: 0,
100
+ amount: Web3Number.fromWei(num.getDecimalString(data.data.amount), 18),
101
+ claimee: addr
102
+ },
103
+ actualReward,
104
+ proof: data.data.proof
105
+ }]
106
+ }
107
+
108
+ async getUnHarvestedRewards(addr: ContractAddr) {
109
+ return await this.getHarvests(addr);
110
+ }
111
+ }
@@ -12,8 +12,8 @@ export class PricerFromApi extends PricerBase {
12
12
  async getPrice(tokenSymbol: string): Promise<PriceInfo> {
13
13
  try {
14
14
  return await this.getPriceFromMyAPI(tokenSymbol);
15
- } catch (e) {
16
- logger.warn('getPriceFromMyAPI error', e);
15
+ } catch (e: any) {
16
+ logger.warn('getPriceFromMyAPI error', JSON.stringify(e.message || e));
17
17
  }
18
18
  logger.log('getPrice coinbase', tokenSymbol);
19
19
  let retry = 0;
@@ -32,8 +32,8 @@ export class PricerFromApi extends PricerBase {
32
32
  price,
33
33
  timestamp: new Date()
34
34
  }
35
- } catch (e) {
36
- logger.warn('getPrice coinbase error', e, retry);
35
+ } catch (e: any) {
36
+ logger.warn('getPrice coinbase error', JSON.stringify(e.message || e));
37
37
  await new Promise((resolve) => setTimeout(resolve, retry * 1000));
38
38
  }
39
39
  }
@@ -48,10 +48,11 @@ export class PricerFromApi extends PricerBase {
48
48
  const priceInfo = await priceInfoRes.json();
49
49
  const now = new Date();
50
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
- }
51
+ // if (now.getTime() - priceTime.getTime() > 900000) {
52
+ // // 15 mins
53
+ // logger.verbose(`Price is stale: ${tokenSymbol}, timestamp: ${priceInfo.timestamp}, price: ${priceInfo.price}`);
54
+ // throw new Error('Price is stale');
55
+ // }
55
56
  const price = Number(priceInfo.price);
56
57
  return {
57
58
  price,
@@ -40,7 +40,8 @@ export class ZkLend extends ILending implements ILending {
40
40
  logo: '',
41
41
  decimals: pool.token.decimals,
42
42
  borrowFactor: Web3Number.fromWei(pool.borrow_factor.value, pool.borrow_factor.decimals),
43
- collareralFactor
43
+ collareralFactor,
44
+ displayDecimals: 2
44
45
  }
45
46
  this.tokens.push(token);
46
47
  });
@@ -16,7 +16,7 @@ export interface DualActionAmount {
16
16
  token1: SingleActionAmount
17
17
  }
18
18
  export interface DualTokenInfo {
19
- netUsdValue: number,
19
+ usdValue: number,
20
20
  token0: SingleTokenInfo,
21
21
  token1: SingleTokenInfo
22
22
  }
@@ -36,11 +36,11 @@ export class BaseStrategy<TVLInfo, ActionInfo> {
36
36
  throw new Error("Not implemented");
37
37
  }
38
38
 
39
- depositCall(amountInfo: ActionInfo, receiver: ContractAddr): Call[] {
39
+ async depositCall(amountInfo: ActionInfo, receiver: ContractAddr): Promise<Call[]> {
40
40
  throw new Error("Not implemented");
41
41
  }
42
42
 
43
- withdrawCall(amountInfo: ActionInfo, receiver: ContractAddr, owner: ContractAddr): Call[] {
43
+ async withdrawCall(amountInfo: ActionInfo, receiver: ContractAddr, owner: ContractAddr): Promise<Call[]> {
44
44
  throw new Error("Not implemented");
45
45
  }
46
46