@strkfarm/sdk 1.0.54 → 1.0.56

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.
@@ -39,4 +39,8 @@ export class ContractAddr {
39
39
  toString() {
40
40
  return this.address;
41
41
  }
42
+
43
+ toBigInt() {
44
+ return num.toBigInt(this.address);
45
+ }
42
46
  }
package/src/global.ts CHANGED
@@ -62,6 +62,16 @@ const defaultTokens: TokenInfo[] = [{
62
62
  decimals: 8,
63
63
  coingeckId: undefined,
64
64
  displayDecimals: 6,
65
+ priceCheckAmount: 0.0001, // 112000 * 0.0001 = $11.2
66
+ }, {
67
+ name: 'tBTC',
68
+ symbol: 'tBTC',
69
+ logo: 'https://assets.strkfarm.com/integrations/tokens/tbtc.svg',
70
+ address: ContractAddr.from('0x4daa17763b286d1e59b97c283c0b8c949994c361e426a28f743c67bdfe9a32f'),
71
+ decimals: 18,
72
+ coingeckId: undefined,
73
+ displayDecimals: 6,
74
+ priceCheckAmount: 0.0001, // 112000 * 0.0001 = $11.2
65
75
  }]
66
76
  const tokens: TokenInfo[] = defaultTokens;
67
77
 
@@ -69,6 +79,8 @@ const tokens: TokenInfo[] = defaultTokens;
69
79
  * - fatalError: Things to do when a fatal error occurs
70
80
  */
71
81
  export class Global {
82
+ static cache: Record<string, {value: any, ttl: number, timestamp: number}> = {};
83
+
72
84
  static fatalError(message: string, err?: Error) {
73
85
  logger.error(message);
74
86
  console.error(message, err);
@@ -149,4 +161,22 @@ export class Global {
149
161
  }
150
162
  return token;
151
163
  }
164
+
165
+ static setGlobalCache(key: string, data: any, ttl: number = 60000) {
166
+ Global.cache[key] = {
167
+ value: data,
168
+ ttl,
169
+ timestamp: Date.now()
170
+ };
171
+ }
172
+
173
+ static getGlobalCache<T>(key: string): T | null {
174
+ const cached = Global.cache[key];
175
+ if (!cached) return null;
176
+ if (Date.now() - cached.timestamp > cached.ttl) {
177
+ delete Global.cache[key];
178
+ return null;
179
+ }
180
+ return cached.value;
181
+ }
152
182
  }
@@ -14,7 +14,8 @@ export enum RiskType {
14
14
  SMART_CONTRACT_RISK = "Smart Contract Risk",
15
15
  ORACLE_RISK = "Oracle Risk",
16
16
  TECHNICAL_RISK = "Technical Risk",
17
- COUNTERPARTY_RISK = "Counterparty Risk" // e.g. bad debt
17
+ COUNTERPARTY_RISK = "Counterparty Risk", // e.g. bad debt
18
+ DEPEG_RISK = "Depeg Risk" // e.g. USDC depeg
18
19
  }
19
20
 
20
21
  export interface RiskFactor {
@@ -32,6 +33,7 @@ export interface TokenInfo {
32
33
  logo: string;
33
34
  coingeckId?: string;
34
35
  displayDecimals: number;
36
+ priceCheckAmount?: number; // for tokens like BTC, doing 1BTC price check may not be ideal, esp on illiquid netwrks like sn
35
37
  }
36
38
 
37
39
  export enum Network {
@@ -104,7 +106,7 @@ export interface IInvestmentFlow {
104
106
  }
105
107
 
106
108
  export function getMainnetConfig(
107
- rpcUrl = "https://starknet-mainnet.public.blastapi.io",
109
+ rpcUrl: string = 'https://starknet-mainnet.public.blastapi.io',
108
110
  blockIdentifier: BlockIdentifier = "pending"
109
111
  ): IConfig {
110
112
  return {
@@ -135,6 +137,8 @@ export const getRiskExplaination = (riskType: RiskType) => {
135
137
  return "The risk of technical issues e.g. backend failure.";
136
138
  case RiskType.COUNTERPARTY_RISK:
137
139
  return "The risk of the counterparty defaulting e.g. bad debt on lending platforms.";
140
+ case RiskType.DEPEG_RISK:
141
+ return "The risk of a token losing its peg to the underlying asset, leading to potential losses for holders.";
138
142
  }
139
143
  };
140
144
 
@@ -197,3 +201,19 @@ export function highlightTextWithLinks(
197
201
  </>
198
202
  );
199
203
  }
204
+
205
+ export interface VaultPosition {
206
+ amount: Web3Number,
207
+ usdValue: number,
208
+ token: TokenInfo,
209
+ remarks: string
210
+ }
211
+
212
+ const VesuProtocol: IProtocol = {
213
+ name: "Vesu",
214
+ logo: "https://static-assets-8zct.onrender.com/integrations/vesu/logo.png"
215
+ };
216
+
217
+ export const Protocols = {
218
+ VESU: VesuProtocol,
219
+ }
@@ -149,7 +149,7 @@ export class Pricer extends PricerBase {
149
149
  }
150
150
  case 'Ekubo':
151
151
  try {
152
- const result = await this._getPriceEkubo(token);
152
+ const result = await this._getPriceEkubo(token, new Web3Number(token.priceCheckAmount ? token.priceCheckAmount : 1, token.decimals));
153
153
  this.methodToUse[token.symbol] = 'Ekubo';
154
154
  return result;
155
155
  } catch (error: any) {
@@ -0,0 +1,219 @@
1
+ import assert from 'assert'
2
+ import {Account, Call, RawArgs, RpcProvider, TransactionExecutionStatus, constants, extractContractHashes, hash, json, num, provider, transaction} from 'starknet'
3
+ import { readFileSync, existsSync, writeFileSync } from 'fs'
4
+ import { IConfig } from '../interfaces';
5
+ import { Store, getDefaultStoreConfig } from '../utils/store';
6
+ import { add } from 'winston';
7
+
8
+ function getContracts() {
9
+ const PATH = './contracts.json'
10
+ if (existsSync(PATH)) {
11
+ return JSON.parse(readFileSync(PATH, {encoding: 'utf-8'}))
12
+ }
13
+ return {}
14
+ }
15
+
16
+ function saveContracts(contracts: any) {
17
+ const PATH = './contracts.json'
18
+ writeFileSync(PATH, JSON.stringify(contracts));
19
+ }
20
+
21
+ function getAccount(
22
+ accountKey: string,
23
+ config: IConfig,
24
+ password = process.env.ACCOUNT_SECURE_PASSWORD,
25
+ accountsFileName = process.env.ACCOUNTS_FILE_NAME || getDefaultStoreConfig(config.network).ACCOUNTS_FILE_NAME,
26
+ secretFileFolder = process.env.SECRET_FILE_FOLDER || getDefaultStoreConfig(config.network).SECRET_FILE_FOLDER
27
+ ) {
28
+ const storeConfig = getDefaultStoreConfig(config.network);
29
+ storeConfig.ACCOUNTS_FILE_NAME = accountsFileName;
30
+ storeConfig.SECRET_FILE_FOLDER = secretFileFolder;
31
+ if (!password) {
32
+ throw new Error(`getAccount: Password is required (either in env as ACCOUNT_SECURE_PASSWORD or input to function)`);
33
+ }
34
+ const store = new Store(config, {
35
+ ...storeConfig,
36
+ PASSWORD: password
37
+ });
38
+
39
+ return store.getAccount(accountKey);
40
+ }
41
+
42
+ async function myDeclare(contract_name: string, package_name: string = 'strkfarm', config: IConfig, acc: Account) {
43
+ const provider = config.provider;
44
+ const compiledSierra = json.parse(
45
+ readFileSync(`./target/release/${package_name}_${contract_name}.contract_class.json`).toString("ascii")
46
+ )
47
+ const compiledCasm = json.parse(
48
+ readFileSync(`./target/release/${package_name}_${contract_name}.compiled_contract_class.json`).toString("ascii")
49
+ )
50
+
51
+ const contracts = getContracts();
52
+ const payload = {
53
+ contract: compiledSierra,
54
+ casm: compiledCasm
55
+ };
56
+
57
+ const result = extractContractHashes(payload);
58
+ console.log("classhash:", result.classHash);
59
+
60
+ try {
61
+ const cls = await provider.getClassByHash(result.classHash);
62
+ console.log(`Class ${result.classHash} already declared`);
63
+ return {
64
+ transaction_hash: '',
65
+ class_hash: result.classHash
66
+ }
67
+ } catch (err) {}
68
+
69
+ const fee = await acc.estimateDeclareFee({
70
+ contract: compiledSierra,
71
+ casm: compiledCasm,
72
+ })
73
+
74
+ const tx = await acc.declareIfNot(payload)
75
+ console.log(`Declaring: ${contract_name}, tx:`, tx.transaction_hash);
76
+ await provider.waitForTransaction(tx.transaction_hash, {
77
+ successStates: [TransactionExecutionStatus.SUCCEEDED]
78
+ })
79
+
80
+ if (!contracts.class_hashes) {
81
+ contracts['class_hashes'] = {};
82
+ }
83
+
84
+ // Todo attach cairo and scarb version. and commit ID
85
+ contracts.class_hashes[contract_name] = tx.class_hash;
86
+ saveContracts(contracts);
87
+ console.log(`Contract declared: ${contract_name}`)
88
+ console.log(`Class hash: ${tx.class_hash}`)
89
+ return tx;
90
+ }
91
+
92
+ async function deployContract(contract_name: string, classHash: string, constructorData: RawArgs, config: IConfig, acc: Account) {
93
+ const provider = config.provider;
94
+
95
+ const fee = await acc.estimateDeployFee({
96
+ classHash,
97
+ constructorCalldata: constructorData,
98
+ })
99
+ console.log("Deploy fee", contract_name, Number(fee.suggestedMaxFee) / 10 ** 18, 'ETH')
100
+
101
+ const tx = await acc.deployContract({
102
+ classHash,
103
+ constructorCalldata: constructorData,
104
+ })
105
+ console.log('Deploy tx: ', tx.transaction_hash);
106
+ await provider.waitForTransaction(tx.transaction_hash, {
107
+ successStates: [TransactionExecutionStatus.SUCCEEDED]
108
+ })
109
+ const contracts = getContracts();
110
+ if (!contracts.contracts) {
111
+ contracts['contracts'] = {};
112
+ }
113
+ contracts.contracts[contract_name] = tx.contract_address;
114
+ saveContracts(contracts);
115
+ console.log(`Contract deployed: ${contract_name}`)
116
+ console.log(`Address: ${tx.contract_address}`)
117
+ return tx;
118
+ }
119
+
120
+ interface DeployContractResult {
121
+ contract_name: string;
122
+ package_name: string;
123
+ class_hash: string;
124
+ call: Call,
125
+ address: string
126
+ }
127
+
128
+ async function prepareMultiDeployContracts(
129
+ contracts: Array<{ contract_name: string, package_name: string, constructorData: RawArgs }>,
130
+ config: IConfig,
131
+ acc: Account
132
+ ) {
133
+ const result: DeployContractResult[] = [];
134
+
135
+ for (const { contract_name, package_name, constructorData } of contracts) {
136
+ const declaredInfo = await myDeclare(contract_name, package_name, config, acc);
137
+ const classHash = declaredInfo.class_hash;
138
+
139
+ const {calls, addresses} = transaction.buildUDCCall({
140
+ classHash,
141
+ constructorCalldata: constructorData,
142
+ }, acc.address);
143
+
144
+ assert(calls.length == 1, `Expected exactly one call, got ${calls.length}`);
145
+ assert(addresses.length == 1, `Expected exactly one address, got ${addresses.length}`);
146
+
147
+ result.push({
148
+ contract_name,
149
+ package_name,
150
+ class_hash: classHash,
151
+ call: calls[0],
152
+ address: addresses[0]
153
+ });
154
+ }
155
+
156
+ return result;
157
+ }
158
+
159
+ async function executeDeployCalls(
160
+ contractsInfo: DeployContractResult[],
161
+ acc: Account,
162
+ provider: RpcProvider
163
+ ) {
164
+ // all must be UDC
165
+ for (let contractInfo of contractsInfo) {
166
+ assert(num.toHexString(contractInfo.call.contractAddress) == num.toHexString(constants.UDC.ADDRESS), 'Must be pointed at UDC address');
167
+ }
168
+
169
+ const allCalls = contractsInfo.map(info => info.call);
170
+ await executeTransactions(allCalls, acc, provider, `Deploying contracts: ${contractsInfo.map(info => info.contract_name).join(', ')}`);
171
+
172
+ // save contracts in storage
173
+ const contracts = getContracts();
174
+ if (!contracts.contracts) {
175
+ contracts['contracts'] = {};
176
+ }
177
+ if (!contracts.class_hashes) {
178
+ contracts['class_hashes'] = {};
179
+ }
180
+
181
+ for (let contractInfo of contractsInfo) {
182
+ // Todo attach cairo and scarb version. and commit ID
183
+ contracts.class_hashes[contractInfo.contract_name] = contractInfo.class_hash;
184
+ contracts.contracts[contractInfo.contract_name] = contractInfo.address;
185
+ console.log(`Contract deployed: ${contractInfo.contract_name}, addr: ${contractInfo.address}`);
186
+ }
187
+ saveContracts(contracts);
188
+ }
189
+
190
+ async function executeTransactions(
191
+ calls: Call[],
192
+ acc: Account,
193
+ provider: RpcProvider,
194
+ remarks?: string, // optional string for logging purposes
195
+ ) {
196
+ const tx = await acc.execute(calls);
197
+ console.log(`Transaction executed: ${tx.transaction_hash}`);
198
+ if (remarks)
199
+ console.log(`Remarks: ${remarks}`);
200
+
201
+
202
+ await provider.waitForTransaction(tx.transaction_hash, {
203
+ successStates: [TransactionExecutionStatus.SUCCEEDED]
204
+ });
205
+ console.log(`Transaction confirmed: ${tx.transaction_hash}`);
206
+
207
+ return tx;
208
+ }
209
+
210
+ const Deployer = {
211
+ getAccount,
212
+ myDeclare,
213
+ deployContract,
214
+ prepareMultiDeployContracts,
215
+ executeDeployCalls,
216
+ executeTransactions
217
+ }
218
+
219
+ export default Deployer;
package/src/node/index.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export * from './pricer-redis';
2
- export * from '@/node/headless';
2
+ export * from '@/node/headless';
3
+ export { default as Deployer } from './deployer';
@@ -21,8 +21,14 @@ export interface DualTokenInfo {
21
21
  token1: SingleTokenInfo
22
22
  }
23
23
 
24
+ interface CacheData {
25
+ timestamp: number;
26
+ ttl: number;
27
+ data: any;
28
+ }
24
29
  export class BaseStrategy<TVLInfo, ActionInfo> {
25
30
  readonly config: IConfig;
31
+ readonly cache: Map<string, CacheData> = new Map();
26
32
 
27
33
  constructor(config: IConfig) {
28
34
  this.config = config;
@@ -43,5 +49,26 @@ export class BaseStrategy<TVLInfo, ActionInfo> {
43
49
  async withdrawCall(amountInfo: ActionInfo, receiver: ContractAddr, owner: ContractAddr): Promise<Call[]> {
44
50
  throw new Error("Not implemented");
45
51
  }
52
+
53
+ setCache(key: string, data: any, ttl: number = 60000): void {
54
+ const timestamp = Date.now();
55
+ this.cache.set(key, { timestamp, ttl, data });
56
+ }
57
+
58
+ getCache(key: string): any | null {
59
+ const cachedData = this.cache.get(key);
60
+ if (!cachedData || !this.isCacheValid(key)) {
61
+ return null;
62
+ }
63
+ return cachedData.data;
64
+ }
65
+
66
+ isCacheValid(key: string): boolean {
67
+ const cachedData = this.cache.get(key);
68
+ if (!cachedData) return false;
69
+
70
+ const { timestamp, ttl } = cachedData;
71
+ return Date.now() - timestamp <= ttl;
72
+ }
46
73
 
47
74
  }
@@ -2,3 +2,6 @@ export * from './autoCompounderStrk';
2
2
  export * from './vesu-rebalance';
3
3
  export * from './ekubo-cl-vault';
4
4
  export * from './base-strategy';
5
+ export * from './sensei';
6
+ export * from './universal-adapters';
7
+ export * from './universal-strategy';