@strkfarm/sdk 2.0.0-dev.27 → 2.0.0-dev.28
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/cli.js +190 -36
- package/dist/cli.mjs +188 -34
- package/dist/index.browser.global.js +79130 -49357
- package/dist/index.browser.mjs +18039 -11434
- package/dist/index.d.ts +2869 -898
- package/dist/index.js +19036 -12210
- package/dist/index.mjs +18942 -12161
- package/package.json +1 -1
- package/src/data/avnu.abi.json +840 -0
- package/src/data/ekubo-price-fethcer.abi.json +265 -0
- package/src/dataTypes/_bignumber.ts +13 -4
- package/src/dataTypes/index.ts +3 -2
- package/src/dataTypes/mynumber.ts +141 -0
- package/src/global.ts +76 -41
- package/src/index.browser.ts +2 -1
- package/src/interfaces/common.tsx +167 -2
- package/src/modules/ExtendedWrapperSDk/types.ts +26 -4
- package/src/modules/ExtendedWrapperSDk/wrapper.ts +110 -67
- package/src/modules/apollo-client-config.ts +28 -0
- package/src/modules/avnu.ts +4 -4
- package/src/modules/ekubo-pricer.ts +79 -0
- package/src/modules/ekubo-quoter.ts +46 -30
- package/src/modules/erc20.ts +17 -0
- package/src/modules/harvests.ts +43 -29
- package/src/modules/pragma.ts +23 -8
- package/src/modules/pricer-from-api.ts +156 -15
- package/src/modules/pricer-lst.ts +1 -1
- package/src/modules/pricer.ts +40 -4
- package/src/modules/pricerBase.ts +2 -1
- package/src/node/deployer.ts +36 -1
- package/src/node/pricer-redis.ts +2 -1
- package/src/strategies/base-strategy.ts +78 -10
- package/src/strategies/ekubo-cl-vault.tsx +906 -347
- package/src/strategies/factory.ts +159 -0
- package/src/strategies/index.ts +6 -1
- package/src/strategies/registry.ts +239 -0
- package/src/strategies/sensei.ts +335 -7
- package/src/strategies/svk-strategy.ts +97 -27
- package/src/strategies/types.ts +4 -0
- package/src/strategies/universal-adapters/adapter-utils.ts +2 -1
- package/src/strategies/universal-adapters/avnu-adapter.ts +177 -268
- package/src/strategies/universal-adapters/baseAdapter.ts +263 -251
- package/src/strategies/universal-adapters/common-adapter.ts +206 -203
- package/src/strategies/universal-adapters/extended-adapter.ts +155 -336
- package/src/strategies/universal-adapters/index.ts +9 -8
- package/src/strategies/universal-adapters/token-transfer-adapter.ts +200 -0
- package/src/strategies/universal-adapters/usdc<>usdce-adapter.ts +200 -0
- package/src/strategies/universal-adapters/vesu-adapter.ts +110 -75
- package/src/strategies/universal-adapters/vesu-modify-position-adapter.ts +476 -0
- package/src/strategies/universal-adapters/vesu-multiply-adapter.ts +762 -844
- package/src/strategies/universal-adapters/vesu-position-common.ts +251 -0
- package/src/strategies/universal-adapters/vesu-supply-only-adapter.ts +18 -3
- package/src/strategies/universal-lst-muliplier-strategy.tsx +396 -204
- package/src/strategies/universal-strategy.tsx +1426 -1178
- package/src/strategies/vesu-extended-strategy/services/executionService.ts +2251 -0
- package/src/strategies/vesu-extended-strategy/services/extended-vesu-state-manager.ts +2941 -0
- package/src/strategies/vesu-extended-strategy/services/operationService.ts +12 -1
- package/src/strategies/vesu-extended-strategy/types/transaction-metadata.ts +52 -0
- package/src/strategies/vesu-extended-strategy/utils/config.runtime.ts +1 -0
- package/src/strategies/vesu-extended-strategy/utils/constants.ts +2 -0
- package/src/strategies/vesu-extended-strategy/utils/helper.ts +158 -124
- package/src/strategies/vesu-extended-strategy/vesu-extended-strategy.tsx +377 -1788
- package/src/strategies/vesu-rebalance.tsx +255 -152
- package/src/utils/health-factor-math.ts +4 -1
- package/src/utils/index.ts +2 -1
- package/src/utils/logger.browser.ts +22 -4
- package/src/utils/logger.node.ts +259 -24
- package/src/utils/starknet-call-parser.ts +1036 -0
- package/src/utils/strategy-utils.ts +61 -0
- package/src/strategies/universal-adapters/unused-balance-adapter.ts +0 -109
|
@@ -3,37 +3,37 @@ import {
|
|
|
3
3
|
IConfig,
|
|
4
4
|
IStrategyMetadata,
|
|
5
5
|
TokenInfo,
|
|
6
|
+
AuditStatus,
|
|
7
|
+
SourceCodeType,
|
|
8
|
+
AccessControlType,
|
|
9
|
+
InstantWithdrawalVault,
|
|
10
|
+
VaultType,
|
|
11
|
+
VaultPosition,
|
|
6
12
|
} from "@/interfaces";
|
|
7
13
|
import {
|
|
8
14
|
UNIVERSAL_MANAGE_IDS,
|
|
9
15
|
UniversalStrategySettings,
|
|
10
16
|
} from "../universal-strategy";
|
|
11
17
|
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
} from "./utils/helper";
|
|
18
|
+
ExtendedSVKVesuStateManager,
|
|
19
|
+
StateManagerConfig,
|
|
20
|
+
} from "./services/extended-vesu-state-manager";
|
|
21
|
+
import { ExecutionService, ExecutionConfig } from "./services/executionService";
|
|
17
22
|
import { logger } from "@/utils";
|
|
18
23
|
import { AUDIT_URL } from "../universal-lst-muliplier-strategy";
|
|
19
24
|
import { getNoRiskTags } from "@/interfaces";
|
|
20
25
|
import { _riskFactor } from "../universal-lst-muliplier-strategy";
|
|
21
26
|
import {
|
|
22
|
-
BUFFER_USDC_IN_WITHDRAWAL,
|
|
23
27
|
LIMIT_BALANCE,
|
|
24
|
-
LIMIT_BALANCE_VALUE,
|
|
25
|
-
MAX_LTV_BTC_USDC,
|
|
26
|
-
MINIMUM_EXTENDED_POSITION_SIZE,
|
|
27
28
|
USDC_TOKEN_DECIMALS,
|
|
28
29
|
WALLET_ADDRESS,
|
|
29
30
|
WBTC_TOKEN_DECIMALS,
|
|
30
31
|
} from "./utils/constants";
|
|
31
|
-
import {
|
|
32
|
+
import { ExecutionCallback } from "./types/transaction-metadata";
|
|
32
33
|
import { PricerBase } from "@/modules/pricerBase";
|
|
33
34
|
import { ContractAddr, Web3Number } from "@/dataTypes";
|
|
34
35
|
import { Global } from "@/global";
|
|
35
36
|
import { ERC20 } from "@/modules";
|
|
36
|
-
import { Balance, OrderSide } from "@/modules/ExtendedWrapperSDk";
|
|
37
37
|
import { Protocols } from "@/interfaces";
|
|
38
38
|
import { MINIMUM_WBTC_DIFFERENCE_FOR_AVNU_SWAP } from "./utils/constants";
|
|
39
39
|
import {
|
|
@@ -49,12 +49,11 @@ import { VesuPools } from "../universal-adapters";
|
|
|
49
49
|
import {
|
|
50
50
|
BaseAdapterConfig,
|
|
51
51
|
CommonAdapter,
|
|
52
|
-
|
|
52
|
+
TokenTransferAdapter,
|
|
53
|
+
VesuModifyPositionAdapter,
|
|
53
54
|
VesuMultiplyAdapter,
|
|
54
55
|
} from "../universal-adapters";
|
|
55
|
-
import { Operations } from "./services/operationService";
|
|
56
56
|
import {
|
|
57
|
-
AVNU_EXCHANGE,
|
|
58
57
|
AVNU_QUOTE_URL,
|
|
59
58
|
AVNU_MIDDLEWARE,
|
|
60
59
|
EXTENDED_CONTRACT,
|
|
@@ -63,22 +62,11 @@ import { PricerFromApi } from "@/modules";
|
|
|
63
62
|
import { ExtendedAdapter } from "../universal-adapters/extended-adapter";
|
|
64
63
|
import { SVKStrategy } from "../svk-strategy";
|
|
65
64
|
import { AvnuAdapter } from "../universal-adapters/avnu-adapter";
|
|
66
|
-
import {
|
|
67
|
-
calculateAmountDistribution,
|
|
68
|
-
calculateAmountDistributionForWithdrawal,
|
|
69
|
-
calculateVesuLeverage,
|
|
70
|
-
calculateVesUPositionSizeGivenExtended,
|
|
71
|
-
} from "./utils/helper";
|
|
72
65
|
import { SingleTokenInfo } from "../base-strategy";
|
|
73
|
-
import {
|
|
74
|
-
import {
|
|
75
|
-
import {
|
|
76
|
-
TransactionMetadata,
|
|
77
|
-
TransactionResult,
|
|
78
|
-
} from "./types/transaction-metadata";
|
|
66
|
+
import { UsdcToUsdceAdapter } from "../universal-adapters/usdc<>usdce-adapter";
|
|
67
|
+
import { VesuConfig } from "./utils/config.runtime";
|
|
79
68
|
|
|
80
|
-
export interface VesuExtendedStrategySettings
|
|
81
|
-
extends UniversalStrategySettings {
|
|
69
|
+
export interface VesuExtendedStrategySettings extends UniversalStrategySettings {
|
|
82
70
|
underlyingToken: TokenInfo;
|
|
83
71
|
borrowable_assets: TokenInfo[];
|
|
84
72
|
targetHealthFactor: number;
|
|
@@ -90,33 +78,81 @@ export interface VesuExtendedStrategySettings
|
|
|
90
78
|
}
|
|
91
79
|
|
|
92
80
|
export class VesuExtendedMultiplierStrategy<
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
81
|
+
S extends VesuExtendedStrategySettings,
|
|
82
|
+
> extends SVKStrategy<S> {
|
|
83
|
+
public wbtcToken: TokenInfo;
|
|
84
|
+
public usdcToken: TokenInfo;
|
|
85
|
+
public usdceToken: TokenInfo;
|
|
86
|
+
public readonly stateManager: ExtendedSVKVesuStateManager;
|
|
87
|
+
|
|
98
88
|
constructor(
|
|
99
89
|
config: IConfig,
|
|
100
90
|
pricer: PricerBase,
|
|
101
|
-
metadata: IStrategyMetadata<S
|
|
91
|
+
metadata: IStrategyMetadata<S>,
|
|
102
92
|
) {
|
|
103
93
|
super(config, pricer, metadata);
|
|
104
94
|
this.metadata.additionalInfo.adapters.forEach((adapter) => {
|
|
105
95
|
adapter.adapter.config.networkConfig = this.config;
|
|
106
96
|
adapter.adapter.config.pricer = this.pricer;
|
|
107
|
-
if ((adapter.adapter as
|
|
108
|
-
(adapter.adapter as
|
|
97
|
+
if ((adapter.adapter as any)._vesuAdapter) {
|
|
98
|
+
(adapter.adapter as any)._vesuAdapter.networkConfig =
|
|
109
99
|
this.config;
|
|
110
|
-
(adapter.adapter as
|
|
100
|
+
(adapter.adapter as any)._vesuAdapter.pricer =
|
|
111
101
|
this.pricer;
|
|
112
102
|
}
|
|
113
103
|
});
|
|
104
|
+
// todo check if this can be generalized
|
|
105
|
+
this.wbtcToken = Global.getDefaultTokens().find((token) => token.symbol === "WBTC")!;
|
|
106
|
+
this.usdcToken = this.metadata.additionalInfo.borrowable_assets[0]!;
|
|
107
|
+
this.usdceToken = Global.getDefaultTokens().find(
|
|
108
|
+
(token) => token.symbol === "USDC.e",
|
|
109
|
+
)!;
|
|
110
|
+
|
|
111
|
+
this.stateManager = this._initializeStateManager();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Extracts the required adapters from metadata and constructs the
|
|
116
|
+
* state manager used by shouldInvest / handleWithdraw.
|
|
117
|
+
*/
|
|
118
|
+
private _initializeStateManager(): ExtendedSVKVesuStateManager {
|
|
119
|
+
const vesuAdapters = this.metadata.additionalInfo.adapters
|
|
120
|
+
.filter((a) => a.adapter.name === VesuMultiplyAdapter.name)
|
|
121
|
+
.map((a) => a.adapter as VesuMultiplyAdapter);
|
|
122
|
+
|
|
123
|
+
const extendedAdapterEntry = this.metadata.additionalInfo.adapters.find(
|
|
124
|
+
(a) => a.adapter.name === ExtendedAdapter.name,
|
|
125
|
+
);
|
|
126
|
+
if (!extendedAdapterEntry) {
|
|
127
|
+
throw new Error(
|
|
128
|
+
`${this.getTag()} ExtendedAdapter not found in adapters — cannot initialise state manager.`,
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const stateManagerConfig: StateManagerConfig = {
|
|
133
|
+
pricer: this.pricer,
|
|
134
|
+
networkConfig: this.config,
|
|
135
|
+
vesuAdapters,
|
|
136
|
+
extendedAdapter: extendedAdapterEntry.adapter as ExtendedAdapter,
|
|
137
|
+
vaultAllocator: this.metadata.additionalInfo.vaultAllocator,
|
|
138
|
+
walletAddress: this.metadata.additionalInfo.walletAddress,
|
|
139
|
+
assetToken: Global.getDefaultTokens().find((token) => token.symbol === 'USDC')!, // ! TODO change to asset() latest
|
|
140
|
+
usdceToken: this.usdceToken,
|
|
141
|
+
collateralToken: this.wbtcToken,
|
|
142
|
+
limitBalanceBufferFactor: LIMIT_BALANCE,
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
return new ExtendedSVKVesuStateManager(stateManagerConfig);
|
|
114
146
|
}
|
|
115
147
|
|
|
116
148
|
getTag() {
|
|
117
149
|
return `${VesuExtendedMultiplierStrategy.name}:${this.metadata.name}`;
|
|
118
150
|
}
|
|
119
151
|
|
|
152
|
+
/**
|
|
153
|
+
* Fetches current WBTC (collateral) and USDC (debt) prices from the pricer.
|
|
154
|
+
* Validates that both prices are finite and positive, throwing if not.
|
|
155
|
+
*/
|
|
120
156
|
async getAssetPrices() {
|
|
121
157
|
const wbtcToken = Global.getDefaultTokens().find(
|
|
122
158
|
(token) => token.symbol === "WBTC"
|
|
@@ -126,1640 +162,160 @@ export class VesuExtendedMultiplierStrategy<
|
|
|
126
162
|
)!;
|
|
127
163
|
const collateralPrice = await this.pricer.getPrice(wbtcToken.symbol);
|
|
128
164
|
const debtPrice = await this.pricer.getPrice(usdcToken.symbol);
|
|
129
|
-
return {
|
|
130
|
-
collateralPrice,
|
|
131
|
-
debtPrice,
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
165
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
const usdValue = Number(balance.toFixed(usdceToken.decimals)) * price.price;
|
|
146
|
-
return {
|
|
147
|
-
tokenInfo: usdceToken,
|
|
148
|
-
amount: balance,
|
|
149
|
-
usdValue,
|
|
150
|
-
};
|
|
151
|
-
}
|
|
166
|
+
if (!Number.isFinite(collateralPrice.price) || collateralPrice.price <= 0) {
|
|
167
|
+
throw new Error(
|
|
168
|
+
`${this.getTag()} Invalid collateralPrice: ${collateralPrice.price}. Expected a finite, positive number.`
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
if (!Number.isFinite(debtPrice.price) || debtPrice.price <= 0) {
|
|
172
|
+
throw new Error(
|
|
173
|
+
`${this.getTag()} Invalid debtPrice: ${debtPrice.price}. Expected a finite, positive number.`
|
|
174
|
+
);
|
|
175
|
+
}
|
|
152
176
|
|
|
153
|
-
|
|
154
|
-
const collateralToken = this.metadata.additionalInfo.borrowable_assets[0]!;
|
|
155
|
-
const balance = await new ERC20(this.config).balanceOf(
|
|
156
|
-
collateralToken.address,
|
|
157
|
-
this.metadata.additionalInfo.vaultAllocator,
|
|
158
|
-
collateralToken.decimals
|
|
159
|
-
);
|
|
160
|
-
const price = await this.pricer.getPrice(collateralToken.symbol);
|
|
161
|
-
const usdValue =
|
|
162
|
-
Number(balance.toFixed(collateralToken.decimals)) * price.price;
|
|
163
|
-
return {
|
|
164
|
-
tokenInfo: collateralToken,
|
|
165
|
-
amount: balance,
|
|
166
|
-
usdValue,
|
|
167
|
-
};
|
|
177
|
+
return { collateralPrice, debtPrice };
|
|
168
178
|
}
|
|
169
179
|
|
|
170
|
-
async getVesuAdapter(): Promise<VesuMultiplyAdapter
|
|
180
|
+
async getVesuAdapter(): Promise<VesuMultiplyAdapter> {
|
|
171
181
|
const vesuAdapter = this.metadata.additionalInfo.adapters.find(
|
|
172
|
-
(adapter) => adapter.adapter.name === VesuMultiplyAdapter.name
|
|
182
|
+
(adapter) => adapter.adapter.name === VesuMultiplyAdapter.name,
|
|
173
183
|
);
|
|
174
184
|
if (!vesuAdapter) {
|
|
175
|
-
|
|
176
|
-
|
|
185
|
+
throw new Error(
|
|
186
|
+
`${this.getTag()} Vesu adapter not configured in metadata.`
|
|
187
|
+
);
|
|
177
188
|
}
|
|
178
189
|
return vesuAdapter.adapter as VesuMultiplyAdapter;
|
|
179
190
|
}
|
|
180
191
|
|
|
181
|
-
async
|
|
182
|
-
const
|
|
183
|
-
(adapter) => adapter.adapter.name ===
|
|
192
|
+
async getVesuModifyPositionAdapter(): Promise<VesuModifyPositionAdapter> {
|
|
193
|
+
const vesuModifyPositionAdapter = this.metadata.additionalInfo.adapters.find(
|
|
194
|
+
(adapter) => adapter.adapter.name === VesuModifyPositionAdapter.name,
|
|
184
195
|
);
|
|
185
|
-
if (!
|
|
186
|
-
|
|
187
|
-
|
|
196
|
+
if (!vesuModifyPositionAdapter) {
|
|
197
|
+
throw new Error(
|
|
198
|
+
`${this.getTag()} Vesu modify position adapter not configured in metadata.`,
|
|
199
|
+
);
|
|
188
200
|
}
|
|
189
|
-
return
|
|
201
|
+
return vesuModifyPositionAdapter.adapter as VesuModifyPositionAdapter;
|
|
190
202
|
}
|
|
191
203
|
|
|
192
|
-
async
|
|
193
|
-
const
|
|
194
|
-
(adapter) => adapter.adapter.name ===
|
|
204
|
+
async getUsdceTransferAdapter(): Promise<TokenTransferAdapter> {
|
|
205
|
+
const usdceTransferAdapter = this.metadata.additionalInfo.adapters.find(
|
|
206
|
+
(adapter) => adapter.adapter.name === TokenTransferAdapter.name,
|
|
195
207
|
);
|
|
196
|
-
if (!
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
}
|
|
200
|
-
return extendedAdapter.adapter as ExtendedAdapter;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
async moveAssetsToVaultAllocator(
|
|
204
|
-
amount: Web3Number,
|
|
205
|
-
extendedAdapter: ExtendedAdapter
|
|
206
|
-
): Promise<{
|
|
207
|
-
calls: Call[];
|
|
208
|
-
status: boolean;
|
|
209
|
-
}> {
|
|
210
|
-
try {
|
|
211
|
-
const usdceToken = Global.getDefaultTokens().find(
|
|
212
|
-
(token) => token.symbol === "USDCe"
|
|
213
|
-
)!;
|
|
214
|
-
const walletBalance = await new ERC20(this.config).balanceOf(
|
|
215
|
-
usdceToken.address,
|
|
216
|
-
WALLET_ADDRESS,
|
|
217
|
-
usdceToken.decimals
|
|
218
|
-
);
|
|
219
|
-
logger.info(
|
|
220
|
-
`${VesuExtendedMultiplierStrategy.name}::moveAssetsToVaultAllocator walletBalance: ${walletBalance}`
|
|
221
|
-
);
|
|
222
|
-
const amountToBeTransferred = amount.minimum(walletBalance);
|
|
223
|
-
logger.info(
|
|
224
|
-
`${
|
|
225
|
-
VesuExtendedMultiplierStrategy.name
|
|
226
|
-
}::moveAssetsToVaultAllocator amountToBeTransferred: ${amountToBeTransferred.toNumber()}`
|
|
227
|
-
);
|
|
228
|
-
|
|
229
|
-
if (amountToBeTransferred.lessThan(0)) {
|
|
230
|
-
logger.error(
|
|
231
|
-
`${
|
|
232
|
-
VesuExtendedMultiplierStrategy.name
|
|
233
|
-
}::moveAssetsToVaultAllocator amountToBeTransferred is less than 0: ${amountToBeTransferred.toNumber()}`
|
|
234
|
-
);
|
|
235
|
-
return {
|
|
236
|
-
calls: [],
|
|
237
|
-
status: false,
|
|
238
|
-
};
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
const approveCall = new ERC20(this.config).approve(
|
|
242
|
-
usdceToken.address,
|
|
243
|
-
this.metadata.additionalInfo.vaultAllocator,
|
|
244
|
-
amountToBeTransferred
|
|
245
|
-
);
|
|
246
|
-
const transferCall = new ERC20(this.config).transfer(
|
|
247
|
-
usdceToken.address,
|
|
248
|
-
this.metadata.additionalInfo.vaultAllocator,
|
|
249
|
-
amountToBeTransferred
|
|
250
|
-
);
|
|
251
|
-
const proofsInfo = extendedAdapter.getProofsForFromLegacySwap(
|
|
252
|
-
this.getMerkleTree()
|
|
208
|
+
if (!usdceTransferAdapter) {
|
|
209
|
+
throw new Error(
|
|
210
|
+
`${this.getTag()} Usdce transfer adapter not configured in metadata.`
|
|
253
211
|
);
|
|
254
|
-
const proofGroups = proofsInfo.proofs;
|
|
255
|
-
const call = this.getManageCall(
|
|
256
|
-
proofGroups,
|
|
257
|
-
await proofsInfo.callConstructor({ amount: amountToBeTransferred })
|
|
258
|
-
);
|
|
259
|
-
return {
|
|
260
|
-
calls: [approveCall, transferCall, call],
|
|
261
|
-
status: true,
|
|
262
|
-
};
|
|
263
|
-
} catch (err) {
|
|
264
|
-
logger.error(`error moving assets to vault allocator: ${err}`);
|
|
265
|
-
return {
|
|
266
|
-
calls: [],
|
|
267
|
-
status: false,
|
|
268
|
-
};
|
|
269
212
|
}
|
|
213
|
+
return usdceTransferAdapter.adapter as TokenTransferAdapter;
|
|
270
214
|
}
|
|
271
215
|
|
|
272
|
-
async
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
vesuLeverage: number;
|
|
280
|
-
debtAmountToBeRepaid: Web3Number;
|
|
281
|
-
}> {
|
|
282
|
-
try {
|
|
283
|
-
logger.info(
|
|
284
|
-
`${VesuExtendedMultiplierStrategy.name}::shouldInvest starting`
|
|
285
|
-
);
|
|
286
|
-
const vesuAdapter = await this.getVesuAdapter();
|
|
287
|
-
const extendedAdapter = await this.getExtendedAdapter();
|
|
288
|
-
logger.info(
|
|
289
|
-
`${
|
|
290
|
-
VesuExtendedMultiplierStrategy.name
|
|
291
|
-
}::shouldInvest adapters fetched: vesuAdapter=${!!vesuAdapter}, extendedAdapter=${!!extendedAdapter}, extendedAdapter.client=${!!extendedAdapter?.client}`
|
|
292
|
-
);
|
|
293
|
-
|
|
294
|
-
if (!vesuAdapter) {
|
|
295
|
-
logger.error(
|
|
296
|
-
`Vesu adapter not configured in metadata. This is a configuration issue, not a temporary failure.`
|
|
297
|
-
);
|
|
298
|
-
return {
|
|
299
|
-
shouldInvest: false,
|
|
300
|
-
vesuAmount: new Web3Number(0, 0),
|
|
301
|
-
extendedAmount: new Web3Number(0, 0),
|
|
302
|
-
extendedLeverage: 0,
|
|
303
|
-
collateralPrice: 0,
|
|
304
|
-
debtPrice: 0,
|
|
305
|
-
vesuLeverage: 0,
|
|
306
|
-
debtAmountToBeRepaid: new Web3Number(0, 0),
|
|
307
|
-
};
|
|
308
|
-
}
|
|
309
|
-
if (!extendedAdapter) {
|
|
310
|
-
logger.error(
|
|
311
|
-
`Extended adapter not configured in metadata. This is a configuration issue, not a temporary failure.`
|
|
312
|
-
);
|
|
313
|
-
return {
|
|
314
|
-
shouldInvest: false,
|
|
315
|
-
vesuAmount: new Web3Number(0, 0),
|
|
316
|
-
extendedAmount: new Web3Number(0, 0),
|
|
317
|
-
extendedLeverage: 0,
|
|
318
|
-
collateralPrice: 0,
|
|
319
|
-
debtPrice: 0,
|
|
320
|
-
vesuLeverage: 0,
|
|
321
|
-
debtAmountToBeRepaid: new Web3Number(0, 0),
|
|
322
|
-
};
|
|
323
|
-
}
|
|
324
|
-
if (!extendedAdapter.client) {
|
|
325
|
-
logger.error(
|
|
326
|
-
`Extended adapter client not initialized. This may be a temporary initialization failure - check network connectivity and API availability.`
|
|
327
|
-
);
|
|
328
|
-
return {
|
|
329
|
-
shouldInvest: false,
|
|
330
|
-
vesuAmount: new Web3Number(0, 0),
|
|
331
|
-
extendedAmount: new Web3Number(0, 0),
|
|
332
|
-
extendedLeverage: 0,
|
|
333
|
-
collateralPrice: 0,
|
|
334
|
-
debtPrice: 0,
|
|
335
|
-
vesuLeverage: 0,
|
|
336
|
-
debtAmountToBeRepaid: new Web3Number(0, 0),
|
|
337
|
-
};
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
logger.info(
|
|
341
|
-
`${VesuExtendedMultiplierStrategy.name}::shouldInvest calling getUnusedBalance`
|
|
342
|
-
);
|
|
343
|
-
const balance = await this.getUnusedBalance();
|
|
344
|
-
|
|
345
|
-
if (!Number.isFinite(balance.usdValue) || balance.usdValue < 0) {
|
|
346
|
-
logger.error(
|
|
347
|
-
`Invalid balance.usdValue: ${balance.usdValue}. Expected a finite, non-negative number.`
|
|
348
|
-
);
|
|
349
|
-
return {
|
|
350
|
-
shouldInvest: false,
|
|
351
|
-
vesuAmount: new Web3Number(0, 0),
|
|
352
|
-
extendedAmount: new Web3Number(0, 0),
|
|
353
|
-
extendedLeverage: 0,
|
|
354
|
-
collateralPrice: 0,
|
|
355
|
-
debtPrice: 0,
|
|
356
|
-
vesuLeverage: 0,
|
|
357
|
-
debtAmountToBeRepaid: new Web3Number(0, 0),
|
|
358
|
-
};
|
|
359
|
-
}
|
|
360
|
-
logger.info(
|
|
361
|
-
`${VesuExtendedMultiplierStrategy.name}::shouldInvest balance: ${balance.usdValue}`
|
|
362
|
-
);
|
|
363
|
-
const usdcBalanceOnExtended =
|
|
364
|
-
await extendedAdapter.getExtendedDepositAmount();
|
|
365
|
-
|
|
366
|
-
if (usdcBalanceOnExtended) {
|
|
367
|
-
const availableForTrade= parseFloat(
|
|
368
|
-
usdcBalanceOnExtended.availableForTrade
|
|
369
|
-
);
|
|
370
|
-
if (
|
|
371
|
-
!Number.isFinite(availableForTrade) ||
|
|
372
|
-
availableForTrade < 0
|
|
373
|
-
) {
|
|
374
|
-
logger.error(
|
|
375
|
-
`Invalid usdcBalanceOnExtended.availableForWithdrawal: ${usdcBalanceOnExtended.availableForWithdrawal}. Expected a finite, non-negative number.`
|
|
376
|
-
);
|
|
377
|
-
return {
|
|
378
|
-
shouldInvest: false,
|
|
379
|
-
vesuAmount: new Web3Number(0, 0),
|
|
380
|
-
extendedAmount: new Web3Number(0, 0),
|
|
381
|
-
extendedLeverage: 0,
|
|
382
|
-
collateralPrice: 0,
|
|
383
|
-
debtPrice: 0,
|
|
384
|
-
vesuLeverage: 0,
|
|
385
|
-
debtAmountToBeRepaid: new Web3Number(0, 0),
|
|
386
|
-
};
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
const amountUsedFromExtended = new Web3Number(usdcBalanceOnExtended?.availableForTrade ?? 0, USDC_TOKEN_DECIMALS);
|
|
391
|
-
|
|
392
|
-
/**
|
|
393
|
-
* The LIMIT_BALANCE is the bffer amount to keep in the investing Cycle
|
|
394
|
-
* 5% buffer amount is kept for extended amounts available to trade as they changes very suddenly
|
|
395
|
-
*/
|
|
396
|
-
const amountToInvest = new Web3Number(
|
|
397
|
-
balance.usdValue,
|
|
398
|
-
USDC_TOKEN_DECIMALS
|
|
399
|
-
)
|
|
400
|
-
.plus(amountUsedFromExtended.multipliedBy(1 - LIMIT_BALANCE) ?? 0)
|
|
401
|
-
.multipliedBy(1 - LIMIT_BALANCE);
|
|
402
|
-
|
|
403
|
-
const amountToInvestNumber = amountToInvest.toNumber();
|
|
404
|
-
if (!Number.isFinite(amountToInvestNumber)) {
|
|
405
|
-
logger.error(
|
|
406
|
-
`Invalid amountToInvest calculation result: ${amountToInvestNumber}. Calculation may have produced NaN or Infinity.`
|
|
407
|
-
);
|
|
408
|
-
return {
|
|
409
|
-
shouldInvest: false,
|
|
410
|
-
vesuAmount: new Web3Number(0, 0),
|
|
411
|
-
extendedAmount: new Web3Number(0, 0),
|
|
412
|
-
extendedLeverage: 0,
|
|
413
|
-
collateralPrice: 0,
|
|
414
|
-
debtPrice: 0,
|
|
415
|
-
vesuLeverage: 0,
|
|
416
|
-
debtAmountToBeRepaid: new Web3Number(0, 0),
|
|
417
|
-
};
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
logger.info(
|
|
421
|
-
`${VesuExtendedMultiplierStrategy.name}::shouldInvest amountToInvest: ${amountToInvestNumber}`
|
|
422
|
-
);
|
|
423
|
-
|
|
424
|
-
if (amountToInvest.lessThan(LIMIT_BALANCE_VALUE)) {
|
|
425
|
-
return {
|
|
426
|
-
shouldInvest: false,
|
|
427
|
-
vesuAmount: new Web3Number(0, 0),
|
|
428
|
-
extendedAmount: new Web3Number(0, 0),
|
|
429
|
-
extendedLeverage: 0,
|
|
430
|
-
collateralPrice: 0,
|
|
431
|
-
debtPrice: 0,
|
|
432
|
-
vesuLeverage: 0,
|
|
433
|
-
debtAmountToBeRepaid: new Web3Number(0, 0),
|
|
434
|
-
};
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
const extendedPositon = await extendedAdapter.getAllOpenPositions();
|
|
438
|
-
if (!extendedPositon) {
|
|
439
|
-
logger.error("error getting extended position to decide move assets");
|
|
440
|
-
return {
|
|
441
|
-
shouldInvest: false,
|
|
442
|
-
vesuAmount: new Web3Number(0, 0),
|
|
443
|
-
extendedAmount: new Web3Number(0, 0),
|
|
444
|
-
extendedLeverage: 0,
|
|
445
|
-
collateralPrice: 0,
|
|
446
|
-
debtPrice: 0,
|
|
447
|
-
vesuLeverage: 0,
|
|
448
|
-
debtAmountToBeRepaid: new Web3Number(0, 0),
|
|
449
|
-
};
|
|
450
|
-
}
|
|
451
|
-
const { collateralTokenAmount, debtTokenAmount } =
|
|
452
|
-
await vesuAdapter.vesuAdapter.getAssetPrices();
|
|
453
|
-
|
|
454
|
-
const { collateralPrice, debtPrice } = await this.getAssetPrices();
|
|
455
|
-
|
|
456
|
-
if (
|
|
457
|
-
!Number.isFinite(collateralPrice.price) ||
|
|
458
|
-
collateralPrice.price <= 0
|
|
459
|
-
) {
|
|
460
|
-
logger.error(
|
|
461
|
-
`Invalid collateralPrice: ${collateralPrice.price}. Expected a finite, positive number.`
|
|
462
|
-
);
|
|
463
|
-
return {
|
|
464
|
-
shouldInvest: false,
|
|
465
|
-
vesuAmount: new Web3Number(0, 0),
|
|
466
|
-
extendedAmount: new Web3Number(0, 0),
|
|
467
|
-
extendedLeverage: 0,
|
|
468
|
-
collateralPrice: 0,
|
|
469
|
-
debtPrice: 0,
|
|
470
|
-
vesuLeverage: 0,
|
|
471
|
-
debtAmountToBeRepaid: new Web3Number(0, 0),
|
|
472
|
-
};
|
|
473
|
-
}
|
|
474
|
-
if (!Number.isFinite(debtPrice.price) || debtPrice.price <= 0) {
|
|
475
|
-
logger.error(
|
|
476
|
-
`Invalid debtPrice: ${debtPrice.price}. Expected a finite, positive number.`
|
|
477
|
-
);
|
|
478
|
-
return {
|
|
479
|
-
shouldInvest: false,
|
|
480
|
-
vesuAmount: new Web3Number(0, 0),
|
|
481
|
-
extendedAmount: new Web3Number(0, 0),
|
|
482
|
-
extendedLeverage: 0,
|
|
483
|
-
collateralPrice: 0,
|
|
484
|
-
debtPrice: 0,
|
|
485
|
-
vesuLeverage: 0,
|
|
486
|
-
debtAmountToBeRepaid: new Web3Number(0, 0),
|
|
487
|
-
};
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
let debtAmountToBeRepaid = calculateDeltaDebtAmount(
|
|
491
|
-
MAX_LTV_BTC_USDC,
|
|
492
|
-
collateralTokenAmount,
|
|
493
|
-
debtTokenAmount,
|
|
494
|
-
collateralPrice.price,
|
|
495
|
-
debtPrice.price,
|
|
496
|
-
this.metadata.additionalInfo.targetHealthFactor
|
|
497
|
-
);
|
|
498
|
-
if (!debtAmountToBeRepaid) {
|
|
499
|
-
logger.error("error calculating debt amount to be repaid");
|
|
500
|
-
return {
|
|
501
|
-
shouldInvest: false,
|
|
502
|
-
vesuAmount: new Web3Number(0, 0),
|
|
503
|
-
extendedAmount: new Web3Number(0, 0),
|
|
504
|
-
extendedLeverage: 0,
|
|
505
|
-
collateralPrice: 0,
|
|
506
|
-
debtPrice: 0,
|
|
507
|
-
vesuLeverage: 0,
|
|
508
|
-
debtAmountToBeRepaid: new Web3Number(0, 0),
|
|
509
|
-
};
|
|
510
|
-
}
|
|
511
|
-
logger.info(
|
|
512
|
-
`${
|
|
513
|
-
VesuExtendedMultiplierStrategy.name
|
|
514
|
-
}::shouldInvest debtAmountToBeRepaid: ${debtAmountToBeRepaid.toNumber()}`
|
|
515
|
-
);
|
|
516
|
-
/**
|
|
517
|
-
* Since the debt amount is negative, we need to add it to the amount to invest
|
|
518
|
-
* to maintain the ltv
|
|
519
|
-
*/
|
|
520
|
-
const vesuLeverage = calculateVesuLeverage();
|
|
521
|
-
debtAmountToBeRepaid = debtAmountToBeRepaid.toNumber() !== 0 ? debtAmountToBeRepaid.dividedBy(vesuLeverage-1) : new Web3Number(0, 0);
|
|
522
|
-
const amountToInvestAfterRepayingDebt =
|
|
523
|
-
amountToInvest.plus(debtAmountToBeRepaid);
|
|
524
|
-
const { vesu_amount, extended_amount, extended_leverage, vesu_leverage } =
|
|
525
|
-
await calculateAmountDistribution(
|
|
526
|
-
amountToInvestAfterRepayingDebt.toNumber(),
|
|
527
|
-
extendedAdapter.client,
|
|
528
|
-
extendedAdapter.config.extendedMarketName,
|
|
529
|
-
collateralPrice.price,
|
|
530
|
-
debtPrice.price,
|
|
531
|
-
collateralTokenAmount,
|
|
532
|
-
extendedPositon
|
|
533
|
-
);
|
|
534
|
-
if (
|
|
535
|
-
!vesu_amount ||
|
|
536
|
-
!extended_amount ||
|
|
537
|
-
!extended_leverage ||
|
|
538
|
-
!vesu_leverage
|
|
539
|
-
) {
|
|
540
|
-
logger.error(
|
|
541
|
-
`Not enough amount to invest: vesu_amount=${vesu_amount}, extended_amount=${extended_amount}`
|
|
542
|
-
);
|
|
543
|
-
return {
|
|
544
|
-
shouldInvest: false,
|
|
545
|
-
vesuAmount: new Web3Number(0, 0),
|
|
546
|
-
extendedAmount: new Web3Number(0, 0),
|
|
547
|
-
extendedLeverage: 0,
|
|
548
|
-
collateralPrice: 0,
|
|
549
|
-
debtPrice: 0,
|
|
550
|
-
vesuLeverage: 0,
|
|
551
|
-
debtAmountToBeRepaid: new Web3Number(0, 0),
|
|
552
|
-
};
|
|
553
|
-
}
|
|
554
|
-
logger.info(
|
|
555
|
-
`${
|
|
556
|
-
VesuExtendedMultiplierStrategy.name
|
|
557
|
-
}::shouldInvest vesu_amount: ${vesu_amount.toNumber()}, extended_amount: ${extended_amount.toNumber()}`
|
|
216
|
+
async getAvnuAdapter(): Promise<AvnuAdapter> {
|
|
217
|
+
const avnuAdapter = this.metadata.additionalInfo.adapters.find(
|
|
218
|
+
(adapter) => adapter.adapter.name === AvnuAdapter.name,
|
|
219
|
+
);
|
|
220
|
+
if (!avnuAdapter) {
|
|
221
|
+
throw new Error(
|
|
222
|
+
`${this.getTag()} Avnu adapter not configured in metadata.`
|
|
558
223
|
);
|
|
559
|
-
return {
|
|
560
|
-
shouldInvest: true,
|
|
561
|
-
vesuAmount: vesu_amount,
|
|
562
|
-
extendedAmount: extended_amount,
|
|
563
|
-
extendedLeverage: extended_leverage,
|
|
564
|
-
vesuLeverage: vesu_leverage,
|
|
565
|
-
collateralPrice: collateralPrice.price,
|
|
566
|
-
debtPrice: debtPrice.price,
|
|
567
|
-
debtAmountToBeRepaid: debtAmountToBeRepaid,
|
|
568
|
-
};
|
|
569
|
-
} catch (err) {
|
|
570
|
-
logger.error(`error deciding invest: ${err}`);
|
|
571
|
-
return {
|
|
572
|
-
shouldInvest: false,
|
|
573
|
-
vesuAmount: new Web3Number(0, 0),
|
|
574
|
-
extendedAmount: new Web3Number(0, 0),
|
|
575
|
-
extendedLeverage: 0,
|
|
576
|
-
collateralPrice: 0,
|
|
577
|
-
debtPrice: 0,
|
|
578
|
-
vesuLeverage: 0,
|
|
579
|
-
debtAmountToBeRepaid: new Web3Number(0, 0),
|
|
580
|
-
};
|
|
581
224
|
}
|
|
225
|
+
return avnuAdapter.adapter as AvnuAdapter;
|
|
582
226
|
}
|
|
583
227
|
|
|
584
|
-
async
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
if (!vesuAdapter || !extendedAdapter || !extendedAdapter.client) {
|
|
592
|
-
logger.error(
|
|
593
|
-
`vesu or extended adapter not found: vesuAdapter=${vesuAdapter}, extendedAdapter=${extendedAdapter}`
|
|
594
|
-
);
|
|
595
|
-
return [];
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
const extendedHoldings = await extendedAdapter.getExtendedDepositAmount();
|
|
599
|
-
if (!extendedHoldings) {
|
|
600
|
-
logger.error(`error getting extended holdings: ${extendedHoldings}`);
|
|
601
|
-
return [];
|
|
602
|
-
}
|
|
603
|
-
const usdcAmountInWallet = (await this.getUnusedBalance()).amount;
|
|
604
|
-
/**
|
|
605
|
-
* Trade is the correct metric, since we can close position for some vesu amount, otherwise underutilisation of funds will be a huge issue
|
|
606
|
-
*/
|
|
607
|
-
const usdcAmountOnExtendedAvailableForTrade = parseFloat(
|
|
608
|
-
extendedHoldings.availableForTrade
|
|
609
|
-
);
|
|
610
|
-
|
|
611
|
-
logger.info(
|
|
612
|
-
`${
|
|
613
|
-
VesuExtendedMultiplierStrategy.name
|
|
614
|
-
}::shouldMoveAssets calculating movements - Extended current: ${usdcAmountOnExtendedAvailableForTrade}, Wallet: ${usdcAmountInWallet.toNumber()}, Target Extended: ${extendedAmount.toNumber()}, Target Vesu: ${vesuAmount.toNumber()}`
|
|
615
|
-
);
|
|
616
|
-
|
|
617
|
-
let totalExtendedWithdrawal = new Web3Number(0, USDC_TOKEN_DECIMALS);
|
|
618
|
-
let totalExtendedDeposit = new Web3Number(0, USDC_TOKEN_DECIMALS);
|
|
619
|
-
|
|
620
|
-
if (
|
|
621
|
-
extendedAmount.isNegative() &&
|
|
622
|
-
extendedAmount
|
|
623
|
-
.abs()
|
|
624
|
-
.greaterThan(extendedAdapter.minimumExtendedMovementAmount)
|
|
625
|
-
) {
|
|
626
|
-
totalExtendedWithdrawal = totalExtendedWithdrawal.plus(
|
|
627
|
-
extendedAmount.abs()
|
|
628
|
-
);
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
// Calculate remaining Extended difference (target vs current)
|
|
632
|
-
// If extendedAmount was negative, we've already accounted for that withdrawal
|
|
633
|
-
// So we calculate based on what Extended will be after that withdrawal
|
|
634
|
-
const extendedTargetAmount = extendedAmount.abs(); // Use absolute value as target
|
|
635
|
-
let projectedExtendedBalance = usdcAmountOnExtendedAvailableForTrade;
|
|
636
|
-
|
|
637
|
-
if (extendedAmount.isNegative()) {
|
|
638
|
-
projectedExtendedBalance =
|
|
639
|
-
projectedExtendedBalance - extendedAmount.abs().toNumber();
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
const extendedAmountDifference = extendedTargetAmount.minus(
|
|
643
|
-
projectedExtendedBalance
|
|
644
|
-
);
|
|
645
|
-
const extendedAmountDifferenceAbs = extendedAmountDifference.abs();
|
|
646
|
-
|
|
647
|
-
// Track additional Extended movements
|
|
648
|
-
if (extendedAmountDifference.lessThan(0)) {
|
|
649
|
-
totalExtendedWithdrawal = totalExtendedWithdrawal.plus(
|
|
650
|
-
extendedAmountDifferenceAbs
|
|
651
|
-
);
|
|
652
|
-
} else if (extendedAmountDifference.greaterThan(0)) {
|
|
653
|
-
totalExtendedDeposit = totalExtendedDeposit.plus(
|
|
654
|
-
extendedAmountDifference
|
|
655
|
-
);
|
|
656
|
-
}
|
|
657
|
-
|
|
658
|
-
const vesuTargetAmount = vesuAmount.abs();
|
|
659
|
-
const projectedWalletBalance = usdcAmountInWallet
|
|
660
|
-
.plus(totalExtendedWithdrawal)
|
|
661
|
-
.minus(totalExtendedDeposit);
|
|
662
|
-
|
|
663
|
-
let vesuAmountDifference = vesuTargetAmount.minus(projectedWalletBalance);
|
|
664
|
-
const vesuAmountDifferenceAbs = vesuAmountDifference.abs();
|
|
665
|
-
|
|
666
|
-
logger.info(
|
|
667
|
-
`${
|
|
668
|
-
VesuExtendedMultiplierStrategy.name
|
|
669
|
-
}::shouldMoveAssets calculated movements - Extended withdrawal: ${totalExtendedWithdrawal.toNumber()}, Extended deposit: ${totalExtendedDeposit.toNumber()}, Extended diff: ${extendedAmountDifference.toNumber()}, Projected wallet: ${projectedWalletBalance.toNumber()}, Vesu diff: ${vesuAmountDifference.toNumber()}`
|
|
228
|
+
async getExtendedAdapter(): Promise<ExtendedAdapter> {
|
|
229
|
+
const extendedAdapter = this.metadata.additionalInfo.adapters.find(
|
|
230
|
+
(adapter) => adapter.adapter.name === ExtendedAdapter.name,
|
|
231
|
+
);
|
|
232
|
+
if (!extendedAdapter) {
|
|
233
|
+
throw new Error(
|
|
234
|
+
`${this.getTag()} Extended adapter not configured in metadata.`
|
|
670
235
|
);
|
|
671
|
-
let transactionResults: TransactionResult[] = [];
|
|
672
|
-
|
|
673
|
-
// Handle negative extendedAmount (initial withdrawal needed)
|
|
674
|
-
if (
|
|
675
|
-
extendedAmount.isNegative() &&
|
|
676
|
-
extendedAmount
|
|
677
|
-
.abs()
|
|
678
|
-
.greaterThan(extendedAdapter.minimumExtendedMovementAmount)
|
|
679
|
-
) {
|
|
680
|
-
try {
|
|
681
|
-
const {
|
|
682
|
-
calls: extendedCalls,
|
|
683
|
-
status: extendedStatus,
|
|
684
|
-
transactionMetadata: extendedTransactionMetadata,
|
|
685
|
-
} = await this.moveAssets(
|
|
686
|
-
{
|
|
687
|
-
to: Protocols.VAULT.name,
|
|
688
|
-
from: Protocols.EXTENDED.name,
|
|
689
|
-
amount: extendedAmount.abs(),
|
|
690
|
-
cycleType: CycleType.INVESTMENT,
|
|
691
|
-
},
|
|
692
|
-
extendedAdapter,
|
|
693
|
-
vesuAdapter
|
|
694
|
-
);
|
|
695
|
-
if (extendedStatus) {
|
|
696
|
-
transactionResults.push({
|
|
697
|
-
status: extendedStatus,
|
|
698
|
-
calls: extendedCalls,
|
|
699
|
-
transactionMetadata: {
|
|
700
|
-
...extendedTransactionMetadata,
|
|
701
|
-
transactionType: "DEPOSIT",
|
|
702
|
-
},
|
|
703
|
-
});
|
|
704
|
-
} else {
|
|
705
|
-
return [
|
|
706
|
-
this.createTransactionResult(
|
|
707
|
-
[],
|
|
708
|
-
false,
|
|
709
|
-
{
|
|
710
|
-
from: Protocols.EXTENDED.name,
|
|
711
|
-
to: Protocols.VAULT.name,
|
|
712
|
-
amount: extendedAmount.abs(),
|
|
713
|
-
},
|
|
714
|
-
"NONE",
|
|
715
|
-
CycleType.INVESTMENT
|
|
716
|
-
),
|
|
717
|
-
];
|
|
718
|
-
}
|
|
719
|
-
} catch (err) {
|
|
720
|
-
logger.error(`Failed moving assets to vault: ${err}`);
|
|
721
|
-
return [
|
|
722
|
-
this.createTransactionResult(
|
|
723
|
-
[],
|
|
724
|
-
false,
|
|
725
|
-
{
|
|
726
|
-
from: Protocols.EXTENDED.name,
|
|
727
|
-
to: Protocols.VAULT.name,
|
|
728
|
-
amount: extendedAmount.abs(),
|
|
729
|
-
},
|
|
730
|
-
"NONE",
|
|
731
|
-
CycleType.INVESTMENT
|
|
732
|
-
),
|
|
733
|
-
];
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
if (
|
|
738
|
-
vesuAmount.isNegative() &&
|
|
739
|
-
vesuAmount.abs().greaterThan(vesuAdapter.minimumVesuMovementAmount)
|
|
740
|
-
) {
|
|
741
|
-
try {
|
|
742
|
-
const {
|
|
743
|
-
calls: vesuCalls,
|
|
744
|
-
status: vesuStatus,
|
|
745
|
-
transactionMetadata: vesuTransactionMetadata,
|
|
746
|
-
} = await this.moveAssets(
|
|
747
|
-
{
|
|
748
|
-
to: Protocols.EXTENDED.name,
|
|
749
|
-
from: Protocols.VESU.name,
|
|
750
|
-
amount: vesuAmount.abs(),
|
|
751
|
-
cycleType: CycleType.INVESTMENT,
|
|
752
|
-
},
|
|
753
|
-
extendedAdapter,
|
|
754
|
-
vesuAdapter
|
|
755
|
-
);
|
|
756
|
-
if (!vesuStatus) {
|
|
757
|
-
return [
|
|
758
|
-
this.createTransactionResult(
|
|
759
|
-
[],
|
|
760
|
-
false,
|
|
761
|
-
{
|
|
762
|
-
from: Protocols.VESU.name,
|
|
763
|
-
to: Protocols.EXTENDED.name,
|
|
764
|
-
amount: vesuAmount.abs(),
|
|
765
|
-
},
|
|
766
|
-
"NONE",
|
|
767
|
-
CycleType.INVESTMENT
|
|
768
|
-
),
|
|
769
|
-
];
|
|
770
|
-
}
|
|
771
|
-
transactionResults.push({
|
|
772
|
-
status: vesuStatus,
|
|
773
|
-
calls: vesuCalls,
|
|
774
|
-
transactionMetadata: {
|
|
775
|
-
...vesuTransactionMetadata,
|
|
776
|
-
transactionType: "DEPOSIT",
|
|
777
|
-
},
|
|
778
|
-
});
|
|
779
|
-
} catch (err) {
|
|
780
|
-
logger.error(
|
|
781
|
-
`Failed moving assets to extended via vault allocator: ${err}`
|
|
782
|
-
);
|
|
783
|
-
return [
|
|
784
|
-
this.createTransactionResult(
|
|
785
|
-
[],
|
|
786
|
-
false,
|
|
787
|
-
{
|
|
788
|
-
from: Protocols.VESU.name,
|
|
789
|
-
to: Protocols.EXTENDED.name,
|
|
790
|
-
amount: vesuAmount.abs(),
|
|
791
|
-
},
|
|
792
|
-
"NONE",
|
|
793
|
-
CycleType.INVESTMENT
|
|
794
|
-
),
|
|
795
|
-
];
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
// Handle Extended adjustments based on calculated difference
|
|
800
|
-
if (
|
|
801
|
-
extendedAmountDifferenceAbs.greaterThan(
|
|
802
|
-
extendedAdapter.minimumExtendedMovementAmount
|
|
803
|
-
)
|
|
804
|
-
) {
|
|
805
|
-
if (extendedAmountDifference.greaterThan(0)) {
|
|
806
|
-
try {
|
|
807
|
-
const {
|
|
808
|
-
calls: extendedCalls,
|
|
809
|
-
status: extendedStatus,
|
|
810
|
-
transactionMetadata: extendedTransactionMetadata,
|
|
811
|
-
} = await this.moveAssets(
|
|
812
|
-
{
|
|
813
|
-
to: Protocols.EXTENDED.name,
|
|
814
|
-
from: Protocols.VAULT.name,
|
|
815
|
-
amount: extendedAmountDifference,
|
|
816
|
-
cycleType: CycleType.INVESTMENT,
|
|
817
|
-
},
|
|
818
|
-
extendedAdapter,
|
|
819
|
-
vesuAdapter
|
|
820
|
-
);
|
|
821
|
-
if (extendedStatus) {
|
|
822
|
-
transactionResults.push({
|
|
823
|
-
status: extendedStatus,
|
|
824
|
-
calls: extendedCalls,
|
|
825
|
-
transactionMetadata: extendedTransactionMetadata,
|
|
826
|
-
});
|
|
827
|
-
} else {
|
|
828
|
-
logger.error(
|
|
829
|
-
`Failed to move assets to extended - operation returned false status`
|
|
830
|
-
);
|
|
831
|
-
return [
|
|
832
|
-
this.createTransactionResult(
|
|
833
|
-
[],
|
|
834
|
-
false,
|
|
835
|
-
{
|
|
836
|
-
from: Protocols.VAULT.name,
|
|
837
|
-
to: Protocols.EXTENDED.name,
|
|
838
|
-
amount: extendedAmountDifference,
|
|
839
|
-
},
|
|
840
|
-
"NONE",
|
|
841
|
-
CycleType.INVESTMENT
|
|
842
|
-
),
|
|
843
|
-
];
|
|
844
|
-
}
|
|
845
|
-
} catch (err) {
|
|
846
|
-
logger.error(`Failed moving assets to extended: ${err}`);
|
|
847
|
-
return [
|
|
848
|
-
this.createTransactionResult(
|
|
849
|
-
[],
|
|
850
|
-
false,
|
|
851
|
-
{
|
|
852
|
-
from: Protocols.VAULT.name,
|
|
853
|
-
to: Protocols.EXTENDED.name,
|
|
854
|
-
amount: extendedAmountDifference,
|
|
855
|
-
},
|
|
856
|
-
"NONE",
|
|
857
|
-
CycleType.INVESTMENT
|
|
858
|
-
),
|
|
859
|
-
];
|
|
860
|
-
}
|
|
861
|
-
} else if (extendedAmountDifference.lessThan(0)) {
|
|
862
|
-
try {
|
|
863
|
-
const {
|
|
864
|
-
calls: extendedCalls,
|
|
865
|
-
status: extendedStatus,
|
|
866
|
-
transactionMetadata: extendedTransactionMetadata,
|
|
867
|
-
} = await this.moveAssets(
|
|
868
|
-
{
|
|
869
|
-
to: Protocols.VAULT.name,
|
|
870
|
-
from: Protocols.EXTENDED.name,
|
|
871
|
-
amount: extendedAmountDifferenceAbs,
|
|
872
|
-
cycleType: CycleType.INVESTMENT,
|
|
873
|
-
},
|
|
874
|
-
extendedAdapter,
|
|
875
|
-
vesuAdapter
|
|
876
|
-
);
|
|
877
|
-
if (extendedStatus) {
|
|
878
|
-
transactionResults.push({
|
|
879
|
-
status: extendedStatus,
|
|
880
|
-
calls: extendedCalls,
|
|
881
|
-
transactionMetadata: {
|
|
882
|
-
...extendedTransactionMetadata,
|
|
883
|
-
transactionType: "DEPOSIT",
|
|
884
|
-
},
|
|
885
|
-
});
|
|
886
|
-
} else {
|
|
887
|
-
logger.error(
|
|
888
|
-
`Failed to withdraw from extended - operation returned false status`
|
|
889
|
-
);
|
|
890
|
-
return [
|
|
891
|
-
this.createTransactionResult(
|
|
892
|
-
[],
|
|
893
|
-
false,
|
|
894
|
-
{
|
|
895
|
-
from: Protocols.EXTENDED.name,
|
|
896
|
-
to: Protocols.VAULT.name,
|
|
897
|
-
amount: extendedAmountDifferenceAbs,
|
|
898
|
-
},
|
|
899
|
-
"NONE",
|
|
900
|
-
CycleType.INVESTMENT
|
|
901
|
-
),
|
|
902
|
-
];
|
|
903
|
-
}
|
|
904
|
-
} catch (err) {
|
|
905
|
-
logger.error(`Failed moving assets from extended to vault: ${err}`);
|
|
906
|
-
return [
|
|
907
|
-
this.createTransactionResult(
|
|
908
|
-
[],
|
|
909
|
-
false,
|
|
910
|
-
{
|
|
911
|
-
from: Protocols.EXTENDED.name,
|
|
912
|
-
to: Protocols.VAULT.name,
|
|
913
|
-
amount: extendedAmountDifferenceAbs,
|
|
914
|
-
},
|
|
915
|
-
"NONE",
|
|
916
|
-
CycleType.INVESTMENT
|
|
917
|
-
),
|
|
918
|
-
];
|
|
919
|
-
}
|
|
920
|
-
}
|
|
921
|
-
}
|
|
922
|
-
|
|
923
|
-
// Handle Vesu adjustments based on calculated difference (already adjusted for Extended movements)
|
|
924
|
-
if (
|
|
925
|
-
vesuAmountDifferenceAbs.greaterThan(
|
|
926
|
-
vesuAdapter.minimumVesuMovementAmount
|
|
927
|
-
)
|
|
928
|
-
) {
|
|
929
|
-
if (vesuAmountDifference.lessThanOrEqualTo(0)) {
|
|
930
|
-
logger.warn(
|
|
931
|
-
`Vesu amount difference is negative or zero: ${vesuAmountDifference.toNumber()}. Skipping operation.`
|
|
932
|
-
);
|
|
933
|
-
} else {
|
|
934
|
-
// Move assets from Extended to Vault (which will then go to Vesu)
|
|
935
|
-
try {
|
|
936
|
-
const {
|
|
937
|
-
calls: vesuCalls,
|
|
938
|
-
status: vesuStatus,
|
|
939
|
-
transactionMetadata: vesuTransactionMetadata,
|
|
940
|
-
} = await this.moveAssets(
|
|
941
|
-
{
|
|
942
|
-
to: Protocols.VAULT.name,
|
|
943
|
-
from: Protocols.EXTENDED.name,
|
|
944
|
-
amount: vesuAmountDifference,
|
|
945
|
-
cycleType: CycleType.INVESTMENT,
|
|
946
|
-
},
|
|
947
|
-
extendedAdapter,
|
|
948
|
-
vesuAdapter
|
|
949
|
-
);
|
|
950
|
-
if (!vesuStatus) {
|
|
951
|
-
logger.error(
|
|
952
|
-
`Failed to move assets to vesu - operation returned false status`
|
|
953
|
-
);
|
|
954
|
-
return [
|
|
955
|
-
this.createTransactionResult(
|
|
956
|
-
[],
|
|
957
|
-
false,
|
|
958
|
-
{
|
|
959
|
-
from: Protocols.EXTENDED.name,
|
|
960
|
-
to: Protocols.VAULT.name,
|
|
961
|
-
amount: vesuAmountDifference,
|
|
962
|
-
},
|
|
963
|
-
"NONE",
|
|
964
|
-
CycleType.INVESTMENT
|
|
965
|
-
),
|
|
966
|
-
];
|
|
967
|
-
}
|
|
968
|
-
transactionResults.push({
|
|
969
|
-
status: vesuStatus,
|
|
970
|
-
calls: vesuCalls,
|
|
971
|
-
transactionMetadata: {
|
|
972
|
-
...vesuTransactionMetadata,
|
|
973
|
-
transactionType: "DEPOSIT",
|
|
974
|
-
},
|
|
975
|
-
});
|
|
976
|
-
} catch (err) {
|
|
977
|
-
logger.error(`Failed moving assets to vault: ${err}`);
|
|
978
|
-
return [
|
|
979
|
-
this.createTransactionResult(
|
|
980
|
-
[],
|
|
981
|
-
false,
|
|
982
|
-
{
|
|
983
|
-
from: Protocols.EXTENDED.name,
|
|
984
|
-
to: Protocols.VAULT.name,
|
|
985
|
-
amount: vesuAmountDifference,
|
|
986
|
-
},
|
|
987
|
-
"NONE",
|
|
988
|
-
CycleType.INVESTMENT
|
|
989
|
-
),
|
|
990
|
-
];
|
|
991
|
-
}
|
|
992
|
-
}
|
|
993
|
-
}
|
|
994
|
-
return transactionResults;
|
|
995
|
-
} catch (err) {
|
|
996
|
-
logger.error(`Failed moving assets to vesu: ${err}`);
|
|
997
|
-
return [
|
|
998
|
-
this.createTransactionResult(
|
|
999
|
-
[],
|
|
1000
|
-
false,
|
|
1001
|
-
{
|
|
1002
|
-
from: Protocols.EXTENDED.name,
|
|
1003
|
-
to: Protocols.VAULT.name,
|
|
1004
|
-
amount: new Web3Number(0, USDC_TOKEN_DECIMALS),
|
|
1005
|
-
},
|
|
1006
|
-
"NONE",
|
|
1007
|
-
CycleType.INVESTMENT
|
|
1008
|
-
),
|
|
1009
|
-
];
|
|
1010
|
-
}
|
|
1011
|
-
}
|
|
1012
|
-
|
|
1013
|
-
/**
|
|
1014
|
-
* Helper method to create transaction result with metadata
|
|
1015
|
-
*/
|
|
1016
|
-
private createTransactionResult(
|
|
1017
|
-
calls: Call[],
|
|
1018
|
-
status: boolean,
|
|
1019
|
-
params: { from: string; to: string; amount: Web3Number },
|
|
1020
|
-
transactionType: "DEPOSIT" | "WITHDRAWAL" | "NONE",
|
|
1021
|
-
cycleType: CycleType
|
|
1022
|
-
): TransactionResult {
|
|
1023
|
-
if (status) {
|
|
1024
|
-
return {
|
|
1025
|
-
calls,
|
|
1026
|
-
status: status,
|
|
1027
|
-
transactionMetadata: {
|
|
1028
|
-
protocolFrom: params.from,
|
|
1029
|
-
protocolTo: params.to,
|
|
1030
|
-
transactionType: transactionType,
|
|
1031
|
-
usdAmount: params.amount.abs().toFixed(),
|
|
1032
|
-
status: "PENDING",
|
|
1033
|
-
cycleType: cycleType,
|
|
1034
|
-
},
|
|
1035
|
-
};
|
|
1036
236
|
}
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
transactionMetadata: {
|
|
1041
|
-
protocolFrom: "",
|
|
1042
|
-
protocolTo: "",
|
|
1043
|
-
transactionType: "DEPOSIT",
|
|
1044
|
-
usdAmount: "0",
|
|
1045
|
-
status: "FAILED",
|
|
1046
|
-
cycleType: cycleType,
|
|
1047
|
-
},
|
|
1048
|
-
};
|
|
1049
|
-
}
|
|
1050
|
-
|
|
1051
|
-
/**
|
|
1052
|
-
* This method is used to move assets between protocols
|
|
1053
|
-
* @param params - The parameters for the move assets operation
|
|
1054
|
-
* @param extendedAdapter - The extended adapter
|
|
1055
|
-
* @param vesuAdapter - The vesu adapter
|
|
1056
|
-
* @returns The transaction result
|
|
1057
|
-
* If Extended amount is greater than amount of withdrawal from extended, then we need to open a long position
|
|
1058
|
-
* so that the amount of withdrawal from extended is fullfilled
|
|
1059
|
-
*/
|
|
1060
|
-
async moveAssets(
|
|
1061
|
-
params: {
|
|
1062
|
-
amount: Web3Number;
|
|
1063
|
-
from: string;
|
|
1064
|
-
to: string;
|
|
1065
|
-
cycleType: CycleType;
|
|
1066
|
-
},
|
|
1067
|
-
extendedAdapter: ExtendedAdapter,
|
|
1068
|
-
vesuAdapter: VesuMultiplyAdapter
|
|
1069
|
-
): Promise<TransactionResult> {
|
|
1070
|
-
try {
|
|
1071
|
-
// Validate amount is positive before starting operations
|
|
1072
|
-
if (params.amount.lessThanOrEqualTo(0)) {
|
|
1073
|
-
logger.error(
|
|
1074
|
-
`Invalid amount for moveAssets: ${params.amount.toNumber()}. Amount must be positive.`
|
|
1075
|
-
);
|
|
1076
|
-
return this.createTransactionResult(
|
|
1077
|
-
[],
|
|
1078
|
-
false,
|
|
1079
|
-
params,
|
|
1080
|
-
"NONE",
|
|
1081
|
-
params.cycleType
|
|
1082
|
-
);
|
|
1083
|
-
}
|
|
1084
|
-
|
|
1085
|
-
// Check minimum movement amounts before starting operations
|
|
1086
|
-
// const amountAbs = params.amount.abs();
|
|
1087
|
-
// if (params.from === Protocols.EXTENDED.name || params.to === Protocols.EXTENDED.name) {
|
|
1088
|
-
// if (amountAbs.lessThanOrEqualTo(extendedAdapter.minimumExtendedMovementAmount)) {
|
|
1089
|
-
// logger.warn(
|
|
1090
|
-
// `Amount ${amountAbs.toNumber()} is below minimum Extended movement amount ${extendedAdapter.minimumExtendedMovementAmount}. Skipping operation.`
|
|
1091
|
-
// );
|
|
1092
|
-
// return this.createTransactionResult([], false, params, "NONE", params.cycleType);
|
|
1093
|
-
// }
|
|
1094
|
-
// }
|
|
1095
|
-
// if (params.from === Protocols.VESU.name || params.to === Protocols.VESU.name) {
|
|
1096
|
-
// if (amountAbs.lessThanOrEqualTo(vesuAdapter.minimumVesuMovementAmount)) {
|
|
1097
|
-
// logger.warn(
|
|
1098
|
-
// `Amount ${amountAbs.toNumber()} is below minimum Vesu movement amount ${vesuAdapter.minimumVesuMovementAmount}. Skipping operation.`
|
|
1099
|
-
// );
|
|
1100
|
-
// return this.createTransactionResult([], false, params, "NONE", params.cycleType);
|
|
1101
|
-
// }
|
|
1102
|
-
// }
|
|
1103
|
-
|
|
1104
|
-
const avnuAdapter = await this.getAvnuAdapter();
|
|
1105
|
-
if (!avnuAdapter) {
|
|
1106
|
-
logger.error(`avnu adapter not found: ${avnuAdapter}`);
|
|
1107
|
-
return this.createTransactionResult(
|
|
1108
|
-
[],
|
|
1109
|
-
false,
|
|
1110
|
-
params,
|
|
1111
|
-
"NONE",
|
|
1112
|
-
params.cycleType
|
|
1113
|
-
);
|
|
1114
|
-
}
|
|
1115
|
-
logger.info(`moveAssets params, ${JSON.stringify(params)}`);
|
|
1116
|
-
const collateralToken = vesuAdapter.config.supportedPositions[0].asset;
|
|
1117
|
-
const { collateralPrice } = await this.getAssetPrices();
|
|
1118
|
-
|
|
1119
|
-
if (
|
|
1120
|
-
params.to === Protocols.EXTENDED.name &&
|
|
1121
|
-
params.from === Protocols.VAULT.name
|
|
1122
|
-
) {
|
|
1123
|
-
const proofsInfo = extendedAdapter.getProofs(
|
|
1124
|
-
true,
|
|
1125
|
-
this.getMerkleTree()
|
|
1126
|
-
);
|
|
1127
|
-
const calls = [];
|
|
1128
|
-
const proofGroups = proofsInfo.proofs;
|
|
1129
|
-
const call = this.getManageCall(
|
|
1130
|
-
proofGroups,
|
|
1131
|
-
await proofsInfo.callConstructor({ amount: params.amount })
|
|
1132
|
-
);
|
|
1133
|
-
calls.push(call);
|
|
1134
|
-
return this.createTransactionResult(
|
|
1135
|
-
calls,
|
|
1136
|
-
true,
|
|
1137
|
-
params,
|
|
1138
|
-
"DEPOSIT",
|
|
1139
|
-
params.cycleType
|
|
1140
|
-
);
|
|
1141
|
-
} else if (
|
|
1142
|
-
params.to === Protocols.VAULT.name &&
|
|
1143
|
-
params.from === Protocols.EXTENDED.name
|
|
1144
|
-
) {
|
|
1145
|
-
const extendedLeverage = calculateExtendedLevergae();
|
|
1146
|
-
const extendedHoldings =
|
|
1147
|
-
await extendedAdapter.getExtendedDepositAmount();
|
|
1148
|
-
if (!extendedHoldings) {
|
|
1149
|
-
logger.error(`error getting extended holdings: ${extendedHoldings}`);
|
|
1150
|
-
return this.createTransactionResult(
|
|
1151
|
-
[],
|
|
1152
|
-
false,
|
|
1153
|
-
params,
|
|
1154
|
-
"NONE",
|
|
1155
|
-
params.cycleType
|
|
1156
|
-
);
|
|
1157
|
-
}
|
|
1158
|
-
const extendedHoldingAmount = new Web3Number(
|
|
1159
|
-
extendedHoldings.availableForWithdrawal,
|
|
1160
|
-
USDC_TOKEN_DECIMALS
|
|
1161
|
-
);
|
|
1162
|
-
logger.info(
|
|
1163
|
-
`${
|
|
1164
|
-
VesuExtendedMultiplierStrategy.name
|
|
1165
|
-
}::moveAssets extendedHoldingAmount: ${extendedHoldingAmount.toNumber()}`
|
|
1166
|
-
);
|
|
1167
|
-
const extendedPositions = await extendedAdapter.getAllOpenPositions();
|
|
1168
|
-
if (!extendedPositions) {
|
|
1169
|
-
logger.error(
|
|
1170
|
-
`error getting extended positions: ${extendedPositions} while moving assets from extended to vault`
|
|
1171
|
-
);
|
|
1172
|
-
return this.createTransactionResult(
|
|
1173
|
-
[],
|
|
1174
|
-
false,
|
|
1175
|
-
params,
|
|
1176
|
-
"NONE",
|
|
1177
|
-
params.cycleType
|
|
1178
|
-
);
|
|
1179
|
-
}
|
|
1180
|
-
if (params.amount.abs().greaterThan(extendedHoldingAmount)) {
|
|
1181
|
-
const leftAmountAfterWithdrawalAmountInAccount = new Web3Number(
|
|
1182
|
-
Math.ceil(
|
|
1183
|
-
params.amount.abs().minus(extendedHoldingAmount).toNumber()
|
|
1184
|
-
),
|
|
1185
|
-
USDC_TOKEN_DECIMALS
|
|
1186
|
-
);
|
|
1187
|
-
const positionAmountToClose =
|
|
1188
|
-
await calculatePositionToCloseToWithdrawAmount(
|
|
1189
|
-
extendedHoldings,
|
|
1190
|
-
extendedPositions[0],
|
|
1191
|
-
params.amount
|
|
1192
|
-
);
|
|
1193
|
-
logger.info(
|
|
1194
|
-
`positionAmountToClose: ${positionAmountToClose} this is without leverage`
|
|
1195
|
-
);
|
|
1196
|
-
logger.info(
|
|
1197
|
-
`${
|
|
1198
|
-
VesuExtendedMultiplierStrategy.name
|
|
1199
|
-
}::moveAssets leftAmountAfterWithdrawalAmountInAccount: ${leftAmountAfterWithdrawalAmountInAccount.toNumber()}`
|
|
1200
|
-
);
|
|
1201
|
-
let priceOfBTC;
|
|
1202
|
-
const { ask, bid, status } =
|
|
1203
|
-
await extendedAdapter.fetchOrderBookBTCUSDC();
|
|
1204
|
-
const price = ask.plus(bid).dividedBy(2);
|
|
1205
|
-
if (status) {
|
|
1206
|
-
priceOfBTC = price;
|
|
1207
|
-
} else {
|
|
1208
|
-
logger.error(`error fetching order book btc usdc: ${status}`);
|
|
1209
|
-
priceOfBTC = collateralPrice.price;
|
|
1210
|
-
}
|
|
1211
|
-
const btcAmount = positionAmountToClose.dividedBy(priceOfBTC);
|
|
1212
|
-
/**
|
|
1213
|
-
* If amount for withdrawal is greater than the amount in extended available for withdrawal,
|
|
1214
|
-
* then we need to open a long position depending on the difference between the two
|
|
1215
|
-
*/
|
|
1216
|
-
const openLongPosition = btcAmount
|
|
1217
|
-
.multipliedBy(3)
|
|
1218
|
-
.greaterThan(MINIMUM_EXTENDED_POSITION_SIZE)
|
|
1219
|
-
? await extendedAdapter.createOrder(
|
|
1220
|
-
extendedLeverage.toString(),
|
|
1221
|
-
btcAmount.toNumber(),
|
|
1222
|
-
OrderSide.BUY
|
|
1223
|
-
)
|
|
1224
|
-
: await extendedAdapter.createOrder(
|
|
1225
|
-
extendedLeverage.toString(),
|
|
1226
|
-
0.000034, // just in case amount falls short then we need to create a withdrawal
|
|
1227
|
-
OrderSide.BUY
|
|
1228
|
-
);
|
|
1229
|
-
if (!openLongPosition) {
|
|
1230
|
-
logger.error(`error opening long position: ${openLongPosition}`);
|
|
1231
|
-
}
|
|
1232
|
-
const updatedHoldings =
|
|
1233
|
-
await extendedAdapter.getExtendedDepositAmount();
|
|
1234
|
-
if (
|
|
1235
|
-
!updatedHoldings ||
|
|
1236
|
-
new Web3Number(
|
|
1237
|
-
updatedHoldings.availableForWithdrawal,
|
|
1238
|
-
USDC_TOKEN_DECIMALS
|
|
1239
|
-
).lessThan(params.amount.abs())
|
|
1240
|
-
) {
|
|
1241
|
-
logger.error(
|
|
1242
|
-
`Insufficient balance after opening position. Available: ${
|
|
1243
|
-
updatedHoldings?.availableForWithdrawal
|
|
1244
|
-
}, Needed: ${params.amount.abs()}`
|
|
1245
|
-
);
|
|
1246
|
-
return this.createTransactionResult(
|
|
1247
|
-
[],
|
|
1248
|
-
false,
|
|
1249
|
-
params,
|
|
1250
|
-
"NONE",
|
|
1251
|
-
params.cycleType
|
|
1252
|
-
);
|
|
1253
|
-
}
|
|
1254
|
-
}
|
|
1255
|
-
const {
|
|
1256
|
-
status: withdrawalFromExtendedStatus,
|
|
1257
|
-
receivedTxnHash: withdrawalFromExtendedTxnHash,
|
|
1258
|
-
} = await extendedAdapter.withdrawFromExtended(params.amount);
|
|
1259
|
-
/**
|
|
1260
|
-
* This logic needs fixing
|
|
1261
|
-
*/
|
|
1262
|
-
logger.info(
|
|
1263
|
-
`withdrawalFromExtendedStatus: ${withdrawalFromExtendedStatus}, withdrawalFromExtendedTxnHash: ${withdrawalFromExtendedTxnHash}`
|
|
1264
|
-
);
|
|
1265
|
-
if (withdrawalFromExtendedStatus && withdrawalFromExtendedTxnHash) {
|
|
1266
|
-
/**
|
|
1267
|
-
* We need to move assets from my wallet back to vault contract
|
|
1268
|
-
*/
|
|
1269
|
-
const extendedHoldings =
|
|
1270
|
-
await extendedAdapter.getExtendedDepositAmount();
|
|
1271
|
-
logger.info(
|
|
1272
|
-
`extendedHoldings after withdrawal ${extendedHoldings?.availableForWithdrawal}`
|
|
1273
|
-
);
|
|
1274
|
-
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
1275
|
-
const { calls, status } = await this.moveAssetsToVaultAllocator(
|
|
1276
|
-
params.amount,
|
|
1277
|
-
extendedAdapter
|
|
1278
|
-
);
|
|
1279
|
-
if (calls.length > 0 && status) {
|
|
1280
|
-
return this.createTransactionResult(
|
|
1281
|
-
calls,
|
|
1282
|
-
true,
|
|
1283
|
-
params,
|
|
1284
|
-
"WITHDRAWAL",
|
|
1285
|
-
params.cycleType
|
|
1286
|
-
);
|
|
1287
|
-
} else {
|
|
1288
|
-
/**
|
|
1289
|
-
* This is a fallback scenario, where the funds were withdrawn from extended, but didn't get transferred to the wallet
|
|
1290
|
-
* We need to return a successful transaction result, but with no calls
|
|
1291
|
-
* Db update will be handled by the risk engine for this specific case
|
|
1292
|
-
*/
|
|
1293
|
-
return this.createTransactionResult(
|
|
1294
|
-
[],
|
|
1295
|
-
true,
|
|
1296
|
-
params,
|
|
1297
|
-
"WITHDRAWAL",
|
|
1298
|
-
params.cycleType
|
|
1299
|
-
);
|
|
1300
|
-
}
|
|
1301
|
-
} else if (
|
|
1302
|
-
withdrawalFromExtendedStatus &&
|
|
1303
|
-
!withdrawalFromExtendedTxnHash
|
|
1304
|
-
) {
|
|
1305
|
-
logger.error(
|
|
1306
|
-
"withdrawal from extended successful, but funds didn't get transferred to the wallet"
|
|
1307
|
-
);
|
|
1308
|
-
return this.createTransactionResult(
|
|
1309
|
-
[],
|
|
1310
|
-
true,
|
|
1311
|
-
params,
|
|
1312
|
-
"WITHDRAWAL",
|
|
1313
|
-
params.cycleType
|
|
1314
|
-
);
|
|
1315
|
-
} else {
|
|
1316
|
-
logger.error("withdrawal from extended failed");
|
|
1317
|
-
return this.createTransactionResult(
|
|
1318
|
-
[],
|
|
1319
|
-
false,
|
|
1320
|
-
params,
|
|
1321
|
-
"NONE",
|
|
1322
|
-
params.cycleType
|
|
1323
|
-
);
|
|
1324
|
-
}
|
|
1325
|
-
} else if (
|
|
1326
|
-
params.to === Protocols.VAULT.name &&
|
|
1327
|
-
params.from === Protocols.VESU.name
|
|
1328
|
-
) {
|
|
1329
|
-
const isPriceDifferenceBetweenAvnuAndExtended =
|
|
1330
|
-
await this.checkPriceDifferenceBetweenAvnuAndExtended(
|
|
1331
|
-
extendedAdapter,
|
|
1332
|
-
vesuAdapter,
|
|
1333
|
-
avnuAdapter,
|
|
1334
|
-
PositionTypeAvnuExtended.CLOSE
|
|
1335
|
-
);
|
|
1336
|
-
if (
|
|
1337
|
-
!isPriceDifferenceBetweenAvnuAndExtended &&
|
|
1338
|
-
params.cycleType === CycleType.WITHDRAWAL
|
|
1339
|
-
) {
|
|
1340
|
-
logger.warn(
|
|
1341
|
-
`price difference between avnu and extended doesn't fit the range for close position, ${avnuAdapter.config.maximumExtendedPriceDifferenceForSwapClosing}`
|
|
1342
|
-
);
|
|
1343
|
-
return this.createTransactionResult(
|
|
1344
|
-
[],
|
|
1345
|
-
false,
|
|
1346
|
-
params,
|
|
1347
|
-
"NONE",
|
|
1348
|
-
params.cycleType
|
|
1349
|
-
);
|
|
1350
|
-
}
|
|
1351
|
-
//withdraw from vesu
|
|
1352
|
-
const vesuAmountInBTC = new Web3Number(
|
|
1353
|
-
params.amount
|
|
1354
|
-
.dividedBy(collateralPrice.price)
|
|
1355
|
-
.toFixed(WBTC_TOKEN_DECIMALS),
|
|
1356
|
-
collateralToken.decimals
|
|
1357
|
-
);
|
|
1358
|
-
const proofsInfo = vesuAdapter.getProofs(false, this.getMerkleTree());
|
|
1359
|
-
const calls = [];
|
|
1360
|
-
const proofGroups = proofsInfo.proofs;
|
|
1361
|
-
const call = this.getManageCall(
|
|
1362
|
-
proofGroups,
|
|
1363
|
-
await proofsInfo.callConstructor({ amount: vesuAmountInBTC })
|
|
1364
|
-
);
|
|
1365
|
-
calls.push(call);
|
|
1366
|
-
const swapProofsInfo = avnuAdapter.getProofs(
|
|
1367
|
-
false,
|
|
1368
|
-
this.getMerkleTree()
|
|
1369
|
-
);
|
|
1370
|
-
const swapProofGroups = swapProofsInfo.proofs;
|
|
1371
|
-
const swapCall = this.getManageCall(
|
|
1372
|
-
swapProofGroups,
|
|
1373
|
-
await swapProofsInfo.callConstructor({ amount: vesuAmountInBTC })
|
|
1374
|
-
);
|
|
1375
|
-
calls.push(swapCall);
|
|
1376
|
-
return this.createTransactionResult(
|
|
1377
|
-
calls,
|
|
1378
|
-
true,
|
|
1379
|
-
params,
|
|
1380
|
-
"WITHDRAWAL",
|
|
1381
|
-
params.cycleType
|
|
1382
|
-
);
|
|
1383
|
-
} else if (
|
|
1384
|
-
params.to === Protocols.EXTENDED.name &&
|
|
1385
|
-
params.from === Protocols.VESU.name
|
|
1386
|
-
) {
|
|
1387
|
-
const isPriceDifferenceBetweenAvnuAndExtended =
|
|
1388
|
-
await this.checkPriceDifferenceBetweenAvnuAndExtended(
|
|
1389
|
-
extendedAdapter,
|
|
1390
|
-
vesuAdapter,
|
|
1391
|
-
avnuAdapter,
|
|
1392
|
-
PositionTypeAvnuExtended.CLOSE
|
|
1393
|
-
);
|
|
1394
|
-
if (!isPriceDifferenceBetweenAvnuAndExtended) {
|
|
1395
|
-
logger.warn(
|
|
1396
|
-
`price difference between avnu and extended doesn't fit the range for close position, ${avnuAdapter.config.maximumExtendedPriceDifferenceForSwapClosing}`
|
|
1397
|
-
);
|
|
1398
|
-
return this.createTransactionResult(
|
|
1399
|
-
[],
|
|
1400
|
-
false,
|
|
1401
|
-
params,
|
|
1402
|
-
"NONE",
|
|
1403
|
-
params.cycleType
|
|
1404
|
-
);
|
|
1405
|
-
}
|
|
1406
|
-
const vesuAmountInBTC = new Web3Number(
|
|
1407
|
-
params.amount.dividedBy(collateralPrice.price).toNumber(),
|
|
1408
|
-
collateralToken.decimals
|
|
1409
|
-
);
|
|
1410
|
-
const proofsInfo = vesuAdapter.getProofs(false, this.getMerkleTree());
|
|
1411
|
-
const calls = [];
|
|
1412
|
-
const proofGroups = proofsInfo.proofs;
|
|
1413
|
-
const call = this.getManageCall(
|
|
1414
|
-
proofGroups,
|
|
1415
|
-
await proofsInfo.callConstructor({ amount: vesuAmountInBTC })
|
|
1416
|
-
);
|
|
1417
|
-
calls.push(call);
|
|
1418
|
-
const swapProofsInfo = avnuAdapter.getProofs(
|
|
1419
|
-
false,
|
|
1420
|
-
this.getMerkleTree()
|
|
1421
|
-
);
|
|
1422
|
-
const swapProofGroups = swapProofsInfo.proofs;
|
|
1423
|
-
const swapCall = this.getManageCall(
|
|
1424
|
-
swapProofGroups,
|
|
1425
|
-
await swapProofsInfo.callConstructor({ amount: vesuAmountInBTC })
|
|
1426
|
-
);
|
|
1427
|
-
calls.push(swapCall);
|
|
1428
|
-
const proofsInfoDeposit = extendedAdapter.getProofs(
|
|
1429
|
-
true,
|
|
1430
|
-
this.getMerkleTree()
|
|
1431
|
-
);
|
|
1432
|
-
//Deposit Amount would still be in usdc
|
|
1433
|
-
const proofGroupsDeposit = proofsInfoDeposit.proofs;
|
|
1434
|
-
const callDeposit = this.getManageCall(
|
|
1435
|
-
proofGroupsDeposit,
|
|
1436
|
-
await proofsInfoDeposit.callConstructor({ amount: params.amount })
|
|
1437
|
-
);
|
|
1438
|
-
calls.push(callDeposit);
|
|
1439
|
-
return this.createTransactionResult(
|
|
1440
|
-
calls,
|
|
1441
|
-
true,
|
|
1442
|
-
params,
|
|
1443
|
-
"DEPOSIT",
|
|
1444
|
-
params.cycleType
|
|
1445
|
-
);
|
|
1446
|
-
}
|
|
1447
|
-
logger.error(
|
|
1448
|
-
`Unsupported assets movement: ${params.from} to ${params.to}`
|
|
1449
|
-
);
|
|
1450
|
-
return this.createTransactionResult(
|
|
1451
|
-
[],
|
|
1452
|
-
false,
|
|
1453
|
-
params,
|
|
1454
|
-
"NONE",
|
|
1455
|
-
params.cycleType
|
|
1456
|
-
);
|
|
1457
|
-
} catch (err) {
|
|
1458
|
-
logger.error(`error moving assets: ${err}`);
|
|
1459
|
-
return this.createTransactionResult(
|
|
1460
|
-
[],
|
|
1461
|
-
false,
|
|
1462
|
-
params,
|
|
1463
|
-
"NONE",
|
|
1464
|
-
params.cycleType
|
|
237
|
+
if (!extendedAdapter.adapter || !(extendedAdapter.adapter as ExtendedAdapter).client) {
|
|
238
|
+
throw new Error(
|
|
239
|
+
`${this.getTag()} Extended adapter client not initialized.`
|
|
1465
240
|
);
|
|
1466
241
|
}
|
|
242
|
+
return extendedAdapter.adapter as ExtendedAdapter;
|
|
1467
243
|
}
|
|
1468
244
|
|
|
1469
|
-
async
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
false,
|
|
1477
|
-
{
|
|
1478
|
-
from: Protocols.VAULT.name,
|
|
1479
|
-
to: Protocols.VAULT.name,
|
|
1480
|
-
amount: new Web3Number(0, 0),
|
|
1481
|
-
},
|
|
1482
|
-
"NONE",
|
|
1483
|
-
CycleType.INVESTMENT
|
|
1484
|
-
);
|
|
1485
|
-
} catch (err) {
|
|
1486
|
-
logger.error(`error handling deposit: ${err}`);
|
|
1487
|
-
return this.createTransactionResult(
|
|
1488
|
-
[],
|
|
1489
|
-
false,
|
|
1490
|
-
{
|
|
1491
|
-
from: Protocols.VAULT.name,
|
|
1492
|
-
to: Protocols.VAULT.name,
|
|
1493
|
-
amount: new Web3Number(0, 0),
|
|
1494
|
-
},
|
|
1495
|
-
"NONE",
|
|
1496
|
-
CycleType.INVESTMENT
|
|
245
|
+
async getUsdcToUsdceAdapter(): Promise<UsdcToUsdceAdapter> {
|
|
246
|
+
const usdcToUsdceAdapter = this.metadata.additionalInfo.adapters.find(
|
|
247
|
+
(adapter) => adapter.adapter.name === UsdcToUsdceAdapter.name,
|
|
248
|
+
);
|
|
249
|
+
if (!usdcToUsdceAdapter) {
|
|
250
|
+
throw new Error(
|
|
251
|
+
`${this.getTag()} UsdcToUsdce adapter not configured in metadata.`
|
|
1497
252
|
);
|
|
1498
253
|
}
|
|
254
|
+
return usdcToUsdceAdapter.adapter as UsdcToUsdceAdapter;
|
|
1499
255
|
}
|
|
1500
256
|
|
|
1501
257
|
/**
|
|
1502
|
-
*
|
|
1503
|
-
*
|
|
1504
|
-
*
|
|
1505
|
-
*
|
|
1506
|
-
* @param
|
|
1507
|
-
* @
|
|
258
|
+
* Creates an ExecutionService wired to this strategy's adapters and config.
|
|
259
|
+
* Use with `stateManager.solve()` to get a SolveResult, then pass it to
|
|
260
|
+
* `executionService.execute(solveResult)` for execution.
|
|
261
|
+
*
|
|
262
|
+
* @param onExecutionEvent - Optional callback for execution lifecycle events (DB persistence, alerts, etc.)
|
|
263
|
+
* @param extendedAcceptableSlippageBps - Slippage for Extended limit orders (default: 10 = 0.1%)
|
|
264
|
+
* @param maxPriceDivergenceBps - Max price divergence between Extended and AVNU (default: 50 = 0.5%)
|
|
1508
265
|
*/
|
|
1509
|
-
async
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
266
|
+
async createExecutionService(opts?: {
|
|
267
|
+
onExecutionEvent?: ExecutionCallback;
|
|
268
|
+
extendedAcceptableSlippageBps?: number;
|
|
269
|
+
maxPriceDivergenceBps?: number;
|
|
270
|
+
}): Promise<ExecutionService> {
|
|
271
|
+
const [
|
|
272
|
+
vesuAdapter,
|
|
273
|
+
vesuModifyPositionAdapter,
|
|
274
|
+
usdceTransferAdapter,
|
|
275
|
+
extendedAdapter,
|
|
276
|
+
avnuAdapter,
|
|
277
|
+
usdcToUsdceAdapter,
|
|
278
|
+
] =
|
|
279
|
+
await Promise.all([
|
|
280
|
+
this.getVesuAdapter(),
|
|
281
|
+
this.getVesuModifyPositionAdapter(),
|
|
282
|
+
this.getUsdceTransferAdapter(),
|
|
283
|
+
this.getExtendedAdapter(),
|
|
284
|
+
this.getAvnuAdapter(),
|
|
285
|
+
this.getUsdcToUsdceAdapter(),
|
|
286
|
+
]);
|
|
287
|
+
|
|
288
|
+
const executionConfig: ExecutionConfig = {
|
|
289
|
+
networkConfig: this.config,
|
|
290
|
+
pricer: this.pricer,
|
|
291
|
+
vesuAdapter,
|
|
292
|
+
vesuModifyPositionAdapter,
|
|
293
|
+
extendedAdapter,
|
|
294
|
+
avnuAdapter,
|
|
295
|
+
usdcToUsdceAdapter,
|
|
296
|
+
vaultAllocator: this.metadata.additionalInfo.vaultAllocator,
|
|
297
|
+
walletAddress: this.metadata.additionalInfo.walletAddress,
|
|
298
|
+
usdceTransferAdapter,
|
|
299
|
+
wbtcToken: this.wbtcToken,
|
|
300
|
+
usdcToken: this.usdcToken,
|
|
301
|
+
usdceToken: this.usdceToken,
|
|
302
|
+
getMerkleTree: () => this.getMerkleTree(),
|
|
303
|
+
getManageCall: (proofs, manageCalls) => this.getManageCall(proofs, manageCalls),
|
|
304
|
+
getBringLiquidityCall: (params) => this.getBringLiquidityCall(params),
|
|
305
|
+
onExecutionEvent: opts?.onExecutionEvent,
|
|
306
|
+
extendedAcceptableSlippageBps: opts?.extendedAcceptableSlippageBps,
|
|
307
|
+
maxPriceDivergenceBps: opts?.maxPriceDivergenceBps,
|
|
308
|
+
};
|
|
1521
309
|
|
|
1522
|
-
|
|
1523
|
-
logger.error(`error getting btc price avnu: ${btcPriceAvnu}`);
|
|
1524
|
-
return false;
|
|
1525
|
-
}
|
|
1526
|
-
logger.info(`price: ${price}`);
|
|
1527
|
-
logger.info(`btcPriceAvnu: ${btcPriceAvnu}`);
|
|
1528
|
-
const priceDifference = new Web3Number(
|
|
1529
|
-
price.minus(btcPriceAvnu).toFixed(2),
|
|
1530
|
-
0
|
|
1531
|
-
);
|
|
1532
|
-
logger.info(`priceDifference: ${priceDifference}`);
|
|
1533
|
-
if (priceDifference.isNegative()) {
|
|
1534
|
-
return false;
|
|
1535
|
-
}
|
|
1536
|
-
if (positionType === PositionTypeAvnuExtended.OPEN) {
|
|
1537
|
-
logger.info(
|
|
1538
|
-
`price difference between avnu and extended for open position: ${priceDifference.toNumber()}, minimumExtendedPriceDifferenceForSwapOpen: ${
|
|
1539
|
-
avnuAdapter.config.minimumExtendedPriceDifferenceForSwapOpen
|
|
1540
|
-
}`
|
|
1541
|
-
);
|
|
1542
|
-
const result = priceDifference.greaterThanOrEqualTo(
|
|
1543
|
-
avnuAdapter.config.minimumExtendedPriceDifferenceForSwapOpen
|
|
1544
|
-
); // 500 for now
|
|
1545
|
-
logger.info(`result: ${result}`);
|
|
1546
|
-
return result;
|
|
1547
|
-
} else {
|
|
1548
|
-
logger.info(
|
|
1549
|
-
`price difference between avnu and extended for close position: ${priceDifference.toNumber()}, maximumExtendedPriceDifferenceForSwapClosing: ${
|
|
1550
|
-
avnuAdapter.config.maximumExtendedPriceDifferenceForSwapClosing
|
|
1551
|
-
}`
|
|
1552
|
-
);
|
|
1553
|
-
const result = priceDifference.lessThanOrEqualTo(
|
|
1554
|
-
avnuAdapter.config.maximumExtendedPriceDifferenceForSwapClosing
|
|
1555
|
-
); // 1000 for now
|
|
1556
|
-
logger.info(`result: ${result}`);
|
|
1557
|
-
return result;
|
|
1558
|
-
}
|
|
310
|
+
return new ExecutionService(executionConfig);
|
|
1559
311
|
}
|
|
1560
312
|
|
|
1561
313
|
/**
|
|
1562
|
-
*
|
|
1563
|
-
*
|
|
1564
|
-
*
|
|
314
|
+
* Calculates the total Assets Under Management across all adapters.
|
|
315
|
+
* Aggregates position values from every adapter, converts to the vault's
|
|
316
|
+
* base asset, and returns the net AUM along with the previous AUM snapshot
|
|
317
|
+
* and per-position breakdowns.
|
|
1565
318
|
*/
|
|
1566
|
-
async handleWithdraw(amount: Web3Number): Promise<TransactionResult[]> {
|
|
1567
|
-
try {
|
|
1568
|
-
const usdcBalanceVaultAllocator = await this.getUnusedBalance();
|
|
1569
|
-
const usdcBalanceDifference = amount
|
|
1570
|
-
.plus(BUFFER_USDC_IN_WITHDRAWAL)
|
|
1571
|
-
.minus(usdcBalanceVaultAllocator.usdValue);
|
|
1572
|
-
logger.info(`usdcBalanceDifference, ${usdcBalanceDifference.toNumber()}`);
|
|
1573
|
-
let calls: Call[] = [];
|
|
1574
|
-
let status: boolean = true;
|
|
1575
|
-
if (usdcBalanceDifference.lessThan(0)) {
|
|
1576
|
-
const withdrawCall = await this.getBringLiquidityCall({
|
|
1577
|
-
amount: usdcBalanceVaultAllocator.amount,
|
|
1578
|
-
});
|
|
1579
|
-
calls.push(withdrawCall);
|
|
1580
|
-
return [
|
|
1581
|
-
this.createTransactionResult(
|
|
1582
|
-
calls,
|
|
1583
|
-
true,
|
|
1584
|
-
{
|
|
1585
|
-
from: Protocols.VAULT.name,
|
|
1586
|
-
to: Protocols.NONE.name,
|
|
1587
|
-
amount: amount,
|
|
1588
|
-
},
|
|
1589
|
-
"WITHDRAWAL",
|
|
1590
|
-
CycleType.WITHDRAWAL
|
|
1591
|
-
),
|
|
1592
|
-
];
|
|
1593
|
-
}
|
|
1594
|
-
const vesuAdapter = await this.getVesuAdapter();
|
|
1595
|
-
const extendedAdapter = await this.getExtendedAdapter();
|
|
1596
|
-
if (!vesuAdapter || !extendedAdapter || !extendedAdapter.client) {
|
|
1597
|
-
status = false;
|
|
1598
|
-
logger.error(
|
|
1599
|
-
`vesu or extended adapter not found: vesuAdapter=${vesuAdapter}, extendedAdapter=${extendedAdapter}`
|
|
1600
|
-
);
|
|
1601
|
-
return [
|
|
1602
|
-
this.createTransactionResult(
|
|
1603
|
-
calls,
|
|
1604
|
-
status,
|
|
1605
|
-
{
|
|
1606
|
-
from: Protocols.VAULT.name,
|
|
1607
|
-
to: Protocols.NONE.name,
|
|
1608
|
-
amount: amount,
|
|
1609
|
-
},
|
|
1610
|
-
"NONE",
|
|
1611
|
-
CycleType.WITHDRAWAL
|
|
1612
|
-
),
|
|
1613
|
-
];
|
|
1614
|
-
}
|
|
1615
|
-
let transactionResults: TransactionResult[] = [];
|
|
1616
|
-
const { collateralTokenAmount } =
|
|
1617
|
-
await vesuAdapter.vesuAdapter.getAssetPrices();
|
|
1618
|
-
const { collateralPrice } = await this.getAssetPrices();
|
|
1619
|
-
const extendedPositon = await extendedAdapter.getAllOpenPositions();
|
|
1620
|
-
if (!extendedPositon) {
|
|
1621
|
-
status = false;
|
|
1622
|
-
logger.error("error getting extended position", extendedPositon);
|
|
1623
|
-
return [
|
|
1624
|
-
this.createTransactionResult(
|
|
1625
|
-
calls,
|
|
1626
|
-
status,
|
|
1627
|
-
{
|
|
1628
|
-
from: Protocols.VAULT.name,
|
|
1629
|
-
to: Protocols.NONE.name,
|
|
1630
|
-
amount: amount,
|
|
1631
|
-
},
|
|
1632
|
-
"NONE",
|
|
1633
|
-
CycleType.WITHDRAWAL
|
|
1634
|
-
),
|
|
1635
|
-
];
|
|
1636
|
-
}
|
|
1637
|
-
const amountDistributionForWithdrawal =
|
|
1638
|
-
await calculateAmountDistributionForWithdrawal(
|
|
1639
|
-
usdcBalanceDifference,
|
|
1640
|
-
collateralPrice.price,
|
|
1641
|
-
collateralTokenAmount,
|
|
1642
|
-
extendedPositon
|
|
1643
|
-
);
|
|
1644
|
-
if (!amountDistributionForWithdrawal) {
|
|
1645
|
-
status = false;
|
|
1646
|
-
logger.error(
|
|
1647
|
-
`error calculating amount distribution for withdrawal: ${amountDistributionForWithdrawal}`
|
|
1648
|
-
);
|
|
1649
|
-
return [
|
|
1650
|
-
this.createTransactionResult(
|
|
1651
|
-
calls,
|
|
1652
|
-
status,
|
|
1653
|
-
{
|
|
1654
|
-
from: Protocols.VAULT.name,
|
|
1655
|
-
to: Protocols.NONE.name,
|
|
1656
|
-
amount: amount,
|
|
1657
|
-
},
|
|
1658
|
-
"NONE",
|
|
1659
|
-
CycleType.WITHDRAWAL
|
|
1660
|
-
),
|
|
1661
|
-
];
|
|
1662
|
-
}
|
|
1663
|
-
const { vesu_amount, extended_amount } = amountDistributionForWithdrawal;
|
|
1664
|
-
|
|
1665
|
-
if (status && vesu_amount.greaterThan(0)) {
|
|
1666
|
-
const {
|
|
1667
|
-
calls: vesuCalls,
|
|
1668
|
-
status: vesuStatus,
|
|
1669
|
-
transactionMetadata: vesuTransactionMetadata,
|
|
1670
|
-
} = await this.moveAssets(
|
|
1671
|
-
{
|
|
1672
|
-
amount: vesu_amount,
|
|
1673
|
-
from: Protocols.VESU.name,
|
|
1674
|
-
to: Protocols.VAULT.name,
|
|
1675
|
-
cycleType: CycleType.WITHDRAWAL,
|
|
1676
|
-
},
|
|
1677
|
-
extendedAdapter,
|
|
1678
|
-
vesuAdapter
|
|
1679
|
-
);
|
|
1680
|
-
status = vesuStatus;
|
|
1681
|
-
transactionResults.push({
|
|
1682
|
-
status: vesuStatus,
|
|
1683
|
-
calls: vesuCalls,
|
|
1684
|
-
transactionMetadata: vesuTransactionMetadata,
|
|
1685
|
-
});
|
|
1686
|
-
}
|
|
1687
|
-
if (status && extended_amount.greaterThan(0)) {
|
|
1688
|
-
const {
|
|
1689
|
-
calls: extendedCalls,
|
|
1690
|
-
status: extendedStatus,
|
|
1691
|
-
transactionMetadata: extendedTransactionMetadata,
|
|
1692
|
-
} = await this.moveAssets(
|
|
1693
|
-
{
|
|
1694
|
-
amount: extended_amount,
|
|
1695
|
-
from: Protocols.EXTENDED.name,
|
|
1696
|
-
to: Protocols.VAULT.name,
|
|
1697
|
-
cycleType: CycleType.WITHDRAWAL,
|
|
1698
|
-
},
|
|
1699
|
-
extendedAdapter,
|
|
1700
|
-
vesuAdapter
|
|
1701
|
-
);
|
|
1702
|
-
status = extendedStatus;
|
|
1703
|
-
if (status) {
|
|
1704
|
-
transactionResults.push({
|
|
1705
|
-
status: extendedStatus,
|
|
1706
|
-
calls: extendedCalls,
|
|
1707
|
-
transactionMetadata: extendedTransactionMetadata,
|
|
1708
|
-
});
|
|
1709
|
-
} else {
|
|
1710
|
-
logger.error(
|
|
1711
|
-
"error moving assets to vault: extendedStatus: ${extendedStatus}"
|
|
1712
|
-
);
|
|
1713
|
-
return [
|
|
1714
|
-
this.createTransactionResult(
|
|
1715
|
-
[],
|
|
1716
|
-
status,
|
|
1717
|
-
{
|
|
1718
|
-
from: Protocols.VAULT.name,
|
|
1719
|
-
to: Protocols.NONE.name,
|
|
1720
|
-
amount: amount,
|
|
1721
|
-
},
|
|
1722
|
-
"NONE",
|
|
1723
|
-
CycleType.WITHDRAWAL
|
|
1724
|
-
),
|
|
1725
|
-
];
|
|
1726
|
-
}
|
|
1727
|
-
}
|
|
1728
|
-
const withdrawCall = await this.getBringLiquidityCall({
|
|
1729
|
-
amount: amount,
|
|
1730
|
-
});
|
|
1731
|
-
logger.info("withdraw call", withdrawCall);
|
|
1732
|
-
transactionResults.push({
|
|
1733
|
-
status: status,
|
|
1734
|
-
calls: [withdrawCall],
|
|
1735
|
-
transactionMetadata: {
|
|
1736
|
-
protocolFrom: Protocols.VAULT.name,
|
|
1737
|
-
protocolTo: Protocols.NONE.name,
|
|
1738
|
-
transactionType: "WITHDRAWAL",
|
|
1739
|
-
usdAmount: amount.toFixed(),
|
|
1740
|
-
status: "PENDING",
|
|
1741
|
-
cycleType: CycleType.WITHDRAWAL,
|
|
1742
|
-
},
|
|
1743
|
-
});
|
|
1744
|
-
return transactionResults;
|
|
1745
|
-
} catch (err) {
|
|
1746
|
-
logger.error(`error handling withdrawal: ${err}`);
|
|
1747
|
-
return [
|
|
1748
|
-
this.createTransactionResult(
|
|
1749
|
-
[],
|
|
1750
|
-
false,
|
|
1751
|
-
{
|
|
1752
|
-
from: Protocols.VAULT.name,
|
|
1753
|
-
to: Protocols.NONE.name,
|
|
1754
|
-
amount: amount,
|
|
1755
|
-
},
|
|
1756
|
-
"NONE",
|
|
1757
|
-
CycleType.WITHDRAWAL
|
|
1758
|
-
),
|
|
1759
|
-
];
|
|
1760
|
-
}
|
|
1761
|
-
}
|
|
1762
|
-
|
|
1763
319
|
async getAUM(): Promise<{
|
|
1764
320
|
net: SingleTokenInfo;
|
|
1765
321
|
prevAum: Web3Number;
|
|
@@ -1767,10 +323,14 @@ export class VesuExtendedMultiplierStrategy<
|
|
|
1767
323
|
}> {
|
|
1768
324
|
const allPositions: PositionInfo[] = [];
|
|
1769
325
|
for (let adapter of this.metadata.additionalInfo.adapters) {
|
|
1770
|
-
|
|
1771
|
-
|
|
326
|
+
let positions = await adapter.adapter.getPositions();
|
|
327
|
+
if (positions && positions.length > 0) {
|
|
328
|
+
const filteredPositions = positions.filter((position) => {
|
|
329
|
+
return position.tokenInfo.address !== this.usdceToken.address;
|
|
330
|
+
});
|
|
331
|
+
allPositions.push(...filteredPositions);
|
|
332
|
+
}
|
|
1772
333
|
}
|
|
1773
|
-
|
|
1774
334
|
const assetPrice = await this.pricer.getPrice(this.asset().symbol);
|
|
1775
335
|
let netAUM = new Web3Number(0, this.asset().decimals);
|
|
1776
336
|
for (let position of allPositions) {
|
|
@@ -1781,6 +341,16 @@ export class VesuExtendedMultiplierStrategy<
|
|
|
1781
341
|
}
|
|
1782
342
|
}
|
|
1783
343
|
|
|
344
|
+
// ! IMO should also include USDC in wallet.
|
|
345
|
+
const walletHoldings = await this.getWalletHoldings();
|
|
346
|
+
for (let holding of walletHoldings) {
|
|
347
|
+
if (holding.tokenInfo.address.eq(this.asset().address)) {
|
|
348
|
+
netAUM = netAUM.plus(holding.amount);
|
|
349
|
+
} else {
|
|
350
|
+
netAUM = netAUM.plus(holding.usdValue / assetPrice.price);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
1784
354
|
const prevAum = await this.getPrevAUM();
|
|
1785
355
|
const realAUM: PositionInfo = {
|
|
1786
356
|
tokenInfo: this.asset(),
|
|
@@ -1811,140 +381,101 @@ export class VesuExtendedMultiplierStrategy<
|
|
|
1811
381
|
};
|
|
1812
382
|
}
|
|
1813
383
|
|
|
1814
|
-
async processTransactionDataFromSDK(
|
|
1815
|
-
txnData: TransactionResult<any>[]
|
|
1816
|
-
): Promise<{
|
|
1817
|
-
callsToBeExecutedFinal: Call[];
|
|
1818
|
-
txnMetadata: TransactionMetadata[];
|
|
1819
|
-
} | null> {
|
|
1820
|
-
try {
|
|
1821
|
-
const txnsToBeExecuted = txnData.filter((txn) => {
|
|
1822
|
-
return (
|
|
1823
|
-
txn.transactionMetadata.transactionType !== "NONE" &&
|
|
1824
|
-
txn.transactionMetadata.protocolFrom !== "" &&
|
|
1825
|
-
txn.transactionMetadata.protocolTo !== ""
|
|
1826
|
-
);
|
|
1827
|
-
});
|
|
1828
|
-
const callsToBeExecutedFinal = txnsToBeExecuted.flatMap(
|
|
1829
|
-
(txn) => txn.calls
|
|
1830
|
-
);
|
|
1831
|
-
const txnMetadata = txnsToBeExecuted.map(
|
|
1832
|
-
(txn) => txn.transactionMetadata
|
|
1833
|
-
);
|
|
1834
|
-
return { callsToBeExecutedFinal, txnMetadata };
|
|
1835
|
-
} catch (err) {
|
|
1836
|
-
logger.error(`error processing transaction data from SDK: ${err}`);
|
|
1837
|
-
return null;
|
|
1838
|
-
}
|
|
1839
|
-
}
|
|
1840
|
-
|
|
1841
|
-
async processTransactionMetadata(
|
|
1842
|
-
txnMetadata: TransactionMetadata[],
|
|
1843
|
-
extendedIntentFulfilled: boolean
|
|
1844
|
-
): Promise<TransactionMetadata[] | null> {
|
|
1845
|
-
try {
|
|
1846
|
-
const txnMetadataNew = txnMetadata.map((txn) => {
|
|
1847
|
-
const isExtendedProtocol =
|
|
1848
|
-
txn.protocolFrom === Protocols.EXTENDED.name ||
|
|
1849
|
-
txn.protocolTo === Protocols.EXTENDED.name;
|
|
1850
|
-
// Only update status for extended protocol transactions since thsoe only cause delays
|
|
1851
|
-
if (isExtendedProtocol) {
|
|
1852
|
-
txn.status = extendedIntentFulfilled ? "COMPLETED" : "PENDING";
|
|
1853
|
-
} else {
|
|
1854
|
-
txn.status = "COMPLETED";
|
|
1855
|
-
}
|
|
1856
|
-
return txn;
|
|
1857
|
-
});
|
|
1858
|
-
return txnMetadataNew;
|
|
1859
|
-
} catch (err) {
|
|
1860
|
-
logger.error(`error processing transaction data from SDK: ${err}`);
|
|
1861
|
-
return null;
|
|
1862
|
-
}
|
|
1863
|
-
}
|
|
1864
384
|
|
|
385
|
+
/**
|
|
386
|
+
* Computes the maximum additional USDC that can be borrowed from Vesu
|
|
387
|
+
* while keeping the strategy profitable. Uses the Extended funding rate
|
|
388
|
+
* and Vesu supply APY to derive a break-even borrow APY, then queries
|
|
389
|
+
* Vesu for the max borrowable amount at that rate.
|
|
390
|
+
*/
|
|
1865
391
|
async getMaxBorrowableAmount(): Promise<Web3Number> {
|
|
1866
|
-
const vesuAdapter = await this.
|
|
392
|
+
const vesuAdapter = await this.getVesuModifyPositionAdapter();
|
|
1867
393
|
const extendedAdapter = await this.getExtendedAdapter();
|
|
1868
|
-
if (!vesuAdapter || !extendedAdapter) {
|
|
1869
|
-
return new Web3Number(0, 0);
|
|
1870
|
-
}
|
|
1871
394
|
const extendedFundingRate = new Web3Number(
|
|
1872
395
|
(await extendedAdapter.getNetAPY()).toFixed(4),
|
|
1873
|
-
0
|
|
396
|
+
0,
|
|
1874
397
|
);
|
|
1875
398
|
const extendedPositions = await extendedAdapter.getAllOpenPositions();
|
|
1876
399
|
if (!extendedPositions || extendedPositions.length === 0) {
|
|
1877
|
-
logger.
|
|
400
|
+
logger.warn(`${this.getTag()} getMaxBorrowableAmount: no extended positions found`);
|
|
1878
401
|
return new Web3Number(0, 0);
|
|
1879
402
|
}
|
|
1880
403
|
const extendePositionSizeUSD = new Web3Number(
|
|
1881
404
|
extendedPositions[0].value || 0,
|
|
1882
|
-
0
|
|
405
|
+
0,
|
|
1883
406
|
);
|
|
1884
407
|
const vesuPositions = await vesuAdapter.getPositions();
|
|
1885
408
|
const vesuSupplyApy = vesuPositions[0].apy.apy;
|
|
1886
409
|
const vesuCollateralSizeUSD = new Web3Number(
|
|
1887
410
|
vesuPositions[0].usdValue.toFixed(USDC_TOKEN_DECIMALS),
|
|
1888
|
-
USDC_TOKEN_DECIMALS
|
|
411
|
+
USDC_TOKEN_DECIMALS,
|
|
1889
412
|
);
|
|
1890
413
|
const vesuDebtSizeUSD = new Web3Number(
|
|
1891
414
|
vesuPositions[1].usdValue.toFixed(USDC_TOKEN_DECIMALS),
|
|
1892
|
-
USDC_TOKEN_DECIMALS
|
|
415
|
+
USDC_TOKEN_DECIMALS,
|
|
1893
416
|
);
|
|
1894
417
|
const num1 = extendePositionSizeUSD.multipliedBy(extendedFundingRate);
|
|
1895
418
|
const num2 = vesuCollateralSizeUSD.multipliedBy(vesuSupplyApy);
|
|
1896
419
|
const num3 = vesuDebtSizeUSD.abs();
|
|
1897
420
|
const maxBorrowApy = num1.plus(num2).minus(0.1).dividedBy(num3);
|
|
1898
|
-
const
|
|
1899
|
-
await vesuAdapter.
|
|
421
|
+
const vesuMaxBorrowableResult =
|
|
422
|
+
await vesuAdapter._vesuAdapter.getMaxBorrowableByInterestRate(
|
|
1900
423
|
this.config,
|
|
1901
424
|
vesuAdapter.config.debt,
|
|
1902
|
-
maxBorrowApy.toNumber()
|
|
425
|
+
maxBorrowApy.toNumber(),
|
|
1903
426
|
);
|
|
1904
427
|
return new Web3Number(
|
|
1905
|
-
|
|
1906
|
-
USDC_TOKEN_DECIMALS
|
|
428
|
+
vesuMaxBorrowableResult.maxDebtToHave.toFixed(USDC_TOKEN_DECIMALS),
|
|
429
|
+
USDC_TOKEN_DECIMALS,
|
|
1907
430
|
);
|
|
1908
431
|
}
|
|
1909
432
|
|
|
433
|
+
/**
|
|
434
|
+
* Returns the current health metrics for the strategy:
|
|
435
|
+
* [0] Vesu health factor (maxLTV / actualLTV) — higher is safer.
|
|
436
|
+
* [1] Extended margin ratio (as percentage) — higher means more margin available.
|
|
437
|
+
*/
|
|
1910
438
|
async getVesuHealthFactors(): Promise<number[]> {
|
|
1911
|
-
const vesuAdapter = await this.
|
|
439
|
+
const vesuAdapter = await this.getVesuModifyPositionAdapter();
|
|
1912
440
|
const extendedAdapter = await this.getExtendedAdapter();
|
|
1913
|
-
if (!vesuAdapter || !extendedAdapter) {
|
|
1914
|
-
return [0, 0];
|
|
1915
|
-
}
|
|
1916
441
|
const vesuPositions = await vesuAdapter.getPositions();
|
|
1917
442
|
const vesuCollateralSizeUSD = new Web3Number(
|
|
1918
443
|
vesuPositions[0].usdValue.toFixed(USDC_TOKEN_DECIMALS),
|
|
1919
|
-
0
|
|
444
|
+
0,
|
|
1920
445
|
);
|
|
1921
446
|
const vesuDebtSizeUSD = new Web3Number(
|
|
1922
447
|
vesuPositions[1].usdValue.toFixed(USDC_TOKEN_DECIMALS),
|
|
1923
|
-
0
|
|
448
|
+
0,
|
|
1924
449
|
);
|
|
1925
450
|
const actualLtv = vesuDebtSizeUSD.dividedBy(vesuCollateralSizeUSD).abs();
|
|
1926
|
-
logger.
|
|
451
|
+
logger.debug(`${this.getTag()} getVesuHealthFactors: actualLtv=${actualLtv.toNumber()}`);
|
|
1927
452
|
const maxLtv = new Web3Number(
|
|
1928
|
-
await vesuAdapter.
|
|
1929
|
-
4
|
|
453
|
+
await vesuAdapter._vesuAdapter.getLTVConfig(this.config),
|
|
454
|
+
4,
|
|
1930
455
|
);
|
|
1931
456
|
const healthFactor = new Web3Number(
|
|
1932
457
|
maxLtv.dividedBy(actualLtv).toFixed(4),
|
|
1933
|
-
4
|
|
458
|
+
4,
|
|
1934
459
|
);
|
|
1935
|
-
logger.
|
|
460
|
+
logger.debug(`${this.getTag()} getVesuHealthFactors: healthFactor=${healthFactor.toNumber()}`);
|
|
1936
461
|
const extendedBalance = await extendedAdapter.getExtendedDepositAmount();
|
|
1937
462
|
if (!extendedBalance) {
|
|
1938
463
|
return [0, 0];
|
|
1939
464
|
}
|
|
1940
465
|
const extendedLeverage = new Web3Number(
|
|
1941
466
|
(Number(extendedBalance.marginRatio) * 100).toFixed(4),
|
|
1942
|
-
4
|
|
467
|
+
4,
|
|
1943
468
|
);
|
|
1944
|
-
logger.
|
|
469
|
+
logger.debug(`${this.getTag()} getVesuHealthFactors: extendedLeverage=${extendedLeverage.toNumber()}`);
|
|
1945
470
|
return [healthFactor.toNumber(), extendedLeverage.toNumber()];
|
|
1946
471
|
}
|
|
1947
472
|
|
|
473
|
+
/**
|
|
474
|
+
* Calculates the weighted net APY of the strategy across all positions.
|
|
475
|
+
* Combines Vesu supply APY (scaled by 0.1 performance fee) and Extended
|
|
476
|
+
* position APY, weighted by their respective USD values.
|
|
477
|
+
* Also returns per-position APY splits.
|
|
478
|
+
*/
|
|
1948
479
|
async netAPY(): Promise<{
|
|
1949
480
|
net: number;
|
|
1950
481
|
splits: { apy: number; id: string }[];
|
|
@@ -1959,23 +490,14 @@ export class VesuExtendedMultiplierStrategy<
|
|
|
1959
490
|
}
|
|
1960
491
|
}
|
|
1961
492
|
const extendedAdapter = await this.getExtendedAdapter();
|
|
1962
|
-
if (!extendedAdapter) {
|
|
1963
|
-
return {
|
|
1964
|
-
net: 0,
|
|
1965
|
-
splits: [],
|
|
1966
|
-
};
|
|
1967
|
-
}
|
|
1968
493
|
let vesuPositions = allPositions.filter(
|
|
1969
|
-
(item) => item.protocol === Protocols.VESU
|
|
494
|
+
(item) => item.protocol === Protocols.VESU,
|
|
1970
495
|
);
|
|
1971
496
|
vesuPositions.map((item) => {
|
|
1972
497
|
item.apy.apy = item.apy.apy * 0.1;
|
|
1973
498
|
});
|
|
1974
499
|
const extendedPositions = await extendedAdapter.getAllOpenPositions();
|
|
1975
|
-
|
|
1976
|
-
(token) => token.symbol === "USDC"
|
|
1977
|
-
);
|
|
1978
|
-
if (!extendedPositions || !usdcToken) {
|
|
500
|
+
if (!extendedPositions || !this.usdcToken) {
|
|
1979
501
|
return {
|
|
1980
502
|
net: 0,
|
|
1981
503
|
splits: [],
|
|
@@ -1988,19 +510,19 @@ export class VesuExtendedMultiplierStrategy<
|
|
|
1988
510
|
const totalHoldingsUSDValue =
|
|
1989
511
|
allPositions.reduce((acc, curr) => acc + curr.usdValue, 0) +
|
|
1990
512
|
Number(extendedEquity);
|
|
1991
|
-
console.log(totalHoldingsUSDValue);
|
|
1992
513
|
const extendedPositionSizeMultipliedByApy =
|
|
1993
514
|
Number(extendedPosition.value) * extendedApy;
|
|
1994
515
|
let weightedAPYs =
|
|
1995
516
|
allPositions.reduce(
|
|
1996
517
|
(acc, curr) => acc + curr.apy.apy * curr.usdValue,
|
|
1997
|
-
0
|
|
518
|
+
0,
|
|
1998
519
|
) + extendedPositionSizeMultipliedByApy;
|
|
1999
|
-
console.log(weightedAPYs);
|
|
2000
520
|
const netAPY = weightedAPYs / totalHoldingsUSDValue;
|
|
2001
|
-
|
|
521
|
+
logger.debug(
|
|
522
|
+
`${this.getTag()} netAPY: holdingsUsd=${totalHoldingsUSDValue}, weightedApy=${weightedAPYs}, net=${netAPY}`,
|
|
523
|
+
);
|
|
2002
524
|
allPositions.push({
|
|
2003
|
-
tokenInfo: usdcToken,
|
|
525
|
+
tokenInfo: this.usdcToken,
|
|
2004
526
|
amount: new Web3Number(extendedPosition.size, 0),
|
|
2005
527
|
usdValue: Number(extendedEquity),
|
|
2006
528
|
apy: { apy: extendedApy, type: APYType.BASE },
|
|
@@ -2016,6 +538,10 @@ export class VesuExtendedMultiplierStrategy<
|
|
|
2016
538
|
};
|
|
2017
539
|
}
|
|
2018
540
|
|
|
541
|
+
/**
|
|
542
|
+
* Fetches the operator wallet's current holdings for USDC.e, USDC, and WBTC,
|
|
543
|
+
* returning each token's balance and USD value.
|
|
544
|
+
*/
|
|
2019
545
|
async getWalletHoldings(): Promise<
|
|
2020
546
|
{
|
|
2021
547
|
tokenInfo: TokenInfo;
|
|
@@ -2023,62 +549,47 @@ export class VesuExtendedMultiplierStrategy<
|
|
|
2023
549
|
usdValue: number;
|
|
2024
550
|
}[]
|
|
2025
551
|
> {
|
|
2026
|
-
|
|
2027
|
-
(token) => token.symbol === "USDCe"
|
|
2028
|
-
);
|
|
2029
|
-
const wbtcToken = Global.getDefaultTokens().find(
|
|
2030
|
-
(token) => token.symbol === "WBTC"
|
|
2031
|
-
);
|
|
2032
|
-
const usdcToken = Global.getDefaultTokens().find(
|
|
2033
|
-
(token) => token.symbol === "USDC"
|
|
2034
|
-
);
|
|
2035
|
-
if (!usdceToken || !wbtcToken || !usdcToken) {
|
|
552
|
+
if (!this.usdceToken || !this.wbtcToken || !this.usdcToken) {
|
|
2036
553
|
return [];
|
|
2037
554
|
}
|
|
2038
555
|
const walletAddress = this.metadata.additionalInfo.walletAddress;
|
|
2039
556
|
const usdceWalletBalance = await new ERC20(this.config).balanceOf(
|
|
2040
|
-
usdceToken.address,
|
|
557
|
+
this.usdceToken.address,
|
|
2041
558
|
walletAddress,
|
|
2042
|
-
usdceToken.decimals
|
|
559
|
+
this.usdceToken.decimals,
|
|
2043
560
|
);
|
|
2044
561
|
const usdcWalletBalance = await new ERC20(this.config).balanceOf(
|
|
2045
|
-
usdcToken.address,
|
|
2046
|
-
walletAddress,
|
|
2047
|
-
usdcToken.decimals
|
|
2048
|
-
);
|
|
2049
|
-
const wbtcWalletBalance = await new ERC20(this.config).balanceOf(
|
|
2050
|
-
wbtcToken.address,
|
|
562
|
+
this.usdcToken.address,
|
|
2051
563
|
walletAddress,
|
|
2052
|
-
|
|
564
|
+
this.usdcToken.decimals,
|
|
2053
565
|
);
|
|
2054
|
-
const price = await this.pricer.getPrice(usdceToken.symbol);
|
|
2055
|
-
const wbtcPrice = await this.pricer.getPrice(wbtcToken.symbol);
|
|
566
|
+
const price = await this.pricer.getPrice(this.usdceToken.symbol);
|
|
567
|
+
const wbtcPrice = await this.pricer.getPrice(this.wbtcToken.symbol);
|
|
2056
568
|
const usdceUsdValue =
|
|
2057
|
-
Number(usdceWalletBalance.toFixed(usdceToken.decimals)) *
|
|
569
|
+
Number(usdceWalletBalance.toFixed(this.usdceToken.decimals)) *
|
|
570
|
+
price.price;
|
|
2058
571
|
const usdcUsdValue =
|
|
2059
|
-
Number(usdcWalletBalance.toFixed(usdcToken.decimals)) * price.price;
|
|
2060
|
-
const wbtcUsdValue =
|
|
2061
|
-
Number(wbtcWalletBalance.toFixed(wbtcToken.decimals)) * wbtcPrice.price;
|
|
572
|
+
Number(usdcWalletBalance.toFixed(this.usdcToken.decimals)) * price.price;
|
|
2062
573
|
return [
|
|
2063
574
|
{
|
|
2064
|
-
tokenInfo: usdceToken,
|
|
575
|
+
tokenInfo: this.usdceToken,
|
|
2065
576
|
amount: usdceWalletBalance,
|
|
2066
577
|
usdValue: usdceUsdValue,
|
|
2067
578
|
},
|
|
2068
579
|
{
|
|
2069
|
-
tokenInfo: usdcToken,
|
|
580
|
+
tokenInfo: this.usdcToken,
|
|
2070
581
|
amount: usdcWalletBalance,
|
|
2071
582
|
usdValue: usdcUsdValue,
|
|
2072
|
-
}
|
|
2073
|
-
{
|
|
2074
|
-
tokenInfo: wbtcToken,
|
|
2075
|
-
amount: wbtcWalletBalance,
|
|
2076
|
-
usdValue: wbtcUsdValue,
|
|
2077
|
-
},
|
|
583
|
+
}
|
|
2078
584
|
];
|
|
2079
585
|
}
|
|
2080
586
|
}
|
|
2081
587
|
|
|
588
|
+
/**
|
|
589
|
+
* Configures all adapters (Vesu, Extended, Avnu, UsdcToUsdce, TokenTransfer)
|
|
590
|
+
* and registers their leaf adapters on the vault settings. This is the central
|
|
591
|
+
* wiring function that connects the strategy to its underlying protocol adapters.
|
|
592
|
+
*/
|
|
2082
593
|
function getLooperSettings(
|
|
2083
594
|
lstSymbol: string,
|
|
2084
595
|
underlyingSymbol: string,
|
|
@@ -2091,15 +602,18 @@ function getLooperSettings(
|
|
|
2091
602
|
minimumVesuMovementAmount: number,
|
|
2092
603
|
minimumExtendedRetriesDelayForOrderStatus: number,
|
|
2093
604
|
minimumExtendedPriceDifferenceForSwapOpen: number,
|
|
2094
|
-
maximumExtendedPriceDifferenceForSwapClosing: number
|
|
605
|
+
maximumExtendedPriceDifferenceForSwapClosing: number,
|
|
2095
606
|
) {
|
|
2096
607
|
vaultSettings.leafAdapters = [];
|
|
2097
608
|
|
|
2098
609
|
const wbtcToken = Global.getDefaultTokens().find(
|
|
2099
|
-
(token) => token.symbol === lstSymbol
|
|
610
|
+
(token) => token.symbol === lstSymbol,
|
|
2100
611
|
)!;
|
|
2101
612
|
const usdcToken = Global.getDefaultTokens().find(
|
|
2102
|
-
(token) => token.symbol === underlyingSymbol
|
|
613
|
+
(token) => token.symbol === underlyingSymbol,
|
|
614
|
+
)!;
|
|
615
|
+
const usdceToken = Global.getDefaultTokens().find(
|
|
616
|
+
(token) => token.symbol === "USDC.e",
|
|
2103
617
|
)!;
|
|
2104
618
|
|
|
2105
619
|
const baseAdapterConfig: BaseAdapterConfig = {
|
|
@@ -2126,9 +640,19 @@ function getLooperSettings(
|
|
|
2126
640
|
maximumExtendedPriceDifferenceForSwapClosing,
|
|
2127
641
|
});
|
|
2128
642
|
|
|
643
|
+
const usdcToUsdceAdapter = new UsdcToUsdceAdapter({
|
|
644
|
+
...baseAdapterConfig,
|
|
645
|
+
supportedPositions: [
|
|
646
|
+
{ asset: usdcToken, isDebt: true },
|
|
647
|
+
{ asset: usdceToken, isDebt: false },
|
|
648
|
+
],
|
|
649
|
+
});
|
|
650
|
+
|
|
2129
651
|
const extendedAdapter = new ExtendedAdapter({
|
|
2130
652
|
...baseAdapterConfig,
|
|
2131
|
-
supportedPositions: [
|
|
653
|
+
supportedPositions: [
|
|
654
|
+
{ asset: usdceToken, isDebt: false },
|
|
655
|
+
],
|
|
2132
656
|
vaultIdExtended: vaultIdExtended,
|
|
2133
657
|
extendedContract: EXTENDED_CONTRACT,
|
|
2134
658
|
extendedBackendWriteUrl: extendedBackendWriteUrl,
|
|
@@ -2147,6 +671,7 @@ function getLooperSettings(
|
|
|
2147
671
|
poolId: pool1,
|
|
2148
672
|
collateral: wbtcToken,
|
|
2149
673
|
debt: usdcToken,
|
|
674
|
+
marginToken: usdcToken,
|
|
2150
675
|
targetHealthFactor: vaultSettings.targetHealthFactor,
|
|
2151
676
|
minHealthFactor: vaultSettings.minHealthFactor,
|
|
2152
677
|
quoteAmountToFetchPrice: vaultSettings.quoteAmountToFetchPrice,
|
|
@@ -2158,8 +683,28 @@ function getLooperSettings(
|
|
|
2158
683
|
minimumVesuMovementAmount: minimumVesuMovementAmount ?? 5, //5 usdc
|
|
2159
684
|
});
|
|
2160
685
|
|
|
2161
|
-
const
|
|
686
|
+
const vesuModifyPositionMaxLtv = VesuConfig.maxLtv;
|
|
687
|
+
const vesuModifyPositionTargetLtv = VesuConfig.targetLtv;
|
|
688
|
+
const vesuModifyPositionAdapter = new VesuModifyPositionAdapter({
|
|
689
|
+
poolId: pool1,
|
|
690
|
+
collateral: wbtcToken,
|
|
691
|
+
debt: usdcToken,
|
|
692
|
+
targetLtv: vesuModifyPositionTargetLtv,
|
|
693
|
+
maxLtv: vesuModifyPositionMaxLtv,
|
|
2162
694
|
...baseAdapterConfig,
|
|
695
|
+
supportedPositions: [
|
|
696
|
+
{ asset: wbtcToken, isDebt: false },
|
|
697
|
+
{ asset: usdcToken, isDebt: true },
|
|
698
|
+
],
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
// Transfers USDC between the vault allocator (fromAddress) and the operator wallet (toAddress)
|
|
702
|
+
const usdceTransferAdapter = new TokenTransferAdapter({
|
|
703
|
+
...baseAdapterConfig,
|
|
704
|
+
baseToken: usdceToken,
|
|
705
|
+
supportedPositions: [{ asset: usdceToken, isDebt: false }],
|
|
706
|
+
fromAddress: vaultSettings.vaultAllocator,
|
|
707
|
+
toAddress: ContractAddr.from(vaultSettings.walletAddress),
|
|
2163
708
|
});
|
|
2164
709
|
|
|
2165
710
|
vaultSettings.adapters.push({
|
|
@@ -2168,8 +713,18 @@ function getLooperSettings(
|
|
|
2168
713
|
});
|
|
2169
714
|
|
|
2170
715
|
vaultSettings.adapters.push({
|
|
2171
|
-
id: `${
|
|
2172
|
-
adapter:
|
|
716
|
+
id: `${vesuModifyPositionAdapter.name}_${wbtcToken.symbol}_${usdcToken.symbol}`,
|
|
717
|
+
adapter: vesuModifyPositionAdapter,
|
|
718
|
+
});
|
|
719
|
+
|
|
720
|
+
vaultSettings.adapters.push({
|
|
721
|
+
id: `${usdceTransferAdapter.name}_${usdceToken.symbol}`,
|
|
722
|
+
adapter: usdceTransferAdapter,
|
|
723
|
+
});
|
|
724
|
+
|
|
725
|
+
vaultSettings.adapters.push({
|
|
726
|
+
id: `${usdcToUsdceAdapter.name}_${usdceToken.symbol}_${usdcToken.symbol}`,
|
|
727
|
+
adapter: usdcToUsdceAdapter,
|
|
2173
728
|
});
|
|
2174
729
|
|
|
2175
730
|
vaultSettings.adapters.push({
|
|
@@ -2192,26 +747,29 @@ function getLooperSettings(
|
|
|
2192
747
|
|
|
2193
748
|
vaultSettings.leafAdapters.push(() => vesuMultiplyAdapter.getDepositLeaf());
|
|
2194
749
|
vaultSettings.leafAdapters.push(() => vesuMultiplyAdapter.getWithdrawLeaf());
|
|
750
|
+
vaultSettings.leafAdapters.push(() => vesuModifyPositionAdapter.getDepositLeaf());
|
|
751
|
+
vaultSettings.leafAdapters.push(() => vesuModifyPositionAdapter.getWithdrawLeaf());
|
|
2195
752
|
vaultSettings.leafAdapters.push(() => extendedAdapter.getDepositLeaf());
|
|
2196
|
-
vaultSettings.leafAdapters.push(() =>
|
|
2197
|
-
|
|
2198
|
-
);
|
|
753
|
+
vaultSettings.leafAdapters.push(() => usdcToUsdceAdapter.getDepositLeaf());
|
|
754
|
+
vaultSettings.leafAdapters.push(() => usdcToUsdceAdapter.getWithdrawLeaf());
|
|
2199
755
|
vaultSettings.leafAdapters.push(() => avnuAdapter.getDepositLeaf());
|
|
2200
756
|
vaultSettings.leafAdapters.push(() => avnuAdapter.getWithdrawLeaf());
|
|
757
|
+
vaultSettings.leafAdapters.push(() => usdceTransferAdapter.getDepositLeaf());
|
|
758
|
+
vaultSettings.leafAdapters.push(() => usdceTransferAdapter.getWithdrawLeaf());
|
|
2201
759
|
vaultSettings.leafAdapters.push(
|
|
2202
760
|
commonAdapter
|
|
2203
761
|
.getApproveAdapter(
|
|
2204
762
|
usdcToken.address,
|
|
2205
763
|
vaultSettings.vaultAddress,
|
|
2206
|
-
UNIVERSAL_MANAGE_IDS.APPROVE_BRING_LIQUIDITY
|
|
764
|
+
UNIVERSAL_MANAGE_IDS.APPROVE_BRING_LIQUIDITY,
|
|
2207
765
|
)
|
|
2208
|
-
.bind(commonAdapter)
|
|
766
|
+
.bind(commonAdapter),
|
|
2209
767
|
);
|
|
2210
768
|
|
|
2211
769
|
vaultSettings.leafAdapters.push(
|
|
2212
770
|
commonAdapter
|
|
2213
771
|
.getBringLiquidityAdapter(UNIVERSAL_MANAGE_IDS.BRING_LIQUIDITY)
|
|
2214
|
-
.bind(commonAdapter)
|
|
772
|
+
.bind(commonAdapter),
|
|
2215
773
|
);
|
|
2216
774
|
return vaultSettings;
|
|
2217
775
|
}
|
|
@@ -2222,7 +780,7 @@ function getDescription(tokenSymbol: string, underlyingSymbol: string) {
|
|
|
2222
780
|
|
|
2223
781
|
export default function VaultDescription(
|
|
2224
782
|
lstSymbol: string,
|
|
2225
|
-
underlyingSymbol: string
|
|
783
|
+
underlyingSymbol: string,
|
|
2226
784
|
) {
|
|
2227
785
|
const containerStyle = {
|
|
2228
786
|
maxWidth: "800px",
|
|
@@ -2306,36 +864,37 @@ export default function VaultDescription(
|
|
|
2306
864
|
|
|
2307
865
|
const re7UsdcPrimeDevansh: VesuExtendedStrategySettings = {
|
|
2308
866
|
vaultAddress: ContractAddr.from(
|
|
2309
|
-
"
|
|
867
|
+
"0x772d6cf5038c18ff5ab89f8945017bbf4d2c6959891339975c70a4f74ac6c8e",
|
|
2310
868
|
),
|
|
2311
869
|
manager: ContractAddr.from(
|
|
2312
|
-
"
|
|
870
|
+
"0x3340c9d7231838e2dccff72b9004f1598a74e65c74b954f07fe1ea19d04a625",
|
|
2313
871
|
),
|
|
2314
872
|
vaultAllocator: ContractAddr.from(
|
|
2315
|
-
"
|
|
873
|
+
"0x537353b35eee5ca2d9a45eb646977baddd4e89ce870a231dcada79884117292",
|
|
2316
874
|
),
|
|
2317
875
|
redeemRequestNFT: ContractAddr.from(
|
|
2318
|
-
"
|
|
876
|
+
"0x6117d1a8c72c0457948083757e1a17ee8c0833b969d5c959b629e5f8feb56ec",
|
|
2319
877
|
),
|
|
2320
878
|
aumOracle: ContractAddr.from(
|
|
2321
|
-
"
|
|
879
|
+
"0x6d7d68045bf5e0b5a4cec43241549851cb9645f7a73a20894152165dbe7083a",
|
|
2322
880
|
),
|
|
2323
881
|
leafAdapters: [],
|
|
2324
882
|
adapters: [],
|
|
2325
883
|
targetHealthFactor: 1.4,
|
|
2326
884
|
minHealthFactor: 1.05,
|
|
2327
885
|
underlyingToken: Global.getDefaultTokens().find(
|
|
2328
|
-
(token) => token.symbol === "USDC"
|
|
886
|
+
(token) => token.symbol === "USDC",
|
|
2329
887
|
)!,
|
|
2330
888
|
quoteAmountToFetchPrice: new Web3Number(
|
|
2331
889
|
"0.001",
|
|
2332
|
-
Global.getDefaultTokens().find((token) => token.symbol === "WBTC")
|
|
890
|
+
Global.getDefaultTokens().find((token) => token.symbol === "WBTC")!
|
|
891
|
+
.decimals,
|
|
2333
892
|
),
|
|
2334
893
|
borrowable_assets: [
|
|
2335
|
-
Global.getDefaultTokens().find((token) => token.symbol === "
|
|
894
|
+
Global.getDefaultTokens().find((token) => token.symbol === "USDC")!,
|
|
2336
895
|
],
|
|
2337
896
|
minimumWBTCDifferenceForAvnuSwap: MINIMUM_WBTC_DIFFERENCE_FOR_AVNU_SWAP,
|
|
2338
|
-
walletAddress:
|
|
897
|
+
walletAddress: '0x024b563C1C7d41B32BF4EFB9F38828508a65Be2d6e25268E9f63F22C5e9E51c5',
|
|
2339
898
|
};
|
|
2340
899
|
|
|
2341
900
|
export const VesuExtendedTestStrategies = (
|
|
@@ -2346,7 +905,7 @@ export const VesuExtendedTestStrategies = (
|
|
|
2346
905
|
minimumVesuMovementAmount: number,
|
|
2347
906
|
minimumExtendedRetriesDelayForOrderStatus: number,
|
|
2348
907
|
minimumExtendedPriceDifferenceForSwapOpen: number,
|
|
2349
|
-
maximumExtendedPriceDifferenceForSwapClosing: number
|
|
908
|
+
maximumExtendedPriceDifferenceForSwapClosing: number,
|
|
2350
909
|
): IStrategyMetadata<VesuExtendedStrategySettings>[] => {
|
|
2351
910
|
return [
|
|
2352
911
|
getStrategySettingsVesuExtended(
|
|
@@ -2362,11 +921,15 @@ export const VesuExtendedTestStrategies = (
|
|
|
2362
921
|
minimumVesuMovementAmount,
|
|
2363
922
|
minimumExtendedRetriesDelayForOrderStatus,
|
|
2364
923
|
minimumExtendedPriceDifferenceForSwapOpen,
|
|
2365
|
-
maximumExtendedPriceDifferenceForSwapClosing
|
|
924
|
+
maximumExtendedPriceDifferenceForSwapClosing,
|
|
2366
925
|
),
|
|
2367
926
|
];
|
|
2368
927
|
};
|
|
2369
928
|
|
|
929
|
+
/**
|
|
930
|
+
* Constructs a complete IStrategyMetadata object for a Vesu-Extended strategy,
|
|
931
|
+
* including adapter wiring, risk configuration, FAQ, and UI description.
|
|
932
|
+
*/
|
|
2370
933
|
function getStrategySettingsVesuExtended(
|
|
2371
934
|
lstSymbol: string,
|
|
2372
935
|
underlyingSymbol: string,
|
|
@@ -2380,17 +943,22 @@ function getStrategySettingsVesuExtended(
|
|
|
2380
943
|
minimumVesuMovementAmount: number,
|
|
2381
944
|
minimumExtendedRetriesDelayForOrderStatus: number,
|
|
2382
945
|
minimumExtendedPriceDifferenceForSwapOpen: number,
|
|
2383
|
-
maximumExtendedPriceDifferenceForSwapClosing: number
|
|
946
|
+
maximumExtendedPriceDifferenceForSwapClosing: number,
|
|
2384
947
|
): IStrategyMetadata<VesuExtendedStrategySettings> {
|
|
2385
948
|
return {
|
|
949
|
+
id: `extended_${underlyingSymbol.toLowerCase()}_test`,
|
|
2386
950
|
name: `Extended Test ${underlyingSymbol}`,
|
|
2387
951
|
description: getDescription(lstSymbol, underlyingSymbol),
|
|
2388
952
|
address: addresses.vaultAddress,
|
|
2389
953
|
launchBlock: 0,
|
|
2390
954
|
type: "Other",
|
|
955
|
+
vaultType: {
|
|
956
|
+
type: VaultType.DELTA_NEUTRAL,
|
|
957
|
+
description: "Delta Neutral strategy using extended position on Vesu"
|
|
958
|
+
},
|
|
2391
959
|
depositTokens: [
|
|
2392
960
|
Global.getDefaultTokens().find(
|
|
2393
|
-
(token) => token.symbol ===
|
|
961
|
+
(token) => token.symbol === 'WBTC',
|
|
2394
962
|
)!,
|
|
2395
963
|
],
|
|
2396
964
|
additionalInfo: getLooperSettings(
|
|
@@ -2405,7 +973,7 @@ function getStrategySettingsVesuExtended(
|
|
|
2405
973
|
minimumVesuMovementAmount,
|
|
2406
974
|
minimumExtendedRetriesDelayForOrderStatus,
|
|
2407
975
|
minimumExtendedPriceDifferenceForSwapOpen,
|
|
2408
|
-
maximumExtendedPriceDifferenceForSwapClosing
|
|
976
|
+
maximumExtendedPriceDifferenceForSwapClosing,
|
|
2409
977
|
),
|
|
2410
978
|
risk: {
|
|
2411
979
|
riskFactor: _riskFactor,
|
|
@@ -2416,7 +984,6 @@ function getStrategySettingsVesuExtended(
|
|
|
2416
984
|
},
|
|
2417
985
|
auditUrl: AUDIT_URL,
|
|
2418
986
|
protocols: [Protocols.ENDUR, Protocols.VESU],
|
|
2419
|
-
maxTVL: Web3Number.fromWei(0, 18),
|
|
2420
987
|
contractDetails: getContractDetails(addresses),
|
|
2421
988
|
faqs: getFAQs(lstSymbol, underlyingSymbol, isLST),
|
|
2422
989
|
investmentSteps: getInvestmentSteps(lstSymbol, underlyingSymbol),
|
|
@@ -2424,5 +991,27 @@ function getStrategySettingsVesuExtended(
|
|
|
2424
991
|
apyMethodology: isLST
|
|
2425
992
|
? "Current annualized APY in terms of base asset of the LST. There is no additional fee taken by Troves on LST APY. We charge a 10% performance fee on the additional gain which is already accounted in the APY shown."
|
|
2426
993
|
: "Current annualized APY in terms of base asset of the Yield Token. There is no additional fee taken by Troves on yield token APY. We charge a 10% performance fee on the additional gain which is already accounted in the APY shown.",
|
|
994
|
+
security: {
|
|
995
|
+
auditStatus: AuditStatus.NOT_AUDITED,
|
|
996
|
+
sourceCode: {
|
|
997
|
+
type: SourceCodeType.OPEN_SOURCE,
|
|
998
|
+
contractLink: AUDIT_URL,
|
|
999
|
+
},
|
|
1000
|
+
accessControl: {
|
|
1001
|
+
type: AccessControlType.MULTISIG_ACCOUNT,
|
|
1002
|
+
addresses: [addresses.vaultAddress],
|
|
1003
|
+
timeLock: "None",
|
|
1004
|
+
},
|
|
1005
|
+
},
|
|
1006
|
+
redemptionInfo: {
|
|
1007
|
+
instantWithdrawalVault: InstantWithdrawalVault.NO,
|
|
1008
|
+
redemptionsInfo: [{
|
|
1009
|
+
title: "Up to $500k",
|
|
1010
|
+
description: "1-24 hours",
|
|
1011
|
+
}],
|
|
1012
|
+
alerts: [],
|
|
1013
|
+
},
|
|
1014
|
+
usualTimeToEarnings: null,
|
|
1015
|
+
usualTimeToEarningsDescription: null,
|
|
2427
1016
|
};
|
|
2428
1017
|
}
|