@strkfarm/sdk 1.1.39 → 1.1.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/index.browser.global.js +722 -186
- package/dist/index.browser.mjs +721 -183
- package/dist/index.d.ts +126 -13
- package/dist/index.js +722 -183
- package/dist/index.mjs +721 -183
- package/package.json +1 -1
- package/src/global.ts +18 -0
- package/src/modules/avnu.ts +5 -4
- package/src/modules/harvests.ts +16 -15
- package/src/strategies/ekubo-cl-vault.tsx +254 -86
- package/src/strategies/universal-adapters/baseAdapter.ts +184 -2
- package/src/strategies/universal-adapters/vesu-adapter.ts +34 -17
- package/src/strategies/universal-adapters/vesu-supply-only-adapter.ts +322 -0
- package/src/strategies/universal-lst-muliplier-strategy.tsx +226 -67
- package/src/strategies/universal-strategy.tsx +5 -5
- package/src/utils/health-factor-math.ts +83 -0
- package/src/utils/math-utils.ts +150 -0
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Call, hash, num, shortString } from "starknet";
|
|
2
2
|
import { SIMPLE_SANITIZER, toBigInt } from "./adapter-utils";
|
|
3
|
-
import { ContractAddr } from "@/dataTypes";
|
|
3
|
+
import { ContractAddr, Web3Number } from "@/dataTypes";
|
|
4
|
+
import { IConfig, TokenInfo } from "@/interfaces";
|
|
5
|
+
import { PricerBase } from "@/modules/pricerBase";
|
|
4
6
|
import { LeafData } from "@/utils";
|
|
5
7
|
import { CacheClass } from "@/utils/cacheClass";
|
|
6
8
|
|
|
@@ -15,10 +17,96 @@ export interface ManageCall {
|
|
|
15
17
|
|
|
16
18
|
export type GenerateCallFn<T> = (params: T) => ManageCall;
|
|
17
19
|
export type AdapterLeafType<T> = {leaf: LeafData, callConstructor: GenerateCallFn<T>}
|
|
20
|
+
// export type GenerateCallFn<T> = (params: T) => ManageCall[];
|
|
21
|
+
// export type AdapterLeafType<T> = {leaves: LeafData[], callConstructor: GenerateCallFn<T>}
|
|
18
22
|
export type LeafAdapterFn<T> = () => AdapterLeafType<T>;
|
|
19
23
|
|
|
20
|
-
export
|
|
24
|
+
export enum APYType {
|
|
25
|
+
BASE = "base",
|
|
26
|
+
REWARD = "reward",
|
|
27
|
+
LST = "lst"
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface SupportedPosition {
|
|
31
|
+
asset: TokenInfo,
|
|
32
|
+
isDebt: boolean
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface BaseAdapterConfig {
|
|
36
|
+
baseToken: TokenInfo,
|
|
37
|
+
supportedPositions: SupportedPosition[],
|
|
38
|
+
networkConfig: IConfig,
|
|
39
|
+
pricer: PricerBase,
|
|
40
|
+
vaultAllocator: ContractAddr
|
|
41
|
+
vaultAddress: ContractAddr
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export type PositionAPY = { apy: number, type: APYType };
|
|
45
|
+
export type PositionInfo = {
|
|
46
|
+
amount: Web3Number,
|
|
47
|
+
usdValue: number,
|
|
48
|
+
remarks?: string,
|
|
49
|
+
apy: PositionAPY
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// export abstract class BaseAdapter<T1, T2> extends CacheClass {
|
|
53
|
+
export abstract class BaseAdapter extends CacheClass {
|
|
54
|
+
|
|
55
|
+
// readonly config: BaseAdapterConfig;
|
|
56
|
+
|
|
57
|
+
// constructor(config: BaseAdapterConfig) {
|
|
58
|
+
// super();
|
|
59
|
+
// this.config = config;
|
|
60
|
+
// }
|
|
61
|
+
|
|
62
|
+
constructor() {
|
|
63
|
+
super();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// /**
|
|
67
|
+
// * Loop through all supported positions and return amount, usd value, remarks and apy for each
|
|
68
|
+
// */
|
|
69
|
+
// async getPositions(): Promise<PositionInfo[]> {
|
|
70
|
+
// const results: PositionInfo[] = [];
|
|
71
|
+
// for (const supported of this.config.supportedPositions) {
|
|
72
|
+
// const amount = await this.getPosition(supported);
|
|
73
|
+
// const usdValue = await this.getUSDValue(supported.asset, amount);
|
|
74
|
+
// const apy = await this.getAPY(supported);
|
|
75
|
+
// results.push({ amount, usdValue, apy });
|
|
76
|
+
// }
|
|
77
|
+
// return results;
|
|
78
|
+
// }
|
|
21
79
|
|
|
80
|
+
// /**
|
|
81
|
+
// * Implemented by child adapters to compute APY for a given supported position
|
|
82
|
+
// */
|
|
83
|
+
// protected abstract getAPY(supportedPosition: SupportedPosition): Promise<PositionAPY>;
|
|
84
|
+
|
|
85
|
+
// /**
|
|
86
|
+
// * Implemented by child adapters to fetch amount for a given supported position
|
|
87
|
+
// */
|
|
88
|
+
// protected abstract getPosition(supportedPosition: SupportedPosition): Promise<Web3Number>;
|
|
89
|
+
|
|
90
|
+
// /**
|
|
91
|
+
// * Implemented by child adapters to calculate maximum deposit positions
|
|
92
|
+
// * @param amount Optional amount in baseToken to deposit
|
|
93
|
+
// */
|
|
94
|
+
// protected abstract maxDeposit(amount?: Web3Number): Promise<PositionInfo[]>;
|
|
95
|
+
|
|
96
|
+
// /**
|
|
97
|
+
// * Implemented by child adapters to calculate maximum withdraw positions
|
|
98
|
+
// */
|
|
99
|
+
// protected abstract maxWithdraw(): Promise<PositionInfo[]>;
|
|
100
|
+
|
|
101
|
+
// /**
|
|
102
|
+
// * Uses pricer to convert an amount of an asset to USD value
|
|
103
|
+
// */
|
|
104
|
+
// protected async getUSDValue(asset: TokenInfo, amount: Web3Number): Promise<number> {
|
|
105
|
+
// const priceInfo = await this.config.pricer.getPrice(asset.symbol);
|
|
106
|
+
// return amount.toNumber() * priceInfo.price;
|
|
107
|
+
// }
|
|
108
|
+
|
|
109
|
+
|
|
22
110
|
protected constructSimpleLeafData(params: {
|
|
23
111
|
id: string,
|
|
24
112
|
target: ContractAddr,
|
|
@@ -38,4 +126,98 @@ export class BaseAdapter extends CacheClass {
|
|
|
38
126
|
]
|
|
39
127
|
};
|
|
40
128
|
}
|
|
129
|
+
|
|
130
|
+
// /**
|
|
131
|
+
// * Implementor must provide target/method/packedArguments/sanitizer for deposit leaf construction
|
|
132
|
+
// */
|
|
133
|
+
// protected abstract _getDepositLeaf(): {
|
|
134
|
+
// target: ContractAddr,
|
|
135
|
+
// method: string,
|
|
136
|
+
// packedArguments: bigint[],
|
|
137
|
+
// sanitizer: ContractAddr,
|
|
138
|
+
// id: string
|
|
139
|
+
// }[];
|
|
140
|
+
|
|
141
|
+
// /**
|
|
142
|
+
// * Implementor must provide target/method/packedArguments/sanitizer for withdraw leaf construction
|
|
143
|
+
// */
|
|
144
|
+
// protected abstract _getWithdrawLeaf(): {
|
|
145
|
+
// target: ContractAddr,
|
|
146
|
+
// method: string,
|
|
147
|
+
// packedArguments: bigint[],
|
|
148
|
+
// sanitizer: ContractAddr,
|
|
149
|
+
// id: string
|
|
150
|
+
// }[];
|
|
151
|
+
|
|
152
|
+
// /**
|
|
153
|
+
// * Returns deposit leaf adapter using configured proof id
|
|
154
|
+
// */
|
|
155
|
+
// getDepositLeaf(): AdapterLeafType<T1> {
|
|
156
|
+
// const leafConfigs = this._getDepositLeaf();
|
|
157
|
+
// const leaves = leafConfigs.map(config => {
|
|
158
|
+
// const { target, method, packedArguments, sanitizer, id } = config;
|
|
159
|
+
// const leaf = this.constructSimpleLeafData({
|
|
160
|
+
// id: id,
|
|
161
|
+
// target,
|
|
162
|
+
// method,
|
|
163
|
+
// packedArguments
|
|
164
|
+
// }, sanitizer);
|
|
165
|
+
// return leaf;
|
|
166
|
+
// });
|
|
167
|
+
// return { leaves, callConstructor: this.getDepositCall.bind(this) as unknown as GenerateCallFn<T1> };
|
|
168
|
+
// }
|
|
169
|
+
|
|
170
|
+
// /**
|
|
171
|
+
// * Returns withdraw leaf adapter using configured proof id
|
|
172
|
+
// */
|
|
173
|
+
// getWithdrawLeaf(): AdapterLeafType<T2> {
|
|
174
|
+
// const leafConfigs = this._getWithdrawLeaf();
|
|
175
|
+
// const leaves = leafConfigs.map(config => {
|
|
176
|
+
// const { target, method, packedArguments, sanitizer, id } = config;
|
|
177
|
+
// const leaf = this.constructSimpleLeafData({
|
|
178
|
+
// id: id,
|
|
179
|
+
// target,
|
|
180
|
+
// method,
|
|
181
|
+
// packedArguments
|
|
182
|
+
// }, sanitizer ?? SIMPLE_SANITIZER);
|
|
183
|
+
// return leaf;
|
|
184
|
+
// });
|
|
185
|
+
// return { leaves, callConstructor: this.getWithdrawCall.bind(this) as unknown as GenerateCallFn<T2> };
|
|
186
|
+
// }
|
|
187
|
+
|
|
188
|
+
// /**
|
|
189
|
+
// * Default deposit callConstructor: expects params as calldata (bigint[])
|
|
190
|
+
// */
|
|
191
|
+
// protected getDepositCall<T1 = bigint[]>(params: T1): ManageCall[] {
|
|
192
|
+
// const leafConfigs = this._getDepositLeaf();
|
|
193
|
+
// return leafConfigs.map(config => {
|
|
194
|
+
// const { target, method, sanitizer } = config;
|
|
195
|
+
// return {
|
|
196
|
+
// sanitizer: sanitizer ?? SIMPLE_SANITIZER,
|
|
197
|
+
// call: {
|
|
198
|
+
// contractAddress: target,
|
|
199
|
+
// selector: hash.getSelectorFromName(method),
|
|
200
|
+
// calldata: params as unknown as bigint[]
|
|
201
|
+
// }
|
|
202
|
+
// };
|
|
203
|
+
// });
|
|
204
|
+
// }
|
|
205
|
+
|
|
206
|
+
// /**
|
|
207
|
+
// * Default withdraw callConstructor: expects params as calldata (bigint[])
|
|
208
|
+
// */
|
|
209
|
+
// protected getWithdrawCall<T2 = bigint[]>(params: T2): ManageCall[] {
|
|
210
|
+
// const leafConfigs = this._getWithdrawLeaf();
|
|
211
|
+
// return leafConfigs.map(config => {
|
|
212
|
+
// const { target, method, sanitizer } = config;
|
|
213
|
+
// return {
|
|
214
|
+
// sanitizer: sanitizer ?? SIMPLE_SANITIZER,
|
|
215
|
+
// call: {
|
|
216
|
+
// contractAddress: target,
|
|
217
|
+
// selector: hash.getSelectorFromName(method),
|
|
218
|
+
// calldata: params as unknown as bigint[]
|
|
219
|
+
// }
|
|
220
|
+
// };
|
|
221
|
+
// });
|
|
222
|
+
// }
|
|
41
223
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { LeafData, logger } from "@/utils"
|
|
2
|
-
import { CairoCustomEnum, Contract, hash, num, RpcProvider, shortString, uint256, Uint256 } from "starknet";
|
|
2
|
+
import { BlockIdentifier, CairoCustomEnum, Contract, hash, num, RpcProvider, shortString, uint256, Uint256 } from "starknet";
|
|
3
3
|
import { SIMPLE_SANITIZER, SIMPLE_SANITIZER_V2, SIMPLE_SANITIZER_VESU_V1_DELEGATIONS, toBigInt, VESU_SINGLETON, VESU_V2_MODIFY_POSITION_SANITIZER } from "./adapter-utils";
|
|
4
4
|
import { ContractAddr, Web3Number } from "@/dataTypes";
|
|
5
5
|
import { AdapterLeafType, BaseAdapter, GenerateCallFn, LeafAdapterFn, ManageCall } from "./baseAdapter";
|
|
@@ -536,7 +536,22 @@ export class VesuAdapter extends BaseAdapter {
|
|
|
536
536
|
}
|
|
537
537
|
const output: any = await contract.call('pair_config', [this.config.collateral.address.address, this.config.debt.address.address]);
|
|
538
538
|
logger.verbose(`${this.config.debt.symbol}::VesuAdapter::getDebtCap debt_cap: ${output.debt_cap.toString()}`);
|
|
539
|
-
|
|
539
|
+
|
|
540
|
+
if (!isV2) {
|
|
541
|
+
throw new Error('getDebtCap is not supported for v1');
|
|
542
|
+
}
|
|
543
|
+
const currentDebt = await this.getCurrentDebtUtilisationAmount(config);
|
|
544
|
+
logger.verbose(`${this.config.debt.symbol}::VesuAdapter::getDebtCap currentDebt: ${currentDebt.toString()}`);
|
|
545
|
+
return Web3Number.fromWei(output.debt_cap.toString(), this.config.debt.decimals).minus(currentDebt);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
async getCurrentDebtUtilisationAmount(config: IConfig) {
|
|
549
|
+
const { contract, isV2 } = await this.getVesuSingletonContract(config, this.config.poolId);
|
|
550
|
+
if (!isV2) {
|
|
551
|
+
throw new Error('getCurrentDebtUtilisationAmount is not supported for v1');
|
|
552
|
+
}
|
|
553
|
+
const output: any = await contract.call('pairs', [this.config.collateral.address.address, this.config.debt.address.address]);
|
|
554
|
+
return new Web3Number((Number(output.total_nominal_debt) / 1e18).toFixed(9), this.config.debt.decimals);
|
|
540
555
|
}
|
|
541
556
|
|
|
542
557
|
async getMaxBorrowableByInterestRate(config: IConfig, asset: TokenInfo, maxBorrowAPY: number) {
|
|
@@ -572,19 +587,20 @@ export class VesuAdapter extends BaseAdapter {
|
|
|
572
587
|
const assetConfig = isV2 ? _assetConfig : _assetConfig['0'];
|
|
573
588
|
const timeDelta = assetConfig.last_updated;
|
|
574
589
|
const lastFullUtilizationRate = assetConfig.last_full_utilization_rate;
|
|
575
|
-
const
|
|
590
|
+
const currentDebt = new Web3Number((Number(assetConfig.total_nominal_debt) / 1e18).toFixed(9), asset.decimals);
|
|
591
|
+
const totalSupply = currentDebt.plus(Web3Number.fromWei(assetConfig.reserve, asset.decimals));
|
|
576
592
|
|
|
577
593
|
const ratePerSecond = BigInt(Math.round(maxBorrowAPY / 365 / 24 / 60 / 60 * Number(SCALE)));
|
|
578
594
|
const maxUtilisation = this.getMaxUtilizationGivenRatePerSecond(interestRateConfig, ratePerSecond, timeDelta, lastFullUtilizationRate);
|
|
579
595
|
logger.verbose(`${asset.symbol}::VesuAdapter::getMaxBorrowableByInterestRate maxUtilisation: ${Number(maxUtilisation) / 1e18}, totalSupply: ${totalSupply.toString()}`);
|
|
580
596
|
|
|
581
597
|
const maxDebtToHave = totalSupply.multipliedBy(Number(maxUtilisation) / 1e18);
|
|
582
|
-
|
|
598
|
+
logger.verbose(`${asset.symbol}::VesuAdapter::getMaxBorrowableByInterestRate currentDebt: ${currentDebt.toString()}, maxDebtToHave: ${maxDebtToHave.toString()}`);
|
|
583
599
|
return maxDebtToHave.minus(currentDebt);
|
|
584
600
|
}
|
|
585
601
|
|
|
586
|
-
async getLTVConfig(config: IConfig) {
|
|
587
|
-
const CACHE_KEY =
|
|
602
|
+
async getLTVConfig(config: IConfig, blockNumber: BlockIdentifier = 'latest') {
|
|
603
|
+
const CACHE_KEY = `ltv_config_${blockNumber}`;
|
|
588
604
|
const cacheData = this.getCache<number>(CACHE_KEY);
|
|
589
605
|
if (cacheData) {
|
|
590
606
|
return cacheData as number;
|
|
@@ -592,10 +608,10 @@ export class VesuAdapter extends BaseAdapter {
|
|
|
592
608
|
const { contract, isV2 } = await this.getVesuSingletonContract(config, this.config.poolId);
|
|
593
609
|
let ltv = 0;
|
|
594
610
|
if (isV2) {
|
|
595
|
-
const output: any = await contract.call('pair_config', [this.config.collateral.address.address, this.config.debt.address.address]);
|
|
611
|
+
const output: any = await contract.call('pair_config', [this.config.collateral.address.address, this.config.debt.address.address], { blockIdentifier: blockNumber });
|
|
596
612
|
ltv = Number(output.max_ltv) / 1e18;
|
|
597
613
|
} else {
|
|
598
|
-
const output: any = await contract.call('ltv_config', [this.config.poolId.address, this.config.collateral.address.address, this.config.debt.address.address]);
|
|
614
|
+
const output: any = await contract.call('ltv_config', [this.config.poolId.address, this.config.collateral.address.address, this.config.debt.address.address], { blockIdentifier: blockNumber });
|
|
599
615
|
ltv = Number(output.max_ltv) / 1e18;
|
|
600
616
|
}
|
|
601
617
|
if (ltv == 0) {
|
|
@@ -605,12 +621,12 @@ export class VesuAdapter extends BaseAdapter {
|
|
|
605
621
|
return this.getCache<number>(CACHE_KEY) as number;
|
|
606
622
|
}
|
|
607
623
|
|
|
608
|
-
async getPositions(config: IConfig): Promise<VaultPosition[]> {
|
|
624
|
+
async getPositions(config: IConfig, blockNumber: BlockIdentifier = 'latest'): Promise<VaultPosition[]> {
|
|
609
625
|
if (!this.pricer) {
|
|
610
626
|
throw new Error('Pricer is not initialized');
|
|
611
627
|
}
|
|
612
628
|
// { '0': { collateral_shares: 0n, nominal_debt: 0n }, '1': 0n, '2': 0n }
|
|
613
|
-
const CACHE_KEY =
|
|
629
|
+
const CACHE_KEY = `positions_${blockNumber}`;
|
|
614
630
|
const cacheData = this.getCache<VaultPosition[]>(CACHE_KEY);
|
|
615
631
|
if (cacheData) {
|
|
616
632
|
return cacheData;
|
|
@@ -623,8 +639,9 @@ export class VesuAdapter extends BaseAdapter {
|
|
|
623
639
|
this.config.collateral.address.address,
|
|
624
640
|
this.config.debt.address.address,
|
|
625
641
|
this.config.vaultAllocator.address
|
|
626
|
-
]);
|
|
642
|
+
], { blockIdentifier: blockNumber });
|
|
627
643
|
|
|
644
|
+
console.log(output)
|
|
628
645
|
const token1Price = await this.pricer.getPrice(this.config.collateral.symbol);
|
|
629
646
|
const token2Price = await this.pricer.getPrice(this.config.debt.symbol);
|
|
630
647
|
logger.verbose(`VesuAdapter::getPositions token1Price: ${token1Price.price}, token2Price: ${token2Price.price}`);
|
|
@@ -646,12 +663,12 @@ export class VesuAdapter extends BaseAdapter {
|
|
|
646
663
|
return value;
|
|
647
664
|
}
|
|
648
665
|
|
|
649
|
-
async getCollateralization(config: IConfig): Promise<Omit<VaultPosition, 'amount'>[]> {
|
|
666
|
+
async getCollateralization(config: IConfig, blockNumber: BlockIdentifier = 'latest'): Promise<Omit<VaultPosition, 'amount'>[]> {
|
|
650
667
|
if (!this.pricer) {
|
|
651
668
|
throw new Error('Pricer is not initialized');
|
|
652
669
|
}
|
|
653
670
|
// { '0': bool, '1': 0n, '2': 0n }
|
|
654
|
-
const CACHE_KEY =
|
|
671
|
+
const CACHE_KEY = `collateralization_${blockNumber}`;
|
|
655
672
|
const cacheData = this.getCache<Omit<VaultPosition, 'amount'>[]>(CACHE_KEY);
|
|
656
673
|
if (cacheData) {
|
|
657
674
|
return cacheData;
|
|
@@ -664,7 +681,7 @@ export class VesuAdapter extends BaseAdapter {
|
|
|
664
681
|
this.config.collateral.address.address,
|
|
665
682
|
this.config.debt.address.address,
|
|
666
683
|
this.config.vaultAllocator.address
|
|
667
|
-
]);
|
|
684
|
+
], { blockIdentifier: blockNumber });
|
|
668
685
|
|
|
669
686
|
// usd values
|
|
670
687
|
const collateralAmount = Web3Number.fromWei(output['1'].toString(), 18);
|
|
@@ -709,9 +726,9 @@ export class VesuAdapter extends BaseAdapter {
|
|
|
709
726
|
}
|
|
710
727
|
}
|
|
711
728
|
|
|
712
|
-
async getHealthFactor() {
|
|
713
|
-
const ltv = await this.getLTVConfig(this.networkConfig
|
|
714
|
-
const collateralisation = await this.getCollateralization(this.networkConfig
|
|
729
|
+
async getHealthFactor(blockNumber: BlockIdentifier = 'latest') {
|
|
730
|
+
const ltv = await this.getLTVConfig(this.networkConfig!, blockNumber);
|
|
731
|
+
const collateralisation = await this.getCollateralization(this.networkConfig!, blockNumber);
|
|
715
732
|
return collateralisation[0].usdValue * ltv / collateralisation[1].usdValue;
|
|
716
733
|
}
|
|
717
734
|
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
import { ContractAddr, Web3Number } from "@/dataTypes";
|
|
2
|
+
import { IConfig, TokenInfo } from "@/interfaces";
|
|
3
|
+
import { PricerBase } from "@/modules/pricerBase";
|
|
4
|
+
import { BaseAdapter, BaseAdapterConfig, SupportedPosition, PositionInfo, PositionAPY, APYType, ManageCall, AdapterLeafType, GenerateCallFn } from "./baseAdapter";
|
|
5
|
+
import { SIMPLE_SANITIZER, toBigInt } from "./adapter-utils";
|
|
6
|
+
import { hash, uint256, Contract } from "starknet";
|
|
7
|
+
import { VesuAdapter } from "./vesu-adapter";
|
|
8
|
+
import { logger } from "@/utils";
|
|
9
|
+
|
|
10
|
+
export interface VesuSupplyOnlyAdapterConfig extends BaseAdapterConfig {
|
|
11
|
+
vTokenContract: ContractAddr;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface DepositParams {
|
|
15
|
+
amount: Web3Number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface WithdrawParams {
|
|
19
|
+
amount: Web3Number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class VesuSupplyOnlyAdapter extends BaseAdapter<any, any> {
|
|
23
|
+
readonly config: VesuSupplyOnlyAdapterConfig;
|
|
24
|
+
|
|
25
|
+
constructor(config: VesuSupplyOnlyAdapterConfig) {
|
|
26
|
+
super(config);
|
|
27
|
+
this.config = config;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
protected async getAPY(supportedPosition: SupportedPosition): Promise<PositionAPY> {
|
|
31
|
+
const CACHE_KEY = `apy_${this.config.vTokenContract.address}`;
|
|
32
|
+
const cacheData = this.getCache<PositionAPY>(CACHE_KEY);
|
|
33
|
+
if (cacheData) {
|
|
34
|
+
return cacheData;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
// Get Vesu pools to find APY for the underlying asset
|
|
39
|
+
const allVesuPools = await VesuAdapter.getVesuPools();
|
|
40
|
+
const baseToken = this.config.baseToken;
|
|
41
|
+
|
|
42
|
+
// Find the pool that contains our base token
|
|
43
|
+
const pool = allVesuPools.pools.find(p => {
|
|
44
|
+
return p.assets.some((asset: any) =>
|
|
45
|
+
asset.symbol.toLowerCase() === baseToken.symbol.toLowerCase()
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
if (!pool) {
|
|
50
|
+
logger.warn(`VesuSupplyOnlyAdapter: Pool not found for token ${baseToken.symbol}`);
|
|
51
|
+
return {
|
|
52
|
+
apy: 0,
|
|
53
|
+
type: APYType.BASE
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Find the asset stats for our token
|
|
58
|
+
const assetStats = pool.assets.find((a: any) =>
|
|
59
|
+
a.symbol.toLowerCase() === baseToken.symbol.toLowerCase()
|
|
60
|
+
)?.stats;
|
|
61
|
+
|
|
62
|
+
if (!assetStats) {
|
|
63
|
+
logger.warn(`VesuSupplyOnlyAdapter: Asset stats not found for token ${baseToken.symbol}`);
|
|
64
|
+
return {
|
|
65
|
+
apy: 0,
|
|
66
|
+
type: APYType.BASE
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Get supply APY (divide by 1e18 as it's in wei)
|
|
71
|
+
const supplyApy = Number(assetStats.supplyApy?.value || 0) / 1e18;
|
|
72
|
+
|
|
73
|
+
// Get LST APR if applicable (for LST tokens)
|
|
74
|
+
let lstAPY = 0;
|
|
75
|
+
if (baseToken.symbol === 'STRK' || baseToken.symbol === 'ETH') {
|
|
76
|
+
// This would need to be implemented based on your LST APR service
|
|
77
|
+
// For now, using a placeholder
|
|
78
|
+
lstAPY = 0;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const totalAPY = supplyApy + lstAPY;
|
|
82
|
+
|
|
83
|
+
const result = {
|
|
84
|
+
apy: totalAPY,
|
|
85
|
+
type: APYType.BASE
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
this.setCache(CACHE_KEY, result, 300000); // Cache for 5 minutes
|
|
89
|
+
return result;
|
|
90
|
+
} catch (error) {
|
|
91
|
+
logger.error(`VesuSupplyOnlyAdapter: Error getting APY for ${supportedPosition.asset.symbol}:`, error);
|
|
92
|
+
return {
|
|
93
|
+
apy: 0,
|
|
94
|
+
type: APYType.BASE
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
protected async getPosition(supportedPosition: SupportedPosition): Promise<Web3Number> {
|
|
100
|
+
const CACHE_KEY = `position_${this.config.vTokenContract.address}`;
|
|
101
|
+
const cacheData = this.getCache<Web3Number>(CACHE_KEY);
|
|
102
|
+
if (cacheData) {
|
|
103
|
+
return cacheData;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
// Create contract instance for the vToken
|
|
108
|
+
const vTokenContract = new Contract({
|
|
109
|
+
abi: [], // We only need basic ERC20 methods
|
|
110
|
+
address: this.config.vTokenContract.address,
|
|
111
|
+
providerOrAccount: this.config.networkConfig.provider
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// Get the vault allocator's balance (shares) in the vToken contract
|
|
115
|
+
const shares = await vTokenContract.balanceOf(this.config.vaultAllocator.address);
|
|
116
|
+
|
|
117
|
+
// Convert shares to assets using convert_to_assets
|
|
118
|
+
const assets = await vTokenContract.convert_to_assets(
|
|
119
|
+
uint256.bnToUint256(shares)
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
const result = Web3Number.fromWei(
|
|
123
|
+
assets.toString(),
|
|
124
|
+
supportedPosition.asset.decimals
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
this.setCache(CACHE_KEY, result, 60000); // Cache for 1 minute
|
|
128
|
+
return result;
|
|
129
|
+
} catch (error) {
|
|
130
|
+
logger.error(`VesuSupplyOnlyAdapter: Error getting position for ${supportedPosition.asset.symbol}:`, error);
|
|
131
|
+
return new Web3Number('0', supportedPosition.asset.decimals);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
protected async maxDeposit(amount?: Web3Number): Promise<PositionInfo[]> {
|
|
136
|
+
const baseToken = this.config.baseToken;
|
|
137
|
+
// todo for assets with some borrowing on Vesu, the yield wont
|
|
138
|
+
// remain same as supply increases. So need to account for that.
|
|
139
|
+
|
|
140
|
+
if (!amount) {
|
|
141
|
+
// Return infinity for max deposit when no amount specified
|
|
142
|
+
return [{
|
|
143
|
+
amount: new Web3Number('999999999999999999999999999', baseToken.decimals),
|
|
144
|
+
usdValue: 999999999999999999999999999,
|
|
145
|
+
remarks: "Max deposit (infinity)",
|
|
146
|
+
apy: await this.getAPY({ asset: baseToken, isDebt: false })
|
|
147
|
+
}];
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Return position info based on input amount
|
|
151
|
+
const usdValue = await this.getUSDValue(baseToken, amount);
|
|
152
|
+
return [{
|
|
153
|
+
amount,
|
|
154
|
+
usdValue,
|
|
155
|
+
remarks: "Deposit amount",
|
|
156
|
+
apy: await this.getAPY({ asset: baseToken, isDebt: false })
|
|
157
|
+
}];
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
protected async maxWithdraw(): Promise<PositionInfo[]> {
|
|
161
|
+
const baseToken = this.config.baseToken;
|
|
162
|
+
const currentPosition = await this.getPosition({ asset: baseToken, isDebt: false });
|
|
163
|
+
|
|
164
|
+
// Return the current position as max withdraw
|
|
165
|
+
const usdValue = await this.getUSDValue(baseToken, currentPosition);
|
|
166
|
+
return [{
|
|
167
|
+
amount: currentPosition,
|
|
168
|
+
usdValue,
|
|
169
|
+
remarks: "Max withdraw",
|
|
170
|
+
apy: await this.getAPY({ asset: baseToken, isDebt: false })
|
|
171
|
+
}];
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
protected _getDepositLeaf(): {
|
|
175
|
+
target: ContractAddr,
|
|
176
|
+
method: string,
|
|
177
|
+
packedArguments: bigint[],
|
|
178
|
+
sanitizer: ContractAddr,
|
|
179
|
+
id: string
|
|
180
|
+
}[] {
|
|
181
|
+
const baseToken = this.config.baseToken;
|
|
182
|
+
const vTokenContract = this.config.vTokenContract;
|
|
183
|
+
|
|
184
|
+
return [
|
|
185
|
+
// Approval step
|
|
186
|
+
{
|
|
187
|
+
target: baseToken.address,
|
|
188
|
+
method: 'approve',
|
|
189
|
+
packedArguments: [
|
|
190
|
+
vTokenContract.toBigInt(), // spender
|
|
191
|
+
],
|
|
192
|
+
sanitizer: SIMPLE_SANITIZER,
|
|
193
|
+
id: `approve_deposit_vtoken_${this.config.vTokenContract.address}`
|
|
194
|
+
},
|
|
195
|
+
// Deposit step
|
|
196
|
+
{
|
|
197
|
+
target: vTokenContract,
|
|
198
|
+
method: 'deposit',
|
|
199
|
+
packedArguments: [
|
|
200
|
+
this.config.vaultAllocator.toBigInt(),
|
|
201
|
+
],
|
|
202
|
+
sanitizer: SIMPLE_SANITIZER,
|
|
203
|
+
id: `deposit_vtoken_${this.config.vTokenContract.address}`
|
|
204
|
+
}
|
|
205
|
+
];
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
protected _getWithdrawLeaf(): {
|
|
209
|
+
target: ContractAddr,
|
|
210
|
+
method: string,
|
|
211
|
+
packedArguments: bigint[],
|
|
212
|
+
sanitizer: ContractAddr,
|
|
213
|
+
id: string
|
|
214
|
+
}[] {
|
|
215
|
+
const vTokenContract = this.config.vTokenContract;
|
|
216
|
+
|
|
217
|
+
return [
|
|
218
|
+
// Withdraw step
|
|
219
|
+
{
|
|
220
|
+
target: vTokenContract,
|
|
221
|
+
method: 'withdraw',
|
|
222
|
+
packedArguments: [
|
|
223
|
+
this.config.vaultAllocator.toBigInt(),
|
|
224
|
+
this.config.vaultAllocator.toBigInt(),
|
|
225
|
+
],
|
|
226
|
+
sanitizer: SIMPLE_SANITIZER,
|
|
227
|
+
id: `withdraw_vtoken_${this.config.vTokenContract.address}`
|
|
228
|
+
}
|
|
229
|
+
];
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
getDepositAdapter(): AdapterLeafType<DepositParams> {
|
|
233
|
+
const leafConfigs = this._getDepositLeaf();
|
|
234
|
+
const leaves = leafConfigs.map(config => {
|
|
235
|
+
const { target, method, packedArguments, sanitizer, id } = config;
|
|
236
|
+
const leaf = this.constructSimpleLeafData({
|
|
237
|
+
id: id,
|
|
238
|
+
target,
|
|
239
|
+
method,
|
|
240
|
+
packedArguments
|
|
241
|
+
}, sanitizer);
|
|
242
|
+
return leaf;
|
|
243
|
+
});
|
|
244
|
+
return { leaves, callConstructor: this.createDepositCall.bind(this) as unknown as GenerateCallFn<DepositParams> };
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
getWithdrawAdapter(): AdapterLeafType<WithdrawParams> {
|
|
248
|
+
const leafConfigs = this._getWithdrawLeaf();
|
|
249
|
+
const leaves = leafConfigs.map(config => {
|
|
250
|
+
const { target, method, packedArguments, sanitizer, id } = config;
|
|
251
|
+
const leaf = this.constructSimpleLeafData({
|
|
252
|
+
id: id,
|
|
253
|
+
target,
|
|
254
|
+
method,
|
|
255
|
+
packedArguments
|
|
256
|
+
}, sanitizer);
|
|
257
|
+
return leaf;
|
|
258
|
+
});
|
|
259
|
+
return { leaves, callConstructor: this.createWithdrawCall.bind(this) as unknown as GenerateCallFn<WithdrawParams> };
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
private createDepositCall(params: DepositParams): ManageCall[] {
|
|
263
|
+
const baseToken = this.config.baseToken;
|
|
264
|
+
const vTokenContract = this.config.vTokenContract;
|
|
265
|
+
|
|
266
|
+
const amount = params.amount;
|
|
267
|
+
const uint256Amount = uint256.bnToUint256(amount.toWei());
|
|
268
|
+
|
|
269
|
+
return [
|
|
270
|
+
// Approval call
|
|
271
|
+
{
|
|
272
|
+
sanitizer: SIMPLE_SANITIZER,
|
|
273
|
+
call: {
|
|
274
|
+
contractAddress: baseToken.address,
|
|
275
|
+
selector: hash.getSelectorFromName('approve'),
|
|
276
|
+
calldata: [
|
|
277
|
+
vTokenContract.toBigInt(), // spender
|
|
278
|
+
toBigInt(uint256Amount.low.toString()), // amount low
|
|
279
|
+
toBigInt(uint256Amount.high.toString()), // amount high
|
|
280
|
+
]
|
|
281
|
+
}
|
|
282
|
+
},
|
|
283
|
+
// Deposit call
|
|
284
|
+
{
|
|
285
|
+
sanitizer: SIMPLE_SANITIZER,
|
|
286
|
+
call: {
|
|
287
|
+
contractAddress: vTokenContract,
|
|
288
|
+
selector: hash.getSelectorFromName('deposit'),
|
|
289
|
+
calldata: [
|
|
290
|
+
toBigInt(uint256Amount.low.toString()), // amount low
|
|
291
|
+
toBigInt(uint256Amount.high.toString()), // amount high
|
|
292
|
+
this.config.vaultAllocator.toBigInt(),
|
|
293
|
+
]
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
];
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
private createWithdrawCall(params: WithdrawParams): ManageCall[] {
|
|
300
|
+
const vTokenContract = this.config.vTokenContract;
|
|
301
|
+
|
|
302
|
+
const amount = params.amount;
|
|
303
|
+
const uint256Amount = uint256.bnToUint256(amount.toWei());
|
|
304
|
+
|
|
305
|
+
return [
|
|
306
|
+
// Withdraw call
|
|
307
|
+
{
|
|
308
|
+
sanitizer: SIMPLE_SANITIZER,
|
|
309
|
+
call: {
|
|
310
|
+
contractAddress: vTokenContract,
|
|
311
|
+
selector: hash.getSelectorFromName('withdraw'),
|
|
312
|
+
calldata: [
|
|
313
|
+
toBigInt(uint256Amount.low.toString()), // amount low
|
|
314
|
+
toBigInt(uint256Amount.high.toString()), // amount high
|
|
315
|
+
this.config.vaultAllocator.toBigInt(),
|
|
316
|
+
this.config.vaultAllocator.toBigInt(),
|
|
317
|
+
]
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
];
|
|
321
|
+
}
|
|
322
|
+
}
|