@haven-fi/solauto-sdk 1.0.58 → 1.0.60

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,5 +1,5 @@
1
1
  import { PublicKey } from "@solana/web3.js";
2
- import { isOption, isSome, Umi } from "@metaplex-foundation/umi";
2
+ import { isOption, isSome, publicKey, Umi } from "@metaplex-foundation/umi";
3
3
  import {
4
4
  AutomationSettings,
5
5
  DCASettings,
@@ -53,9 +53,10 @@ export function nextAutomationPeriodTimestamp(
53
53
  }
54
54
 
55
55
  export function eligibleForNextAutomationPeriod(
56
- automation: AutomationSettings
56
+ automation: AutomationSettings,
57
+ currentUnixTime: number
57
58
  ): boolean {
58
- return currentUnixSeconds() >= nextAutomationPeriodTimestamp(automation);
59
+ return currentUnixTime >= nextAutomationPeriodTimestamp(automation);
59
60
  }
60
61
 
61
62
  export function getUpdatedValueFromAutomation(
@@ -78,16 +79,16 @@ export function getUpdatedValueFromAutomation(
78
79
 
79
80
  export function getAdjustedSettingsFromAutomation(
80
81
  settings: SolautoSettingsParameters,
81
- currentUnixSeconds: number
82
+ currentUnixTime: number
82
83
  ): SolautoSettingsParameters {
83
84
  const boostToBps =
84
85
  settings.automation.targetPeriods > 0 &&
85
- eligibleForNextAutomationPeriod(settings.automation)
86
+ eligibleForNextAutomationPeriod(settings.automation, currentUnixTime)
86
87
  ? getUpdatedValueFromAutomation(
87
88
  settings.boostToBps,
88
89
  settings.targetBoostToBps,
89
90
  settings.automation,
90
- currentUnixSeconds
91
+ currentUnixTime
91
92
  )
92
93
  : settings.boostToBps;
93
94
 
@@ -121,11 +122,12 @@ export function getSolautoFeesBps(
121
122
  export function eligibleForRebalance(
122
123
  positionState: PositionState,
123
124
  positionSettings: SolautoSettingsParameters,
124
- positionDca: DCASettings
125
+ positionDca: DCASettings,
126
+ currentUnixSecs: number
125
127
  ): RebalanceAction | undefined {
126
128
  if (
127
129
  positionDca.automation.targetPeriods > 0 &&
128
- eligibleForNextAutomationPeriod(positionDca.automation)
130
+ eligibleForNextAutomationPeriod(positionDca.automation, currentUnixSecs)
129
131
  ) {
130
132
  return "dca";
131
133
  }
@@ -135,13 +137,13 @@ export function eligibleForRebalance(
135
137
  }
136
138
 
137
139
  const boostToBps =
138
- eligibleForRefresh(positionState, positionSettings) &&
140
+ eligibleForRefresh(positionState, positionSettings, currentUnixSecs) &&
139
141
  positionSettings.automation.targetPeriods > 0
140
142
  ? getUpdatedValueFromAutomation(
141
143
  positionSettings.boostToBps,
142
144
  positionSettings.targetBoostToBps,
143
145
  positionSettings.automation,
144
- currentUnixSeconds()
146
+ currentUnixSecs
145
147
  )
146
148
  : positionSettings.boostToBps;
147
149
  const repayFrom = positionSettings.repayToBps + positionSettings.repayGap;
@@ -158,10 +160,14 @@ export function eligibleForRebalance(
158
160
 
159
161
  export function eligibleForRefresh(
160
162
  positionState: PositionState,
161
- positionSettings: SolautoSettingsParameters
163
+ positionSettings: SolautoSettingsParameters,
164
+ currentUnixTime: number
162
165
  ): boolean {
163
166
  if (positionSettings.automation.targetPeriods > 0) {
164
- return eligibleForNextAutomationPeriod(positionSettings.automation);
167
+ return eligibleForNextAutomationPeriod(
168
+ positionSettings.automation,
169
+ currentUnixTime
170
+ );
165
171
  } else {
166
172
  return (
167
173
  currentUnixSeconds() - Number(positionState.lastUpdated) >
@@ -319,15 +325,32 @@ export async function getAllPositionsByAuthority(
319
325
  return allPositions;
320
326
  }
321
327
 
322
- export async function positionStateWithPrices(
323
- umi: Umi,
324
- state: PositionState,
325
- protocolAccount: PublicKey,
326
- lendingPlatform: LendingPlatform,
327
- supplyPrice?: number,
328
- debtPrice?: number
329
- ): Promise<PositionState | undefined> {
328
+ interface GetLatestStateProps {
329
+ state: PositionState;
330
+ umi?: Umi;
331
+ protocolAccount?: PublicKey;
332
+ lendingPlatform?: LendingPlatform;
333
+ supplyPrice?: number;
334
+ debtPrice?: number;
335
+ }
336
+
337
+ export async function positionStateWithPrices({
338
+ state,
339
+ supplyPrice,
340
+ debtPrice,
341
+ umi,
342
+ protocolAccount,
343
+ lendingPlatform,
344
+ }: GetLatestStateProps): Promise<PositionState | undefined> {
330
345
  if (currentUnixSeconds() - Number(state.lastUpdated) > 60 * 60 * 24 * 7) {
346
+ if (
347
+ umi === undefined ||
348
+ protocolAccount === undefined ||
349
+ lendingPlatform === undefined
350
+ ) {
351
+ throw new Error("Missing required parameters");
352
+ }
353
+
331
354
  if (lendingPlatform === LendingPlatform.Marginfi) {
332
355
  return await getMarginfiAccountPositionState(
333
356
  umi,
@@ -361,7 +384,10 @@ export async function positionStateWithPrices(
361
384
  state.liqThresholdBps
362
385
  ),
363
386
  netWorth: {
364
- ...state.netWorth,
387
+ baseUnit: toBaseUnit(
388
+ (supplyUsd - debtUsd) / supplyPrice,
389
+ state.supply.decimals
390
+ ),
365
391
  baseAmountUsdValue: toBaseUnit(supplyUsd - debtUsd, USD_DECIMALS),
366
392
  },
367
393
  supply: {
@@ -381,6 +407,82 @@ export async function positionStateWithPrices(
381
407
  };
382
408
  }
383
409
 
410
+ interface AssetProps {
411
+ amountUsedBaseUnit: bigint;
412
+ decimals: number;
413
+ price: number;
414
+ mint: PublicKey;
415
+ }
416
+
417
+ export function createFakePositionState(
418
+ supply: AssetProps,
419
+ debt: AssetProps,
420
+ maxLtvBps: number,
421
+ liqThresholdBps: number
422
+ ): PositionState {
423
+ const supplyUsd =
424
+ fromBaseUnit(supply.amountUsedBaseUnit, supply.decimals) * supply.price;
425
+ const debtUsd =
426
+ fromBaseUnit(debt.amountUsedBaseUnit, debt.decimals) * debt.price;
427
+
428
+ return {
429
+ liqUtilizationRateBps: getLiqUtilzationRateBps(
430
+ supplyUsd,
431
+ debtUsd,
432
+ liqThresholdBps
433
+ ),
434
+ supply: {
435
+ amountUsed: {
436
+ baseUnit: supply.amountUsedBaseUnit,
437
+ baseAmountUsdValue: toBaseUnit(supplyUsd, USD_DECIMALS),
438
+ },
439
+ amountCanBeUsed: {
440
+ baseUnit: toBaseUnit(1000000, supply.decimals),
441
+ baseAmountUsdValue: BigInt(Math.round(1000000 * supply.price)),
442
+ },
443
+ baseAmountMarketPriceUsd: toBaseUnit(supply.price, USD_DECIMALS),
444
+ borrowFeeBps: 0,
445
+ decimals: supply.decimals,
446
+ flashLoanFeeBps: 0,
447
+ mint: publicKey(supply.mint),
448
+ padding1: [],
449
+ padding2: [],
450
+ padding: new Uint8Array([]),
451
+ },
452
+ debt: {
453
+ amountUsed: {
454
+ baseUnit: debt.amountUsedBaseUnit,
455
+ baseAmountUsdValue: toBaseUnit(debtUsd, USD_DECIMALS),
456
+ },
457
+ amountCanBeUsed: {
458
+ baseUnit: toBaseUnit(1000000, debt.decimals),
459
+ baseAmountUsdValue: BigInt(Math.round(1000000 * debt.price)),
460
+ },
461
+ baseAmountMarketPriceUsd: toBaseUnit(debt.price, USD_DECIMALS),
462
+ borrowFeeBps: 0,
463
+ decimals: debt.decimals,
464
+ flashLoanFeeBps: 0,
465
+ mint: publicKey(debt.mint),
466
+ padding1: [],
467
+ padding2: [],
468
+ padding: new Uint8Array([]),
469
+ },
470
+ netWorth: {
471
+ baseUnit: toBaseUnit(
472
+ (supplyUsd - debtUsd) / supply.price,
473
+ supply.decimals
474
+ ),
475
+ baseAmountUsdValue: toBaseUnit(supplyUsd - debtUsd, USD_DECIMALS),
476
+ },
477
+ maxLtvBps,
478
+ liqThresholdBps,
479
+ lastUpdated: BigInt(currentUnixSeconds()),
480
+ padding1: [],
481
+ padding2: [],
482
+ padding: [],
483
+ };
484
+ }
485
+
384
486
  type PositionAdjustment =
385
487
  | { type: "supply"; value: bigint }
386
488
  | { type: "debt"; value: bigint }
@@ -1,6 +1,12 @@
1
1
  import { PublicKey } from "@solana/web3.js";
2
2
  import { SolautoClient } from "../../clients/solautoClient";
3
- import { FeeType, PositionTokenUsage } from "../../generated";
3
+ import {
4
+ DCASettings,
5
+ FeeType,
6
+ PositionState,
7
+ PositionTokenUsage,
8
+ SolautoSettingsParameters,
9
+ } from "../../generated";
4
10
  import {
5
11
  eligibleForNextAutomationPeriod,
6
12
  getAdjustedSettingsFromAutomation,
@@ -26,15 +32,12 @@ import {
26
32
  PRICES,
27
33
  } from "../../constants/solautoConstants";
28
34
 
29
- function getAdditionalAmountToDcaIn(client: SolautoClient): number {
30
- const dca = client.solautoPositionActiveDca()!;
35
+ function getAdditionalAmountToDcaIn(dca: DCASettings): number {
31
36
  if (dca.debtToAddBaseUnit === BigInt(0)) {
32
37
  return 0;
33
38
  }
34
39
 
35
- const debtBalance =
36
- Number(client.solautoPositionData?.position.dca.debtToAddBaseUnit ?? 0) +
37
- Number(client.livePositionUpdates.debtTaBalanceAdjustment ?? 0);
40
+ const debtBalance = Number(dca.debtToAddBaseUnit);
38
41
  const updatedDebtBalance = getUpdatedValueFromAutomation(
39
42
  debtBalance,
40
43
  0,
@@ -45,44 +48,44 @@ function getAdditionalAmountToDcaIn(client: SolautoClient): number {
45
48
  return debtBalance - updatedDebtBalance;
46
49
  }
47
50
 
48
- function getStandardTargetLiqUtilizationRateBps(client: SolautoClient): number {
49
- if (!client.selfManaged) {
50
- const adjustedSettings = getAdjustedSettingsFromAutomation(
51
- client.solautoPositionSettings()!,
52
- currentUnixSeconds()
53
- );
51
+ function getStandardTargetLiqUtilizationRateBps(
52
+ state: PositionState,
53
+ settings: SolautoSettingsParameters
54
+ ): number {
55
+ const adjustedSettings = getAdjustedSettingsFromAutomation(
56
+ settings,
57
+ currentUnixSeconds()
58
+ );
59
+
60
+ const repayFrom = adjustedSettings.repayToBps - adjustedSettings.repayGap;
61
+ const boostFrom = adjustedSettings.boostToBps + adjustedSettings.boostGap;
54
62
 
55
- const repayFrom = adjustedSettings.repayToBps - adjustedSettings.repayGap;
56
- const boostFrom = adjustedSettings.boostToBps + adjustedSettings.boostGap;
57
-
58
- if (client.solautoPositionState!.liqUtilizationRateBps < boostFrom) {
59
- return adjustedSettings.boostToBps;
60
- } else if (
61
- client.solautoPositionState!.liqUtilizationRateBps > repayFrom ||
62
- repayFrom - client.solautoPositionState!.liqUtilizationRateBps <
63
- repayFrom * 0.015
64
- ) {
65
- return adjustedSettings.repayToBps;
66
- } else {
67
- throw new Error("Invalid rebalance condition");
68
- }
63
+ if (state.liqUtilizationRateBps < boostFrom) {
64
+ return adjustedSettings.boostToBps;
65
+ } else if (
66
+ state.liqUtilizationRateBps > repayFrom ||
67
+ repayFrom - state.liqUtilizationRateBps < repayFrom * 0.015
68
+ ) {
69
+ return adjustedSettings.repayToBps;
69
70
  } else {
70
- throw new Error(
71
- "This is a self-managed position, a targetLiqUtilizationRateBps must be provided initiate a rebalance"
72
- );
71
+ throw new Error("Invalid rebalance condition");
73
72
  }
74
73
  }
75
74
 
76
- function targetLiqUtilizationRateBpsFromDCA(client: SolautoClient) {
75
+ function targetLiqUtilizationRateBpsFromDCA(
76
+ state: PositionState,
77
+ settings: SolautoSettingsParameters,
78
+ dca: DCASettings
79
+ ) {
77
80
  const adjustedSettings = getAdjustedSettingsFromAutomation(
78
- client.solautoPositionSettings()!,
81
+ settings,
79
82
  currentUnixSeconds()
80
83
  );
81
84
 
82
85
  let targetRateBps = 0;
83
- if (client.solautoPositionActiveDca()!.debtToAddBaseUnit > BigInt(0)) {
86
+ if (dca.debtToAddBaseUnit > BigInt(0)) {
84
87
  targetRateBps = Math.max(
85
- client.solautoPositionState!.liqUtilizationRateBps,
88
+ state.liqUtilizationRateBps,
86
89
  adjustedSettings.boostToBps
87
90
  );
88
91
  } else {
@@ -91,32 +94,29 @@ function targetLiqUtilizationRateBpsFromDCA(client: SolautoClient) {
91
94
  return targetRateBps;
92
95
  }
93
96
 
94
- function isDcaRebalance(client: SolautoClient): boolean {
95
- if (client.solautoPositionActiveDca() === undefined || client.selfManaged) {
97
+ function isDcaRebalance(
98
+ state: PositionState,
99
+ settings: SolautoSettingsParameters,
100
+ dca: DCASettings | undefined,
101
+ currentUnixTime: number
102
+ ): boolean {
103
+ if (dca === undefined || dca.automation.targetPeriods === 0) {
96
104
  return false;
97
105
  }
98
106
 
99
107
  const adjustedSettings = getAdjustedSettingsFromAutomation(
100
- client.solautoPositionSettings()!,
108
+ settings,
101
109
  currentUnixSeconds()
102
110
  );
103
111
 
104
112
  if (
105
- client.solautoPositionState!.liqUtilizationRateBps >
113
+ state.liqUtilizationRateBps >
106
114
  adjustedSettings.repayToBps + adjustedSettings.repayGap
107
115
  ) {
108
116
  return false;
109
117
  }
110
118
 
111
- if (client.solautoPositionActiveDca()!.automation.targetPeriods === 0) {
112
- return false;
113
- }
114
-
115
- if (
116
- !eligibleForNextAutomationPeriod(
117
- client.solautoPositionActiveDca()!.automation
118
- )
119
- ) {
119
+ if (!eligibleForNextAutomationPeriod(dca.automation, currentUnixTime)) {
120
120
  return false;
121
121
  }
122
122
 
@@ -124,7 +124,10 @@ function isDcaRebalance(client: SolautoClient): boolean {
124
124
  }
125
125
 
126
126
  function getTargetRateAndDcaAmount(
127
- client: SolautoClient,
127
+ state: PositionState,
128
+ settings: SolautoSettingsParameters | undefined,
129
+ dca: DCASettings | undefined,
130
+ currentUnixTime: number,
128
131
  targetLiqUtilizationRateBps?: number
129
132
  ): { targetRateBps: number; amountToDcaIn?: number } {
130
133
  if (targetLiqUtilizationRateBps !== undefined) {
@@ -133,10 +136,19 @@ function getTargetRateAndDcaAmount(
133
136
  };
134
137
  }
135
138
 
136
- if (isDcaRebalance(client)) {
137
- const amountToDcaIn = getAdditionalAmountToDcaIn(client);
138
- const targetLiqUtilizationRateBps =
139
- targetLiqUtilizationRateBpsFromDCA(client);
139
+ if (settings === undefined) {
140
+ throw new Error(
141
+ "If rebalancing a self-managed position, settings, and DCA should be provided"
142
+ );
143
+ }
144
+
145
+ if (isDcaRebalance(state, settings, dca, currentUnixTime)) {
146
+ const amountToDcaIn = getAdditionalAmountToDcaIn(dca!);
147
+ const targetLiqUtilizationRateBps = targetLiqUtilizationRateBpsFromDCA(
148
+ state,
149
+ settings,
150
+ dca!
151
+ );
140
152
 
141
153
  return {
142
154
  targetRateBps: targetLiqUtilizationRateBps,
@@ -144,7 +156,7 @@ function getTargetRateAndDcaAmount(
144
156
  };
145
157
  } else {
146
158
  return {
147
- targetRateBps: getStandardTargetLiqUtilizationRateBps(client),
159
+ targetRateBps: getStandardTargetLiqUtilizationRateBps(state, settings),
148
160
  };
149
161
  }
150
162
  }
@@ -152,17 +164,24 @@ function getTargetRateAndDcaAmount(
152
164
  export interface RebalanceValues {
153
165
  increasingLeverage: boolean;
154
166
  debtAdjustmentUsd: number;
167
+ amountToDcaIn: number;
155
168
  amountUsdToDcaIn: number;
156
169
  }
157
170
 
158
171
  export function getRebalanceValues(
159
- client: SolautoClient,
172
+ state: PositionState,
173
+ settings: SolautoSettingsParameters | undefined,
174
+ dca: DCASettings | undefined,
175
+ feeType: FeeType,
176
+ currentUnixTime: number,
177
+ supplyPrice: number,
178
+ debtPrice: number,
160
179
  targetLiqUtilizationRateBps?: number,
161
180
  limitGapBps?: number
162
181
  ): RebalanceValues {
163
182
  if (
164
- client.solautoPositionState === undefined ||
165
- client.solautoPositionState.lastUpdated <
183
+ state === undefined ||
184
+ state.lastUpdated <
166
185
  BigInt(
167
186
  Math.round(currentUnixSeconds() - MIN_POSITION_STATE_FRESHNESS_SECS)
168
187
  )
@@ -171,50 +190,41 @@ export function getRebalanceValues(
171
190
  }
172
191
 
173
192
  const { targetRateBps, amountToDcaIn } = getTargetRateAndDcaAmount(
174
- client,
193
+ state,
194
+ settings,
195
+ dca,
196
+ currentUnixTime,
175
197
  targetLiqUtilizationRateBps
176
198
  );
177
199
 
178
200
  const amountUsdToDcaIn =
179
- fromBaseUnit(
180
- BigInt(Math.round(amountToDcaIn ?? 0)),
181
- client.solautoPositionState!.debt.decimals
182
- ) * PRICES[client.debtMint.toString()].price;
201
+ fromBaseUnit(BigInt(Math.round(amountToDcaIn ?? 0)), state.debt.decimals) *
202
+ debtPrice;
183
203
 
184
204
  const increasingLeverage =
185
- amountUsdToDcaIn > 0 ||
186
- client.solautoPositionState!.liqUtilizationRateBps < targetRateBps;
205
+ amountUsdToDcaIn > 0 || state.liqUtilizationRateBps < targetRateBps;
187
206
  let adjustmentFeeBps = 0;
188
207
  if (increasingLeverage) {
189
- adjustmentFeeBps = getSolautoFeesBps(
190
- client.referredByState !== undefined,
191
- client.solautoPositionData?.feeType ?? FeeType.Small
192
- ).total;
208
+ adjustmentFeeBps = getSolautoFeesBps(false, feeType).total;
193
209
  }
194
210
 
195
211
  const supplyUsd =
196
- fromBaseUnit(
197
- client.solautoPositionState!.supply.amountUsed.baseAmountUsdValue,
198
- USD_DECIMALS
199
- ) + amountUsdToDcaIn;
212
+ fromBaseUnit(state.supply.amountUsed.baseAmountUsdValue, USD_DECIMALS) +
213
+ amountUsdToDcaIn;
200
214
  const debtUsd = fromBaseUnit(
201
- client.solautoPositionState!.debt.amountUsed.baseAmountUsdValue,
215
+ state.debt.amountUsed.baseAmountUsdValue,
202
216
  USD_DECIMALS
203
217
  );
204
218
  let debtAdjustmentUsd = getDebtAdjustmentUsd(
205
- client.solautoPositionState!.liqThresholdBps,
219
+ state.liqThresholdBps,
206
220
  supplyUsd,
207
221
  debtUsd,
208
222
  targetRateBps,
209
223
  adjustmentFeeBps
210
224
  );
211
225
 
212
- const input = increasingLeverage
213
- ? client.solautoPositionState!.debt
214
- : client.solautoPositionState!.supply;
215
- const inputMarketPrice = increasingLeverage
216
- ? PRICES[client.debtMint.toString()].price
217
- : PRICES[client.supplyMint.toString()].price;
226
+ const input = increasingLeverage ? state.debt : state.supply;
227
+ const inputMarketPrice = increasingLeverage ? debtPrice : supplyPrice;
218
228
 
219
229
  const limitGap = limitGapBps
220
230
  ? fromBps(limitGapBps)
@@ -235,6 +245,7 @@ export function getRebalanceValues(
235
245
  return {
236
246
  increasingLeverage,
237
247
  debtAdjustmentUsd,
248
+ amountToDcaIn: amountToDcaIn ?? 0,
238
249
  amountUsdToDcaIn,
239
250
  };
240
251
  }
@@ -269,7 +280,7 @@ export function getFlashLoanDetails(
269
280
  values.debtAdjustmentUsd > 0
270
281
  ? debtUsd + debtAdjustmentWithSlippage
271
282
  : debtUsd;
272
-
283
+
273
284
  const tempLiqUtilizationRateBps = getLiqUtilzationRateBps(
274
285
  supplyUsd,
275
286
  debtUsd,
@@ -342,7 +353,7 @@ export function getJupSwapRebalanceDetails(
342
353
  inputMint: toWeb3JsPublicKey(input.mint),
343
354
  outputMint: toWeb3JsPublicKey(output.mint),
344
355
  destinationWallet: client.solautoPosition,
345
- slippageBpsIncFactor: 0.25 + ((attemptNum ?? 0) * 0.2),
356
+ slippageBpsIncFactor: 0.25 + (attemptNum ?? 0) * 0.2,
346
357
  amount: rebalancingToZero
347
358
  ? client.solautoPositionState!.debt.amountUsed.baseUnit +
348
359
  BigInt(
package/tests/shared.ts CHANGED
@@ -1,14 +1,17 @@
1
1
  import { Signer, createSignerFromKeypair } from "@metaplex-foundation/umi";
2
- import { Connection, clusterApiUrl } from "@solana/web3.js";
2
+ import { Connection, Keypair, clusterApiUrl } from "@solana/web3.js";
3
3
  import { createUmi } from "@metaplex-foundation/umi-bundle-defaults";
4
4
  import { getSecretKey } from "../local/shared";
5
+ import { fromWeb3JsKeypair } from "@metaplex-foundation/umi-web3js-adapters";
5
6
 
6
- export function setupTest(keypairFilename?: string): Signer {
7
+ export function setupTest(keypairFilename?: string, random?: boolean): Signer {
7
8
  const umi = createUmi(
8
9
  new Connection(clusterApiUrl("mainnet-beta"), "confirmed")
9
10
  );
10
11
  const secretKey = getSecretKey(keypairFilename);
11
- const signerKeypair = umi.eddsa.createKeypairFromSecretKey(secretKey);
12
+ const signerKeypair = random
13
+ ? fromWeb3JsKeypair(Keypair.generate())
14
+ : umi.eddsa.createKeypairFromSecretKey(secretKey);
12
15
  const signer = createSignerFromKeypair(umi, signerKeypair);
13
16
 
14
17
  return signer;