@strkfarm/sdk 1.0.55 → 1.0.57

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
  }
@@ -33,6 +33,7 @@ export interface TokenInfo {
33
33
  logo: string;
34
34
  coingeckId?: string;
35
35
  displayDecimals: number;
36
+ priceCheckAmount?: number; // for tokens like BTC, doing 1BTC price check may not be ideal, esp on illiquid netwrks like sn
36
37
  }
37
38
 
38
39
  export enum Network {
@@ -105,7 +106,7 @@ export interface IInvestmentFlow {
105
106
  }
106
107
 
107
108
  export function getMainnetConfig(
108
- rpcUrl: string,
109
+ rpcUrl: string = 'https://starknet-mainnet.public.blastapi.io',
109
110
  blockIdentifier: BlockIdentifier = "pending"
110
111
  ): IConfig {
111
112
  return {
@@ -200,3 +201,19 @@ export function highlightTextWithLinks(
200
201
  </>
201
202
  );
202
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';
@@ -5,10 +5,8 @@ export class TelegramNotif {
5
5
  private subscribers: string[] = [
6
6
  // '6820228303',
7
7
  '1505578076',
8
- // '5434736198', // maaza
9
8
  '1356705582', // langs
10
9
  '1388729514', // hwashere
11
- '6020162572', //minato
12
10
  '985902592'
13
11
  ];
14
12
  readonly bot: TelegramBot;
@@ -1,5 +1,6 @@
1
1
  import { ContractAddr, Web3Number } from "@/dataTypes";
2
2
  import { IConfig, TokenInfo } from "@/interfaces";
3
+ import { CacheClass } from "@/utils/cacheClass";
3
4
  import { Call } from "starknet";
4
5
 
5
6
  export interface SingleActionAmount {
@@ -26,11 +27,12 @@ interface CacheData {
26
27
  ttl: number;
27
28
  data: any;
28
29
  }
29
- export class BaseStrategy<TVLInfo, ActionInfo> {
30
+ export class BaseStrategy<TVLInfo, ActionInfo> extends CacheClass {
30
31
  readonly config: IConfig;
31
32
  readonly cache: Map<string, CacheData> = new Map();
32
33
 
33
34
  constructor(config: IConfig) {
35
+ super();
34
36
  this.config = config;
35
37
  }
36
38
 
@@ -49,26 +51,4 @@ export class BaseStrategy<TVLInfo, ActionInfo> {
49
51
  async withdrawCall(amountInfo: ActionInfo, receiver: ContractAddr, owner: ContractAddr): Promise<Call[]> {
50
52
  throw new Error("Not implemented");
51
53
  }
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
- }
73
-
74
54
  }
@@ -2,4 +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';
5
+ export * from './sensei';
6
+ export * from './universal-adapters';
7
+ export * from './universal-strategy';
@@ -126,9 +126,16 @@ export class SenseiVault extends BaseStrategy<
126
126
  collateralInSTRK: number;
127
127
  }> {
128
128
  const CACHE_KEY = 'positionInfo';
129
- if (this.isCacheValid(CACHE_KEY)) {
130
- return this.getCache(CACHE_KEY);
131
- }
129
+ const existingCacheData = this.getCache<{
130
+ collateralXSTRK: Web3Number;
131
+ collateralUSDValue: Web3Number;
132
+ debtSTRK: Web3Number;
133
+ debtUSDValue: Web3Number;
134
+ xSTRKPrice: number;
135
+ collateralInSTRK: number;
136
+ }>(CACHE_KEY);
137
+ if (existingCacheData) return existingCacheData;
138
+
132
139
  const resp = await fetch(
133
140
  `${getTrovesEndpoint()}/vesu/positions?walletAddress=${this.address.address}`,
134
141
  );
@@ -179,9 +186,9 @@ export class SenseiVault extends BaseStrategy<
179
186
 
180
187
  async getSecondaryTokenPriceRelativeToMain(retry = 0): Promise<number> {
181
188
  const CACHE_KEY = 'xSTRKPrice';
182
- if (this.isCacheValid(CACHE_KEY)) {
183
- return this.getCache(CACHE_KEY);
184
- }
189
+ const existingCacheData = this.getCache<number>(CACHE_KEY);
190
+ if (existingCacheData) return existingCacheData;
191
+
185
192
  const params: QuoteRequest = {
186
193
  sellTokenAddress: this.metadata.additionalInfo.secondaryToken.address.address,
187
194
  buyTokenAddress: this.metadata.additionalInfo.mainToken.address.address,
@@ -0,0 +1,13 @@
1
+ import { ContractAddr } from "@/dataTypes";
2
+
3
+ export const SIMPLE_SANITIZER = ContractAddr.from('0x11b59e89b35dfceb3e48ec18c01f8ec569592026c275bcb58e22af9f4dedaac');
4
+
5
+ export function toBigInt(value: string | number): bigint {
6
+ if (typeof value === 'string') {
7
+ return BigInt(value);
8
+ } else if (typeof value === 'number') {
9
+ return BigInt(value.toString());
10
+ } else {
11
+ throw new Error('Value must be a string or number');
12
+ }
13
+ }
@@ -0,0 +1,41 @@
1
+ import { Call, hash, num, shortString } from "starknet";
2
+ import { SIMPLE_SANITIZER, toBigInt } from "./adapter-utils";
3
+ import { ContractAddr } from "@/dataTypes";
4
+ import { LeafData } from "@/utils";
5
+ import { CacheClass } from "@/utils/cacheClass";
6
+
7
+ export interface ManageCall {
8
+ sanitizer: ContractAddr,
9
+ call: {
10
+ contractAddress: ContractAddr,
11
+ selector: string,
12
+ calldata: bigint[]
13
+ }
14
+ }
15
+
16
+ export type GenerateCallFn<T> = (params: T) => ManageCall;
17
+ export type AdapterLeafType<T> = {leaf: LeafData, callConstructor: GenerateCallFn<T>}
18
+ export type LeafAdapterFn<T> = () => AdapterLeafType<T>;
19
+
20
+ export class BaseAdapter extends CacheClass {
21
+
22
+ protected constructSimpleLeafData(params: {
23
+ id: string,
24
+ target: ContractAddr,
25
+ method: string,
26
+ packedArguments: bigint[]
27
+ }): LeafData {
28
+ const { id, target, method, packedArguments } = params;
29
+ return {
30
+ id: BigInt(num.getDecimalString(shortString.encodeShortString(id))),
31
+ readableId: id,
32
+ data: [
33
+ SIMPLE_SANITIZER.toBigInt(), // sanitizer address
34
+ target.toBigInt(), // contract
35
+ toBigInt(hash.getSelectorFromName(method)), // method name
36
+ BigInt(packedArguments.length),
37
+ ...packedArguments
38
+ ]
39
+ };
40
+ }
41
+ }
@@ -0,0 +1,96 @@
1
+ import { ContractAddr, Web3Number } from "@/dataTypes";
2
+ import { LeafData } from "@/utils";
3
+ import { Call, hash, num, shortString, uint256 } from "starknet";
4
+ import { SIMPLE_SANITIZER, toBigInt } from "./adapter-utils";
5
+ import { AdapterLeafType, BaseAdapter, GenerateCallFn, LeafAdapterFn, ManageCall } from "./baseAdapter";
6
+
7
+ export interface FlashloanCallParams {
8
+ amount: Web3Number,
9
+ data: bigint[]
10
+ }
11
+ export interface ApproveCallParams {
12
+ amount: Web3Number,
13
+ }
14
+
15
+ export interface CommonAdapterConfig {
16
+ id: string,
17
+ manager: ContractAddr,
18
+ asset: ContractAddr
19
+ }
20
+
21
+ export class CommonAdapter extends BaseAdapter {
22
+ config: CommonAdapterConfig;
23
+
24
+ constructor(config: CommonAdapterConfig) {
25
+ super();
26
+ this.config = config;
27
+ }
28
+
29
+ getFlashloanAdapter(): AdapterLeafType<FlashloanCallParams> {
30
+ const manageCall = this.getFlashloanCall.bind(this)({amount: Web3Number.fromWei('0', 6), data: []});
31
+ const packedArguments: bigint[] = [
32
+ this.config.manager.toBigInt(), // receiver
33
+ this.config.asset.toBigInt(), // asset
34
+ toBigInt(0), // is legacy false
35
+ ];
36
+ const leaf = this.constructSimpleLeafData({
37
+ id: this.config.id,
38
+ target: manageCall.call.contractAddress,
39
+ method: 'flash_loan',
40
+ packedArguments
41
+ });
42
+ return { leaf, callConstructor: this.getFlashloanCall.bind(this) };
43
+ }
44
+
45
+ getFlashloanCall(params: FlashloanCallParams): ManageCall {
46
+ const uint256Amount = uint256.bnToUint256(params.amount.toWei());
47
+ return {
48
+ sanitizer: SIMPLE_SANITIZER,
49
+ call: {
50
+ contractAddress: this.config.manager,
51
+ selector: hash.getSelectorFromName('flash_loan'),
52
+ calldata: [
53
+ this.config.manager.toBigInt(), // receiver
54
+ this.config.asset.toBigInt(), // asset
55
+ toBigInt(uint256Amount.low.toString()), // amount low
56
+ toBigInt(uint256Amount.high.toString()), // amount high
57
+ toBigInt(0), // is legacy false
58
+ BigInt(params.data.length),
59
+ ...params.data
60
+ ]
61
+ }
62
+ }
63
+ }
64
+
65
+ getApproveAdapter(token: ContractAddr, spender: ContractAddr, id: string): () => AdapterLeafType<ApproveCallParams> {
66
+ return () => ({
67
+ leaf: this.constructSimpleLeafData({
68
+ id: id,
69
+ target: token,
70
+ method: 'approve',
71
+ packedArguments: [
72
+ spender.toBigInt(), // spender
73
+ ]
74
+ }),
75
+ callConstructor: this.getApproveCall(token, spender).bind(this)
76
+ });
77
+ }
78
+
79
+ getApproveCall(token: ContractAddr, spender: ContractAddr) {
80
+ return (params: ApproveCallParams) => {
81
+ const uint256Amount = uint256.bnToUint256(params.amount.toWei());
82
+ return {
83
+ sanitizer: SIMPLE_SANITIZER,
84
+ call: {
85
+ contractAddress: token,
86
+ selector: hash.getSelectorFromName('approve'),
87
+ calldata: [
88
+ spender.toBigInt(), // spender
89
+ toBigInt(uint256Amount.low.toString()), // amount low
90
+ toBigInt(uint256Amount.high.toString()), // amount high
91
+ ]
92
+ }
93
+ }
94
+ }
95
+ }
96
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./baseAdapter";
2
+ export * from "./common-adapter";
3
+ export * from "./vesu-adapter";