@haven-fi/solauto-sdk 1.0.661 → 1.0.663
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/services/flashLoans/flProviderAggregator.d.ts.map +1 -1
- package/dist/services/flashLoans/marginfiFlProvider.d.ts +2 -0
- package/dist/services/flashLoans/marginfiFlProvider.d.ts.map +1 -1
- package/dist/services/flashLoans/marginfiFlProvider.js +14 -31
- package/dist/services/rebalance/rebalanceTxBuilder.d.ts +0 -1
- package/dist/services/rebalance/rebalanceTxBuilder.d.ts.map +1 -1
- package/dist/services/rebalance/rebalanceTxBuilder.js +1 -6
- package/dist/services/solauto/solautoClient.d.ts.map +1 -1
- package/dist/services/solauto/solautoClient.js +1 -4
- package/dist/services/solauto/solautoMarginfiClient.d.ts +2 -1
- package/dist/services/solauto/solautoMarginfiClient.d.ts.map +1 -1
- package/dist/services/solauto/solautoMarginfiClient.js +20 -7
- package/dist/solautoPosition/solautoPositionEx.d.ts +9 -1
- package/dist/solautoPosition/solautoPositionEx.d.ts.map +1 -1
- package/dist/solautoPosition/solautoPositionEx.js +56 -29
- package/dist/utils/marginfiUtils.d.ts +3 -2
- package/dist/utils/marginfiUtils.d.ts.map +1 -1
- package/dist/utils/marginfiUtils.js +14 -0
- package/dist/utils/numberUtils.d.ts +2 -0
- package/dist/utils/numberUtils.d.ts.map +1 -1
- package/dist/utils/numberUtils.js +7 -0
- package/dist/utils/solanaUtils.d.ts +2 -1
- package/dist/utils/solanaUtils.d.ts.map +1 -1
- package/dist/utils/solanaUtils.js +4 -0
- package/local/logPositions.ts +0 -2
- package/local/txSandbox.ts +13 -7
- package/package.json +1 -1
- package/src/services/flashLoans/flProviderAggregator.ts +3 -1
- package/src/services/flashLoans/marginfiFlProvider.ts +28 -38
- package/src/services/rebalance/rebalanceTxBuilder.ts +6 -19
- package/src/services/solauto/solautoClient.ts +1 -4
- package/src/services/solauto/solautoMarginfiClient.ts +50 -9
- package/src/solautoPosition/solautoPositionEx.ts +104 -46
- package/src/utils/marginfiUtils.ts +19 -1
- package/src/utils/numberUtils.ts +13 -1
- package/src/utils/solanaUtils.ts +10 -0
package/local/txSandbox.ts
CHANGED
@@ -2,6 +2,7 @@ import { Keypair, PublicKey } from "@solana/web3.js";
|
|
2
2
|
import { createSignerFromKeypair } from "@metaplex-foundation/umi";
|
3
3
|
import { fromWeb3JsKeypair } from "@metaplex-foundation/umi-web3js-adapters";
|
4
4
|
import {
|
5
|
+
borrow,
|
5
6
|
consoleLog,
|
6
7
|
getClient,
|
7
8
|
getSolanaRpcConnection,
|
@@ -12,7 +13,10 @@ import {
|
|
12
13
|
rebalance,
|
13
14
|
SOLAUTO_PROD_PROGRAM,
|
14
15
|
SOLAUTO_TEST_PROGRAM,
|
16
|
+
toBaseUnit,
|
17
|
+
tokenInfo,
|
15
18
|
TransactionsManager,
|
19
|
+
USDC,
|
16
20
|
} from "../src";
|
17
21
|
import { getSecretKey } from "./shared";
|
18
22
|
|
@@ -29,7 +33,7 @@ export async function main() {
|
|
29
33
|
|
30
34
|
const signer = createSignerFromKeypair(
|
31
35
|
umi,
|
32
|
-
fromWeb3JsKeypair(Keypair.fromSecretKey(getSecretKey(
|
36
|
+
fromWeb3JsKeypair(Keypair.fromSecretKey(getSecretKey()))
|
33
37
|
);
|
34
38
|
|
35
39
|
const client = getClient(LendingPlatform.Marginfi, {
|
@@ -41,14 +45,16 @@ export async function main() {
|
|
41
45
|
});
|
42
46
|
|
43
47
|
await client.initialize({
|
44
|
-
positionId:
|
45
|
-
authority: new PublicKey("5UqsR2PGzbP8pGPbXEeXx86Gjz2N2UFBAuFZUSVydAEe"),
|
46
|
-
|
47
|
-
|
48
|
-
|
48
|
+
positionId: 0,
|
49
|
+
// authority: new PublicKey("5UqsR2PGzbP8pGPbXEeXx86Gjz2N2UFBAuFZUSVydAEe"),
|
50
|
+
lpUserAccount: new PublicKey(
|
51
|
+
"GEokw9jqbh6d1xUNA3qaeYFFetbSR5Y1nt7C3chwwgSz"
|
52
|
+
),
|
49
53
|
});
|
50
54
|
|
51
|
-
const transactionItems = [
|
55
|
+
const transactionItems = [
|
56
|
+
rebalance(client, 300),
|
57
|
+
];
|
52
58
|
|
53
59
|
const txManager = new TransactionsManager(
|
54
60
|
client,
|
package/package.json
CHANGED
@@ -76,6 +76,8 @@ export class FlProviderAggregator extends FlProviderBase {
|
|
76
76
|
}
|
77
77
|
|
78
78
|
flashRepay(flashLoan: FlashLoanDetails): TransactionBuilder {
|
79
|
-
return this.flProvider(flashLoan.liquiditySource).flashRepay(
|
79
|
+
return this.flProvider(flashLoan.liquiditySource).flashRepay(
|
80
|
+
flashLoan
|
81
|
+
);
|
80
82
|
}
|
81
83
|
}
|
@@ -30,6 +30,7 @@ import {
|
|
30
30
|
getBankLiquidityAvailableBaseUnit,
|
31
31
|
getEmptyMarginfiAccountsByAuthority,
|
32
32
|
getMarginfiPriceOracle,
|
33
|
+
getRemainingAccountsForMarginfiHealthCheck,
|
33
34
|
getTokenAccount,
|
34
35
|
rpcAccountCreated,
|
35
36
|
safeGetPrice,
|
@@ -49,8 +50,11 @@ export class MarginfiFlProvider extends FlProviderBase {
|
|
49
50
|
private existingMarginfiAccounts!: MarginfiAccount[];
|
50
51
|
private supplyBankLiquiditySource!: Bank;
|
51
52
|
private debtBankLiquiditySource!: Bank;
|
53
|
+
|
52
54
|
private supplyImfiAccount!: IMFIAccount;
|
53
55
|
private debtImfiAccount!: IMFIAccount;
|
56
|
+
private supplyRemainingAccounts!: AccountMeta[];
|
57
|
+
private debtRemainingAccounts!: AccountMeta[];
|
54
58
|
|
55
59
|
async initialize() {
|
56
60
|
await this.setAvailableBanks();
|
@@ -62,10 +66,10 @@ export class MarginfiFlProvider extends FlProviderBase {
|
|
62
66
|
this.liquidityBank(TokenType.Supply).group.toString() !==
|
63
67
|
this.liquidityBank(TokenType.Debt).group.toString()
|
64
68
|
) {
|
65
|
-
this.setIntermediaryAccount([TokenType.Supply]);
|
66
|
-
this.setIntermediaryAccount([TokenType.Debt]);
|
69
|
+
await this.setIntermediaryAccount([TokenType.Supply]);
|
70
|
+
await this.setIntermediaryAccount([TokenType.Debt]);
|
67
71
|
} else {
|
68
|
-
this.setIntermediaryAccount([TokenType.Supply, TokenType.Debt]);
|
72
|
+
await this.setIntermediaryAccount([TokenType.Supply, TokenType.Debt]);
|
69
73
|
}
|
70
74
|
}
|
71
75
|
|
@@ -114,7 +118,7 @@ export class MarginfiFlProvider extends FlProviderBase {
|
|
114
118
|
this.debtBankLiquiditySource = debtBanks[0][1];
|
115
119
|
}
|
116
120
|
|
117
|
-
private setIntermediaryAccount(sources: TokenType[]) {
|
121
|
+
private async setIntermediaryAccount(sources: TokenType[]) {
|
118
122
|
const compatibleMarginfiAccounts = this.existingMarginfiAccounts.filter(
|
119
123
|
(x) => x.group.toString() == this.liquidityBank(sources[0]).group
|
120
124
|
);
|
@@ -136,6 +140,16 @@ export class MarginfiFlProvider extends FlProviderBase {
|
|
136
140
|
this.flSigners.push(signer);
|
137
141
|
}
|
138
142
|
|
143
|
+
const remainingAccounts = accountData
|
144
|
+
? (
|
145
|
+
await Promise.all(
|
146
|
+
accountData.lendingAccount.balances.map((balance) =>
|
147
|
+
getRemainingAccountsForMarginfiHealthCheck(this.umi, balance)
|
148
|
+
)
|
149
|
+
)
|
150
|
+
).flat()
|
151
|
+
: [];
|
152
|
+
|
139
153
|
for (const s of sources) {
|
140
154
|
const data: IMFIAccount = {
|
141
155
|
signer,
|
@@ -146,8 +160,10 @@ export class MarginfiFlProvider extends FlProviderBase {
|
|
146
160
|
const supply = s === TokenType.Supply;
|
147
161
|
if (supply) {
|
148
162
|
this.supplyImfiAccount = data;
|
163
|
+
this.supplyRemainingAccounts = remainingAccounts;
|
149
164
|
} else {
|
150
165
|
this.debtImfiAccount = data;
|
166
|
+
this.debtRemainingAccounts = remainingAccounts;
|
151
167
|
}
|
152
168
|
consoleLog(
|
153
169
|
`${supply ? "Supply" : "Debt"} iMfi account:`,
|
@@ -292,45 +308,19 @@ export class MarginfiFlProvider extends FlProviderBase {
|
|
292
308
|
);
|
293
309
|
const iMfiAccount = this.iMfiAccount(flashLoan.liquiditySource)!;
|
294
310
|
|
295
|
-
const remainingAccounts: AccountMeta[] =
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
flBankHadPrevBalance = true;
|
303
|
-
}
|
304
|
-
|
305
|
-
const priceOracle = publicKey(
|
306
|
-
await getMarginfiPriceOracle(this.umi, {
|
307
|
-
pk: toWeb3JsPublicKey(x.bankPk),
|
308
|
-
})
|
309
|
-
);
|
310
|
-
|
311
|
-
remainingAccounts.push(
|
312
|
-
...[
|
313
|
-
{
|
314
|
-
pubkey: x.bankPk,
|
315
|
-
isSigner: false,
|
316
|
-
isWritable: false,
|
317
|
-
},
|
318
|
-
{
|
319
|
-
pubkey: priceOracle,
|
320
|
-
isSigner: false,
|
321
|
-
isWritable: false,
|
322
|
-
},
|
323
|
-
]
|
324
|
-
);
|
325
|
-
}
|
326
|
-
});
|
327
|
-
}
|
311
|
+
const remainingAccounts: AccountMeta[] =
|
312
|
+
flashLoan.liquiditySource === TokenType.Supply
|
313
|
+
? this.supplyRemainingAccounts
|
314
|
+
: this.debtRemainingAccounts;
|
315
|
+
let iMfiAccountHadPrevFlBalance = remainingAccounts.find(
|
316
|
+
(x) => x.pubkey.toString() === bank.publicKey.toString()
|
317
|
+
);
|
328
318
|
|
329
319
|
return transactionBuilder()
|
330
320
|
.add(
|
331
321
|
lendingAccountRepay(this.umi, {
|
332
322
|
amount: flashLoan.baseUnitAmount,
|
333
|
-
repayAll: !
|
323
|
+
repayAll: !iMfiAccountHadPrevFlBalance,
|
334
324
|
bank: bank.publicKey,
|
335
325
|
bankLiquidityVault: publicKey(associatedBankAccs.liquidityVault),
|
336
326
|
marginfiAccount: publicKey(iMfiAccount.accountPk),
|
@@ -15,6 +15,7 @@ import {
|
|
15
15
|
getTokenAccount,
|
16
16
|
hasFirstRebalance,
|
17
17
|
hasLastRebalance,
|
18
|
+
realtimeUsdToEmaUsd,
|
18
19
|
safeGetPrice,
|
19
20
|
tokenInfo,
|
20
21
|
} from "../../utils";
|
@@ -202,31 +203,17 @@ export class RebalanceTxBuilder {
|
|
202
203
|
}
|
203
204
|
}
|
204
205
|
|
205
|
-
private realtimeUsdToEmaUsd(realtimeAmountUsd: number, mint: PublicKey) {
|
206
|
-
return (
|
207
|
-
(realtimeAmountUsd / safeGetPrice(mint, PriceType.Realtime)!) *
|
208
|
-
safeGetPrice(mint, PriceType.Ema)!
|
209
|
-
);
|
210
|
-
}
|
211
|
-
|
212
206
|
private getInitialRebalanceValues() {
|
213
207
|
let rebalanceValues = this.getRebalanceValues();
|
214
208
|
if (!rebalanceValues) {
|
215
209
|
return undefined;
|
216
210
|
}
|
217
211
|
|
218
|
-
|
219
|
-
this.
|
220
|
-
rebalanceValues.
|
221
|
-
|
222
|
-
|
223
|
-
this.realtimeUsdToEmaUsd(
|
224
|
-
rebalanceValues.endResult.debtUsd,
|
225
|
-
this.client.pos.debtMint
|
226
|
-
),
|
227
|
-
this.client.pos.state.liqThresholdBps
|
228
|
-
);
|
229
|
-
if (postRebalanceEmaUtilRateBps > this.client.pos.maxBoostToBps) {
|
212
|
+
if (
|
213
|
+
!this.client.pos.rebalanceHelper.validRealtimePricesBoost(
|
214
|
+
rebalanceValues.debtAdjustmentUsd
|
215
|
+
)
|
216
|
+
) {
|
230
217
|
this.priceType = PriceType.Ema;
|
231
218
|
rebalanceValues = this.getRebalanceValues();
|
232
219
|
if (!rebalanceValues) {
|
@@ -78,9 +78,6 @@ export abstract class SolautoClient extends ReferralStateManager {
|
|
78
78
|
await super.initialize(args);
|
79
79
|
|
80
80
|
const positionId = args.positionId ?? 0;
|
81
|
-
if (positionId === 0 && !args.lpUserAccount) {
|
82
|
-
throw new Error("Self managed position is missing arguments");
|
83
|
-
}
|
84
81
|
this.pos = await getOrCreatePositionEx(
|
85
82
|
this.umi,
|
86
83
|
this.authority,
|
@@ -96,7 +93,7 @@ export abstract class SolautoClient extends ReferralStateManager {
|
|
96
93
|
},
|
97
94
|
this.contextUpdates
|
98
95
|
);
|
99
|
-
if (this.pos.selfManaged
|
96
|
+
if (this.pos.selfManaged) {
|
100
97
|
await this.pos.refreshPositionState();
|
101
98
|
}
|
102
99
|
|
@@ -5,8 +5,12 @@ import {
|
|
5
5
|
publicKey,
|
6
6
|
PublicKey as UmiPublicKey,
|
7
7
|
createSignerFromKeypair,
|
8
|
+
AccountMeta,
|
8
9
|
} from "@metaplex-foundation/umi";
|
9
|
-
import {
|
10
|
+
import {
|
11
|
+
fromWeb3JsPublicKey,
|
12
|
+
toWeb3JsPublicKey,
|
13
|
+
} from "@metaplex-foundation/umi-web3js-adapters";
|
10
14
|
import { MarginfiAssetAccounts, RebalanceDetails } from "../../types";
|
11
15
|
import { getMarginfiAccounts, MarginfiProgramAccounts } from "../../constants";
|
12
16
|
import {
|
@@ -31,18 +35,26 @@ import {
|
|
31
35
|
marginfiAccountEmpty,
|
32
36
|
getTokenAccount,
|
33
37
|
hasFirstRebalance,
|
38
|
+
getRemainingAccountsForMarginfiHealthCheck,
|
39
|
+
getAccountMeta,
|
34
40
|
} from "../../utils";
|
35
41
|
import {
|
36
42
|
Bank,
|
43
|
+
fetchMarginfiAccount,
|
37
44
|
lendingAccountBorrow,
|
38
45
|
lendingAccountDeposit,
|
39
46
|
lendingAccountRepay,
|
40
47
|
lendingAccountWithdraw,
|
41
48
|
marginfiAccountInitialize,
|
42
49
|
safeFetchAllMarginfiAccount,
|
50
|
+
safeFetchMarginfiAccount,
|
43
51
|
} from "../../marginfi-sdk";
|
44
52
|
import { SolautoClient, SolautoClientArgs } from "./solautoClient";
|
45
53
|
|
54
|
+
function isSigner(account: PublicKey | Signer): account is Signer {
|
55
|
+
return "publicKey" in account;
|
56
|
+
}
|
57
|
+
|
46
58
|
export class SolautoMarginfiClient extends SolautoClient {
|
47
59
|
public lendingPlatform = LendingPlatform.Marginfi;
|
48
60
|
|
@@ -50,6 +62,7 @@ export class SolautoMarginfiClient extends SolautoClient {
|
|
50
62
|
|
51
63
|
public marginfiAccount!: PublicKey | Signer;
|
52
64
|
public marginfiAccountPk!: PublicKey;
|
65
|
+
public healthCheckRemainingAccounts?: AccountMeta[];
|
53
66
|
public marginfiGroup!: PublicKey;
|
54
67
|
|
55
68
|
public marginfiSupplyAccounts!: MarginfiAssetAccounts;
|
@@ -97,13 +110,25 @@ export class SolautoMarginfiClient extends SolautoClient {
|
|
97
110
|
);
|
98
111
|
}
|
99
112
|
}
|
100
|
-
this.marginfiAccountPk =
|
101
|
-
"publicKey" in this.marginfiAccount
|
102
|
-
? toWeb3JsPublicKey(this.marginfiAccount.publicKey)
|
103
|
-
: this.marginfiAccount;
|
104
113
|
|
105
|
-
|
114
|
+
this.marginfiAccountPk = isSigner(this.marginfiAccount)
|
115
|
+
? toWeb3JsPublicKey(this.marginfiAccount.publicKey)
|
116
|
+
: this.marginfiAccount;
|
117
|
+
|
118
|
+
if (isSigner(this.marginfiAccount)) {
|
106
119
|
this.otherSigners.push(this.marginfiAccount);
|
120
|
+
} else if (this.pos.selfManaged) {
|
121
|
+
const accountData = await fetchMarginfiAccount(
|
122
|
+
this.umi,
|
123
|
+
fromWeb3JsPublicKey(this.marginfiAccount as PublicKey)
|
124
|
+
);
|
125
|
+
this.healthCheckRemainingAccounts = (
|
126
|
+
await Promise.all(
|
127
|
+
accountData.lendingAccount.balances.map((balance) =>
|
128
|
+
getRemainingAccountsForMarginfiHealthCheck(this.umi, balance)
|
129
|
+
)
|
130
|
+
)
|
131
|
+
).flat();
|
107
132
|
}
|
108
133
|
|
109
134
|
this.marginfiSupplyAccounts =
|
@@ -247,6 +272,20 @@ export class SolautoMarginfiClient extends SolautoClient {
|
|
247
272
|
});
|
248
273
|
}
|
249
274
|
case "Borrow": {
|
275
|
+
const remainingAccounts = this.healthCheckRemainingAccounts ?? [];
|
276
|
+
if (
|
277
|
+
!remainingAccounts.find(
|
278
|
+
(x) =>
|
279
|
+
x.pubkey.toString() === this.marginfiDebtAccounts.bank.toString()
|
280
|
+
)
|
281
|
+
) {
|
282
|
+
remainingAccounts.push(
|
283
|
+
...[
|
284
|
+
getAccountMeta(new PublicKey(this.marginfiDebtAccounts.bank)),
|
285
|
+
getAccountMeta(this.debtPriceOracle),
|
286
|
+
]
|
287
|
+
);
|
288
|
+
}
|
250
289
|
return lendingAccountBorrow(this.umi, {
|
251
290
|
amount: args.fields[0],
|
252
291
|
signer: this.signer,
|
@@ -260,7 +299,7 @@ export class SolautoMarginfiClient extends SolautoClient {
|
|
260
299
|
bankLiquidityVaultAuthority: publicKey(
|
261
300
|
this.marginfiDebtAccounts.vaultAuthority
|
262
301
|
),
|
263
|
-
});
|
302
|
+
}).addRemainingAccounts(remainingAccounts);
|
264
303
|
}
|
265
304
|
case "Repay": {
|
266
305
|
return lendingAccountRepay(this.umi, {
|
@@ -293,7 +332,7 @@ export class SolautoMarginfiClient extends SolautoClient {
|
|
293
332
|
bankLiquidityVaultAuthority: publicKey(
|
294
333
|
this.marginfiSupplyAccounts.vaultAuthority
|
295
334
|
),
|
296
|
-
});
|
335
|
+
}).addRemainingAccounts(this.healthCheckRemainingAccounts ?? []);
|
297
336
|
}
|
298
337
|
}
|
299
338
|
}
|
@@ -408,7 +447,9 @@ export class SolautoMarginfiClient extends SolautoClient {
|
|
408
447
|
? publicKey(getTokenAccount(this.authority, this.pos.supplyMint))
|
409
448
|
: undefined,
|
410
449
|
vaultSupplyTa: publicKey(this.marginfiSupplyAccounts.liquidityVault),
|
411
|
-
supplyVaultAuthority: publicKey(
|
450
|
+
supplyVaultAuthority: publicKey(
|
451
|
+
this.marginfiSupplyAccounts.vaultAuthority
|
452
|
+
),
|
412
453
|
debtBank: publicKey(this.marginfiDebtAccounts.bank),
|
413
454
|
debtPriceOracle: publicKey(this.debtPriceOracle),
|
414
455
|
positionDebtTa: publicKey(this.positionDebtTa),
|
@@ -30,6 +30,7 @@ import {
|
|
30
30
|
maxRepayFromBps,
|
31
31
|
maxRepayToBps,
|
32
32
|
positionStateWithLatestPrices,
|
33
|
+
realtimeUsdToEmaUsd,
|
33
34
|
safeGetPrice,
|
34
35
|
solautoStrategyName,
|
35
36
|
supplyLiquidityDepositable,
|
@@ -87,6 +88,8 @@ export abstract class SolautoPositionEx {
|
|
87
88
|
protected _supplyPrice?: number;
|
88
89
|
protected _debtPrice?: number;
|
89
90
|
|
91
|
+
public rebalanceHelper!: PositionRebalanceHelper;
|
92
|
+
|
90
93
|
constructor(args: PositionExArgs) {
|
91
94
|
this.umi = args.umi;
|
92
95
|
this.contextUpdates = args.contextUpdates;
|
@@ -111,6 +114,8 @@ export abstract class SolautoPositionEx {
|
|
111
114
|
|
112
115
|
this._data = args.data;
|
113
116
|
this.firstState = { ...args.data.state };
|
117
|
+
|
118
|
+
this.rebalanceHelper = new PositionRebalanceHelper(this);
|
114
119
|
}
|
115
120
|
|
116
121
|
abstract lendingPool(): Promise<PublicKey>;
|
@@ -277,53 +282,12 @@ export abstract class SolautoPositionEx {
|
|
277
282
|
return tokenInfo(this.supplyMint).isMeme || tokenInfo(this.debtMint).isMeme;
|
278
283
|
}
|
279
284
|
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
if (limitsUpToDate) {
|
286
|
-
const { debtAdjustmentUsd } = getDebtAdjustment(
|
287
|
-
this.state.liqThresholdBps,
|
288
|
-
{ supplyUsd: this.supplyUsd(), debtUsd: this.debtUsd() },
|
289
|
-
this.boostToBps,
|
290
|
-
{ solauto: 50, lpBorrow: 50, flashLoan: 50 } // TODO: get true data here instead of magic numbers
|
291
|
-
);
|
292
|
-
|
293
|
-
const sufficientLiquidity =
|
294
|
-
this.debtLiquidityUsdAvailable * 0.95 > debtAdjustmentUsd &&
|
295
|
-
this.supplyLiquidityUsdDepositable * 0.95 > debtAdjustmentUsd;
|
296
|
-
|
297
|
-
if (!sufficientLiquidity) {
|
298
|
-
consoleLog("Insufficient liquidity to further boost");
|
299
|
-
}
|
300
|
-
return sufficientLiquidity;
|
301
|
-
}
|
302
|
-
|
303
|
-
return true;
|
304
|
-
}
|
305
|
-
|
306
|
-
eligibleForRebalance(bpsDistanceThreshold = 0): RebalanceAction | undefined {
|
307
|
-
if (!this.settings || !this.supplyUsd()) {
|
308
|
-
return undefined;
|
309
|
-
}
|
310
|
-
|
311
|
-
const realtimeLiqUtilRateBps = this.liqUtilizationRateBps(
|
312
|
-
PriceType.Realtime
|
285
|
+
eligibleForRebalance(
|
286
|
+
bpsDistanceThreshold: number = 0
|
287
|
+
): RebalanceAction | undefined {
|
288
|
+
return this.rebalanceHelper.eligibleForRebalance(
|
289
|
+
bpsDistanceThreshold
|
313
290
|
);
|
314
|
-
const emaLiqUtilRateBps = this.liqUtilizationRateBps(PriceType.Ema);
|
315
|
-
|
316
|
-
if (this.repayFromBps - realtimeLiqUtilRateBps <= bpsDistanceThreshold) {
|
317
|
-
return "repay";
|
318
|
-
} else if (
|
319
|
-
realtimeLiqUtilRateBps - this.boostFromBps <= bpsDistanceThreshold ||
|
320
|
-
emaLiqUtilRateBps - this.boostFromBps <= bpsDistanceThreshold
|
321
|
-
) {
|
322
|
-
const sufficientLiquidity = this.sufficientLiquidityToBoost();
|
323
|
-
return sufficientLiquidity ? "boost" : undefined;
|
324
|
-
}
|
325
|
-
|
326
|
-
return undefined;
|
327
291
|
}
|
328
292
|
|
329
293
|
eligibleForRefresh(): boolean {
|
@@ -438,3 +402,97 @@ export abstract class SolautoPositionEx {
|
|
438
402
|
);
|
439
403
|
}
|
440
404
|
}
|
405
|
+
|
406
|
+
class PositionRebalanceHelper {
|
407
|
+
constructor(private pos: SolautoPositionEx) {}
|
408
|
+
|
409
|
+
private sufficientLiquidityToBoost() {
|
410
|
+
const limitsUpToDate =
|
411
|
+
this.pos.debtLiquidityUsdAvailable !== 0 ||
|
412
|
+
this.pos.supplyLiquidityUsdDepositable !== 0;
|
413
|
+
|
414
|
+
if (limitsUpToDate) {
|
415
|
+
const { debtAdjustmentUsd } = getDebtAdjustment(
|
416
|
+
this.pos.state.liqThresholdBps,
|
417
|
+
{ supplyUsd: this.pos.supplyUsd(), debtUsd: this.pos.debtUsd() },
|
418
|
+
this.pos.boostToBps,
|
419
|
+
{ solauto: 50, lpBorrow: 50, flashLoan: 50 } // Overshoot fees
|
420
|
+
);
|
421
|
+
|
422
|
+
const sufficientLiquidity =
|
423
|
+
this.pos.debtLiquidityUsdAvailable * 0.95 > debtAdjustmentUsd &&
|
424
|
+
this.pos.supplyLiquidityUsdDepositable * 0.95 > debtAdjustmentUsd;
|
425
|
+
|
426
|
+
if (!sufficientLiquidity) {
|
427
|
+
consoleLog("Insufficient liquidity to further boost");
|
428
|
+
}
|
429
|
+
return sufficientLiquidity;
|
430
|
+
}
|
431
|
+
|
432
|
+
return true;
|
433
|
+
}
|
434
|
+
|
435
|
+
validRealtimePricesBoost(debtAdjustmentUsd: number) {
|
436
|
+
if (this.pos.lendingPlatform !== LendingPlatform.Marginfi) {
|
437
|
+
// TODO: LP
|
438
|
+
return true;
|
439
|
+
}
|
440
|
+
|
441
|
+
const postRebalanceLiqUtilRate = getLiqUtilzationRateBps(
|
442
|
+
realtimeUsdToEmaUsd(
|
443
|
+
this.pos.supplyUsd() + debtAdjustmentUsd,
|
444
|
+
this.pos.supplyMint
|
445
|
+
),
|
446
|
+
realtimeUsdToEmaUsd(
|
447
|
+
this.pos.debtUsd() + debtAdjustmentUsd,
|
448
|
+
this.pos.debtMint
|
449
|
+
),
|
450
|
+
this.pos.state.liqThresholdBps
|
451
|
+
);
|
452
|
+
|
453
|
+
return postRebalanceLiqUtilRate <= this.pos.maxBoostToBps;
|
454
|
+
}
|
455
|
+
|
456
|
+
private validBoostFromHere() {
|
457
|
+
const { debtAdjustmentUsd } = getDebtAdjustment(
|
458
|
+
this.pos.state.liqThresholdBps,
|
459
|
+
{
|
460
|
+
supplyUsd: this.pos.supplyUsd(PriceType.Realtime),
|
461
|
+
debtUsd: this.pos.debtUsd(PriceType.Realtime),
|
462
|
+
},
|
463
|
+
this.pos.boostToBps,
|
464
|
+
{ solauto: 25, lpBorrow: 0, flashLoan: 0 } // Undershoot fees
|
465
|
+
);
|
466
|
+
|
467
|
+
return this.validRealtimePricesBoost(debtAdjustmentUsd);
|
468
|
+
}
|
469
|
+
|
470
|
+
eligibleForRebalance(
|
471
|
+
bpsDistanceThreshold: number
|
472
|
+
): RebalanceAction | undefined {
|
473
|
+
if (!this.pos.settings || !this.pos.supplyUsd()) {
|
474
|
+
return undefined;
|
475
|
+
}
|
476
|
+
|
477
|
+
const realtimeLiqUtilRateBps = this.pos.liqUtilizationRateBps(
|
478
|
+
PriceType.Realtime
|
479
|
+
);
|
480
|
+
const emaLiqUtilRateBps = this.pos.liqUtilizationRateBps(PriceType.Ema);
|
481
|
+
|
482
|
+
if (
|
483
|
+
this.pos.repayFromBps - realtimeLiqUtilRateBps <=
|
484
|
+
bpsDistanceThreshold
|
485
|
+
) {
|
486
|
+
return "repay";
|
487
|
+
} else if (
|
488
|
+
(realtimeLiqUtilRateBps - this.pos.boostFromBps <= bpsDistanceThreshold ||
|
489
|
+
emaLiqUtilRateBps - this.pos.boostFromBps <= bpsDistanceThreshold) &&
|
490
|
+
this.validBoostFromHere()
|
491
|
+
) {
|
492
|
+
const sufficientLiquidity = this.sufficientLiquidityToBoost();
|
493
|
+
return sufficientLiquidity ? "boost" : undefined;
|
494
|
+
}
|
495
|
+
|
496
|
+
return undefined;
|
497
|
+
}
|
498
|
+
}
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import { PublicKey } from "@solana/web3.js";
|
2
|
-
import { Program, publicKey, Umi } from "@metaplex-foundation/umi";
|
2
|
+
import { AccountMeta, Program, publicKey, Umi } from "@metaplex-foundation/umi";
|
3
3
|
import {
|
4
4
|
fromWeb3JsPublicKey,
|
5
5
|
toWeb3JsPublicKey,
|
@@ -16,6 +16,7 @@ import {
|
|
16
16
|
USD_DECIMALS,
|
17
17
|
} from "../constants";
|
18
18
|
import {
|
19
|
+
Balance,
|
19
20
|
Bank,
|
20
21
|
deserializeMarginfiAccount,
|
21
22
|
fetchBank,
|
@@ -44,6 +45,7 @@ import {
|
|
44
45
|
getMostUpToDatePythOracle,
|
45
46
|
getPythPushOracleAddress,
|
46
47
|
} from "./pythUtils";
|
48
|
+
import { getAccountMeta } from "./solanaUtils";
|
47
49
|
|
48
50
|
export function createDynamicMarginfiProgram(env?: ProgramEnv): Program {
|
49
51
|
return {
|
@@ -179,6 +181,22 @@ export function findMarginfiAccounts(
|
|
179
181
|
throw new Error(`Marginfi accounts not found by the bank: ${bank}`);
|
180
182
|
}
|
181
183
|
|
184
|
+
export async function getRemainingAccountsForMarginfiHealthCheck(
|
185
|
+
umi: Umi,
|
186
|
+
balance: Balance
|
187
|
+
): Promise<AccountMeta[]> {
|
188
|
+
if (!balance.active) {
|
189
|
+
return [];
|
190
|
+
}
|
191
|
+
const priceOracle = await getMarginfiPriceOracle(umi, {
|
192
|
+
pk: toWeb3JsPublicKey(balance.bankPk),
|
193
|
+
});
|
194
|
+
return [
|
195
|
+
getAccountMeta(toWeb3JsPublicKey(balance.bankPk)),
|
196
|
+
getAccountMeta(priceOracle),
|
197
|
+
];
|
198
|
+
}
|
199
|
+
|
182
200
|
export function calcMarginfiMaxLtvAndLiqThresholdBps(
|
183
201
|
supplyBank: Bank,
|
184
202
|
debtBank: Bank,
|
package/src/utils/numberUtils.ts
CHANGED
@@ -1,11 +1,13 @@
|
|
1
|
+
import { PublicKey } from "@solana/web3.js";
|
1
2
|
import {
|
2
3
|
BASIS_POINTS,
|
3
4
|
MIN_REPAY_GAP_BPS,
|
4
5
|
OFFSET_FROM_MAX_LTV,
|
5
6
|
USD_DECIMALS,
|
6
7
|
} from "../constants";
|
7
|
-
import { PositionState } from "../generated";
|
8
|
+
import { PositionState, PriceType } from "../generated";
|
8
9
|
import { RoundAction } from "../types";
|
10
|
+
import { safeGetPrice } from "./priceUtils";
|
9
11
|
|
10
12
|
export function calcNetWorthUsd(state?: PositionState) {
|
11
13
|
return fromRoundedUsdValue(state?.netWorth.baseAmountUsdValue ?? BigInt(0));
|
@@ -203,3 +205,13 @@ export function maxBoostToBps(maxLtvBps: number, liqThresholdBps: number) {
|
|
203
205
|
getMaxLiqUtilizationRateBps(maxLtvBps, liqThresholdBps, OFFSET_FROM_MAX_LTV)
|
204
206
|
);
|
205
207
|
}
|
208
|
+
|
209
|
+
export function realtimeUsdToEmaUsd(
|
210
|
+
realtimeAmountUsd: number,
|
211
|
+
mint: PublicKey
|
212
|
+
) {
|
213
|
+
return (
|
214
|
+
(realtimeAmountUsd / safeGetPrice(mint, PriceType.Realtime)!) *
|
215
|
+
safeGetPrice(mint, PriceType.Ema)!
|
216
|
+
);
|
217
|
+
}
|
package/src/utils/solanaUtils.ts
CHANGED
@@ -18,6 +18,7 @@ import {
|
|
18
18
|
createTransferInstruction,
|
19
19
|
} from "@solana/spl-token";
|
20
20
|
import {
|
21
|
+
AccountMeta,
|
21
22
|
AddressLookupTableInput,
|
22
23
|
Signer,
|
23
24
|
TransactionBuilder,
|
@@ -28,6 +29,7 @@ import {
|
|
28
29
|
} from "@metaplex-foundation/umi";
|
29
30
|
import {
|
30
31
|
fromWeb3JsInstruction,
|
32
|
+
fromWeb3JsPublicKey,
|
31
33
|
toWeb3JsPublicKey,
|
32
34
|
toWeb3JsTransaction,
|
33
35
|
} from "@metaplex-foundation/umi-web3js-adapters";
|
@@ -154,6 +156,14 @@ export function splTokenTransferUmiIx(
|
|
154
156
|
);
|
155
157
|
}
|
156
158
|
|
159
|
+
export function getAccountMeta(
|
160
|
+
pubkey: PublicKey,
|
161
|
+
isSigner: boolean = false,
|
162
|
+
isWritable: boolean = false
|
163
|
+
): AccountMeta {
|
164
|
+
return { pubkey: fromWeb3JsPublicKey(pubkey), isSigner, isWritable };
|
165
|
+
}
|
166
|
+
|
157
167
|
export async function getWalletSplBalances(
|
158
168
|
conn: Connection,
|
159
169
|
wallet: PublicKey,
|