@haven-fi/solauto-sdk 1.0.836 → 1.0.837
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/constants/switchboardConstants.d.ts.map +1 -1
- package/dist/constants/switchboardConstants.js +2 -25
- package/dist/externalSdks/marginfi/types/oracleSetup.d.ts +22 -1
- package/dist/externalSdks/marginfi/types/oracleSetup.d.ts.map +1 -1
- package/dist/externalSdks/marginfi/types/oracleSetup.js +21 -0
- package/dist/services/flashLoans/marginfiFlProvider.d.ts.map +1 -1
- package/dist/services/flashLoans/marginfiFlProvider.js +5 -8
- package/dist/services/solauto/solautoMarginfiClient.d.ts +3 -0
- package/dist/services/solauto/solautoMarginfiClient.d.ts.map +1 -1
- package/dist/services/solauto/solautoMarginfiClient.js +22 -12
- package/dist/solautoPosition/marginfiSolautoPositionEx.d.ts.map +1 -1
- package/dist/solautoPosition/marginfiSolautoPositionEx.js +4 -2
- package/dist/utils/marginfi/data.d.ts.map +1 -1
- package/dist/utils/marginfi/data.js +9 -15
- package/dist/utils/marginfi/general.d.ts +5 -0
- package/dist/utils/marginfi/general.d.ts.map +1 -1
- package/dist/utils/marginfi/general.js +36 -8
- package/dist/utils/marginfi/index.d.ts +1 -0
- package/dist/utils/marginfi/index.d.ts.map +1 -1
- package/dist/utils/marginfi/index.js +1 -0
- package/dist/utils/marginfi/prices.d.ts +14 -0
- package/dist/utils/marginfi/prices.d.ts.map +1 -0
- package/dist/utils/marginfi/prices.js +119 -0
- package/dist/utils/switchboardUtils.d.ts +5 -3
- package/dist/utils/switchboardUtils.d.ts.map +1 -1
- package/dist/utils/switchboardUtils.js +35 -12
- package/local/txSandbox.ts +3 -3
- package/package.json +1 -1
- package/src/constants/switchboardConstants.ts +2 -27
- package/src/externalSdks/marginfi/types/oracleSetup.ts +21 -0
- package/src/services/flashLoans/marginfiFlProvider.ts +10 -11
- package/src/services/solauto/solautoMarginfiClient.ts +34 -12
- package/src/solautoPosition/marginfiSolautoPositionEx.ts +14 -3
- package/src/utils/marginfi/data.ts +16 -16
- package/src/utils/marginfi/general.ts +46 -12
- package/src/utils/marginfi/index.ts +2 -1
- package/src/utils/marginfi/prices.ts +165 -0
- package/src/utils/switchboardUtils.ts +55 -17
- package/tests/fixtures/README.md +17 -0
- package/tests/fixtures/marginfiOracles.json +82 -0
- package/tests/unit/marginfiOracles.ts +333 -0
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import { assert } from "chai";
|
|
2
|
+
import {
|
|
3
|
+
publicKey,
|
|
4
|
+
RpcAccount,
|
|
5
|
+
Umi,
|
|
6
|
+
createNoopSigner,
|
|
7
|
+
} from "@metaplex-foundation/umi";
|
|
8
|
+
import { createUmi } from "@metaplex-foundation/umi-bundle-defaults";
|
|
9
|
+
import { PublicKey } from "@solana/web3.js";
|
|
10
|
+
import {
|
|
11
|
+
composeRemainingAccounts,
|
|
12
|
+
getMarginfiBankHealthAccounts,
|
|
13
|
+
getMarginfiBankPrice,
|
|
14
|
+
getPythLstPriceData,
|
|
15
|
+
safeGetMarginfiBankPrice,
|
|
16
|
+
addSwbOraclePullTxs,
|
|
17
|
+
getMarginfiSwitchboardFeeds,
|
|
18
|
+
LendingPlatform,
|
|
19
|
+
PriceType,
|
|
20
|
+
PRICES,
|
|
21
|
+
SWITCHBOARD_PRICE_FEED_IDS,
|
|
22
|
+
SolautoMarginfiClient,
|
|
23
|
+
getMarginfiAccounts,
|
|
24
|
+
getAccountMeta,
|
|
25
|
+
RebalanceStep,
|
|
26
|
+
RebalanceDirection,
|
|
27
|
+
SolautoRebalanceType,
|
|
28
|
+
} from "../../src";
|
|
29
|
+
import {
|
|
30
|
+
deserializeBank,
|
|
31
|
+
OracleSetup,
|
|
32
|
+
PriceBias,
|
|
33
|
+
} from "../../src/externalSdks/marginfi";
|
|
34
|
+
import fixtures from "../fixtures/marginfiOracles.json";
|
|
35
|
+
|
|
36
|
+
function account(value: {
|
|
37
|
+
publicKey: string;
|
|
38
|
+
owner: string;
|
|
39
|
+
data: string;
|
|
40
|
+
}): RpcAccount {
|
|
41
|
+
return {
|
|
42
|
+
...value,
|
|
43
|
+
publicKey: publicKey(value.publicKey),
|
|
44
|
+
owner: publicKey(value.owner),
|
|
45
|
+
data: Buffer.from(value.data, "base64"),
|
|
46
|
+
exists: true,
|
|
47
|
+
} as RpcAccount;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const bank = (ticker: string) =>
|
|
51
|
+
deserializeBank(account(fixtures.banks.find((b) => b.ticker === ticker)!));
|
|
52
|
+
const oracle = (address: string) =>
|
|
53
|
+
account(fixtures.oracles.find((o) => o.publicKey === address)!);
|
|
54
|
+
const clock = () => account(fixtures.oracles[4]);
|
|
55
|
+
const noRpc = {} as Umi;
|
|
56
|
+
|
|
57
|
+
describe("Marginfi oracle migration", () => {
|
|
58
|
+
it("decodes the migrated mainnet banks without changing discriminator values", () => {
|
|
59
|
+
for (const ticker of ["LST", "hSOL", "JupSOL"]) {
|
|
60
|
+
assert.equal(bank(ticker).config.oracleSetup, OracleSetup.PythLST);
|
|
61
|
+
}
|
|
62
|
+
for (const ticker of ["SOL", "JitoSOL", "JTO"]) {
|
|
63
|
+
assert.equal(bank(ticker).config.oracleSetup, OracleSetup.PythPushOracle);
|
|
64
|
+
}
|
|
65
|
+
assert.equal(OracleSetup.PythLST, 22);
|
|
66
|
+
assert.equal(bank("USDC").config.oracleSetup, OracleSetup.Fixed);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("does not crank the six banks that migrated away from Switchboard", async () => {
|
|
70
|
+
const banks = fixtures.banks
|
|
71
|
+
.filter((b) => b.env === "Prod")
|
|
72
|
+
.map((b) => bank(b.ticker));
|
|
73
|
+
assert.deepEqual(getMarginfiSwitchboardFeeds(banks), {});
|
|
74
|
+
for (const b of banks)
|
|
75
|
+
assert.isUndefined(SWITCHBOARD_PRICE_FEED_IDS[b.mint]);
|
|
76
|
+
let calls = 0;
|
|
77
|
+
const client: any = {
|
|
78
|
+
lendingPlatform: LendingPlatform.Marginfi,
|
|
79
|
+
pos: {
|
|
80
|
+
supplyMint: new PublicKey(banks[0].mint),
|
|
81
|
+
debtMint: new PublicKey(banks[3].mint),
|
|
82
|
+
lpSupplyAccount: new PublicKey(banks[0].publicKey),
|
|
83
|
+
lpDebtAccount: new PublicKey(banks[3].publicKey),
|
|
84
|
+
},
|
|
85
|
+
umi: {
|
|
86
|
+
rpc: {
|
|
87
|
+
getAccounts: async (keys: string[]) => {
|
|
88
|
+
calls++;
|
|
89
|
+
return keys.map((key) =>
|
|
90
|
+
account(fixtures.banks.find((b) => b.publicKey === key)!)
|
|
91
|
+
);
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
connection: {
|
|
96
|
+
getSlot: () => {
|
|
97
|
+
throw new Error("Should not read a Switchboard feed");
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
const txs: any[] = [{ oracleInteractor: true }];
|
|
102
|
+
await addSwbOraclePullTxs(client, txs);
|
|
103
|
+
assert.equal(calls, 1);
|
|
104
|
+
assert.lengthOf(txs, 1);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("uses the bank's configured Switchboard address even for an unmapped mint", () => {
|
|
108
|
+
const b = bank("SOL");
|
|
109
|
+
b.config.oracleSetup = OracleSetup.SwitchboardPull;
|
|
110
|
+
b.config.oracleKeys[0] = publicKey(
|
|
111
|
+
"4Hmd6PdjVA9auCoScE12iaBogfwS4ZXQ6VZoBeqanwWW"
|
|
112
|
+
);
|
|
113
|
+
assert.deepEqual(getMarginfiSwitchboardFeeds([b]), {
|
|
114
|
+
[b.mint]: b.config.oracleKeys[0],
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("keeps stake pools with their banks when sorting mixed health-account groups", async () => {
|
|
119
|
+
const lst = bank("LST");
|
|
120
|
+
const sol = bank("SOL");
|
|
121
|
+
const lstGroup = await getMarginfiBankHealthAccounts(noRpc, lst);
|
|
122
|
+
const solGroup = await getMarginfiBankHealthAccounts(
|
|
123
|
+
noRpc,
|
|
124
|
+
sol,
|
|
125
|
+
new PublicKey(sol.config.oracleKeys[0])
|
|
126
|
+
);
|
|
127
|
+
assert.deepEqual(
|
|
128
|
+
lstGroup.map((a) => a.pubkey),
|
|
129
|
+
[lst.publicKey, ...lst.config.oracleKeys.slice(0, 2)]
|
|
130
|
+
);
|
|
131
|
+
assert.deepEqual(
|
|
132
|
+
composeRemainingAccounts([...solGroup, ...lstGroup]).map((a) => a.pubkey),
|
|
133
|
+
[...lstGroup, ...solGroup].map((a) => a.pubkey)
|
|
134
|
+
);
|
|
135
|
+
// Repeated base oracles must appear in each bank group, including two LSTs.
|
|
136
|
+
const hsol = await getMarginfiBankHealthAccounts(noRpc, bank("hSOL"));
|
|
137
|
+
const mixed = composeRemainingAccounts([...lstGroup, ...hsol, ...solGroup]);
|
|
138
|
+
assert.lengthOf(mixed, 8);
|
|
139
|
+
assert.equal(
|
|
140
|
+
mixed.filter((a) => a.pubkey === lst.config.oracleKeys[0]).length,
|
|
141
|
+
3
|
|
142
|
+
);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("prices SPL and both Sanctum stake-pool variants from the Pyth base feed", () => {
|
|
146
|
+
const expected = {
|
|
147
|
+
LST: 152.58419233,
|
|
148
|
+
hSOL: 117.37554225,
|
|
149
|
+
JupSOL: 119.63081594,
|
|
150
|
+
};
|
|
151
|
+
for (const [ticker, price] of Object.entries(expected)) {
|
|
152
|
+
const b = bank(ticker);
|
|
153
|
+
const prices = getPythLstPriceData(
|
|
154
|
+
b,
|
|
155
|
+
oracle(b.config.oracleKeys[0]),
|
|
156
|
+
oracle(b.config.oracleKeys[1]),
|
|
157
|
+
clock()
|
|
158
|
+
);
|
|
159
|
+
assert.closeTo(prices.realtimePrice, price, 1e-8);
|
|
160
|
+
assert.isAbove(prices.emaPrice, prices.realtimePrice);
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it("includes the stake pool in Solauto refresh, interaction, rebalance and direct borrow instructions", async () => {
|
|
165
|
+
const supply = bank("LST");
|
|
166
|
+
const debt = bank("SOL");
|
|
167
|
+
const key = new PublicKey(supply.publicKey);
|
|
168
|
+
const pool = getAccountMeta(new PublicKey(supply.config.oracleKeys[1]));
|
|
169
|
+
const client: any = Object.assign(
|
|
170
|
+
Object.create(SolautoMarginfiClient.prototype),
|
|
171
|
+
{
|
|
172
|
+
umi: createUmi("https://example.invalid"),
|
|
173
|
+
signer: createNoopSigner(publicKey(key)),
|
|
174
|
+
mfiAccounts: getMarginfiAccounts("Prod"),
|
|
175
|
+
marginfiGroup: new PublicKey(supply.group),
|
|
176
|
+
marginfiAccount: key,
|
|
177
|
+
marginfiAccountPk: key,
|
|
178
|
+
marginfiSupplyAccounts:
|
|
179
|
+
getMarginfiAccounts("Prod").bankAccounts[supply.group][supply.mint],
|
|
180
|
+
marginfiDebtAccounts:
|
|
181
|
+
getMarginfiAccounts("Prod").bankAccounts[debt.group][debt.mint],
|
|
182
|
+
supplyPriceOracle: new PublicKey(supply.config.oracleKeys[0]),
|
|
183
|
+
debtPriceOracle: new PublicKey(debt.config.oracleKeys[0]),
|
|
184
|
+
auxiliaryOracleAccounts: [pool],
|
|
185
|
+
pos: {
|
|
186
|
+
publicKey: key,
|
|
187
|
+
selfManaged: false,
|
|
188
|
+
supplyMint: new PublicKey(supply.mint),
|
|
189
|
+
debtMint: new PublicKey(debt.mint),
|
|
190
|
+
},
|
|
191
|
+
positionSupplyTa: key,
|
|
192
|
+
positionDebtTa: key,
|
|
193
|
+
signerSupplyTa: key,
|
|
194
|
+
signerDebtTa: key,
|
|
195
|
+
solautoFeesSupplyTa: key,
|
|
196
|
+
solautoFeesDebtTa: key,
|
|
197
|
+
referralState: key,
|
|
198
|
+
authority: key,
|
|
199
|
+
healthCheckRemainingAccounts: await getMarginfiBankHealthAccounts(
|
|
200
|
+
noRpc,
|
|
201
|
+
supply
|
|
202
|
+
),
|
|
203
|
+
debtBankHealthAccounts: await getMarginfiBankHealthAccounts(
|
|
204
|
+
noRpc,
|
|
205
|
+
debt,
|
|
206
|
+
new PublicKey(debt.config.oracleKeys[0])
|
|
207
|
+
),
|
|
208
|
+
}
|
|
209
|
+
);
|
|
210
|
+
const instructions = [
|
|
211
|
+
client.refreshIx(),
|
|
212
|
+
client.marginfiSolautoProtocolInteractionIx({
|
|
213
|
+
__kind: "Borrow",
|
|
214
|
+
fields: [1n],
|
|
215
|
+
}),
|
|
216
|
+
client.rebalanceIx(RebalanceStep.PreSwap, {
|
|
217
|
+
rebalanceType: SolautoRebalanceType.Regular,
|
|
218
|
+
priceType: PriceType.Realtime,
|
|
219
|
+
values: { rebalanceDirection: RebalanceDirection.Boost },
|
|
220
|
+
swapQuote: {
|
|
221
|
+
inputMint: supply.mint,
|
|
222
|
+
inAmount: "1",
|
|
223
|
+
swapMode: "ExactIn",
|
|
224
|
+
},
|
|
225
|
+
}),
|
|
226
|
+
];
|
|
227
|
+
for (const tx of instructions) {
|
|
228
|
+
const keys = tx.getInstructions()[0].keys;
|
|
229
|
+
assert.equal(keys[keys.length - 1].pubkey, pool.pubkey);
|
|
230
|
+
assert.isFalse(keys[keys.length - 1].isWritable);
|
|
231
|
+
}
|
|
232
|
+
client.pos.selfManaged = true;
|
|
233
|
+
const borrow = client
|
|
234
|
+
.marginfiProtocolInteractionIx({ __kind: "Borrow", fields: [1n] })
|
|
235
|
+
.getInstructions()[0];
|
|
236
|
+
assert.deepEqual(
|
|
237
|
+
borrow.keys.slice(-5).map((a: any) => a.pubkey),
|
|
238
|
+
[
|
|
239
|
+
supply.publicKey,
|
|
240
|
+
supply.config.oracleKeys[0],
|
|
241
|
+
pool.pubkey,
|
|
242
|
+
debt.publicKey,
|
|
243
|
+
debt.config.oracleKeys[0],
|
|
244
|
+
]
|
|
245
|
+
);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it("preserves exchange-rate-adjusted EMA and confidence independently of generic prices", async () => {
|
|
249
|
+
const b = bank("LST");
|
|
250
|
+
const umi = {
|
|
251
|
+
rpc: { getAccounts: async (keys: string[]) => keys.map(oracle) },
|
|
252
|
+
} as unknown as Umi;
|
|
253
|
+
assert.closeTo(
|
|
254
|
+
await getMarginfiBankPrice(umi, b, PriceType.Ema, PriceBias.Low),
|
|
255
|
+
152.8792992004,
|
|
256
|
+
1e-8
|
|
257
|
+
);
|
|
258
|
+
PRICES[b.mint] = {
|
|
259
|
+
realtimePrice: 1,
|
|
260
|
+
emaPrice: 1,
|
|
261
|
+
confInterval: 0,
|
|
262
|
+
emaConfInterval: 0,
|
|
263
|
+
time: 0,
|
|
264
|
+
};
|
|
265
|
+
assert.closeTo(
|
|
266
|
+
safeGetMarginfiBankPrice(b, PriceType.Realtime, PriceBias.High)!,
|
|
267
|
+
152.6201380112,
|
|
268
|
+
1e-8
|
|
269
|
+
);
|
|
270
|
+
delete PRICES[b.mint];
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it("reads the staging fixed price from the bank without fetching an oracle", async () => {
|
|
274
|
+
assert.closeTo(await getMarginfiBankPrice(noRpc, bank("USDC")), 1, 1e-8);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it("rejects incorrect, malformed, stale, or unverified pricing accounts", () => {
|
|
278
|
+
const b = bank("LST");
|
|
279
|
+
const mutations: Array<
|
|
280
|
+
(o: RpcAccount, p: RpcAccount, c: RpcAccount) => void
|
|
281
|
+
> = [
|
|
282
|
+
(_, p) => {
|
|
283
|
+
p.owner = publicKey(PublicKey.default);
|
|
284
|
+
},
|
|
285
|
+
(_, p) => {
|
|
286
|
+
p.publicKey = publicKey(PublicKey.default);
|
|
287
|
+
},
|
|
288
|
+
(_, p) => {
|
|
289
|
+
p.data = p.data.slice(0, 281);
|
|
290
|
+
},
|
|
291
|
+
(_, p) => {
|
|
292
|
+
p.data[0] = 0;
|
|
293
|
+
},
|
|
294
|
+
(_, p) => {
|
|
295
|
+
p.data[162] ^= 1;
|
|
296
|
+
},
|
|
297
|
+
(_, p) => {
|
|
298
|
+
(p.data as Buffer).writeBigUInt64LE(0n, 266);
|
|
299
|
+
},
|
|
300
|
+
(_, p) => {
|
|
301
|
+
(p.data as Buffer).writeBigUInt64LE(1n, 266);
|
|
302
|
+
},
|
|
303
|
+
(_, p) => {
|
|
304
|
+
(p.data as Buffer).writeBigUInt64LE(0n, 274);
|
|
305
|
+
},
|
|
306
|
+
(o) => {
|
|
307
|
+
o.owner = publicKey(PublicKey.default);
|
|
308
|
+
},
|
|
309
|
+
(o) => {
|
|
310
|
+
o.publicKey = publicKey(PublicKey.default);
|
|
311
|
+
},
|
|
312
|
+
(o) => {
|
|
313
|
+
o.data[0] ^= 1;
|
|
314
|
+
},
|
|
315
|
+
(o) => {
|
|
316
|
+
o.data[40] = 0;
|
|
317
|
+
},
|
|
318
|
+
(o) => {
|
|
319
|
+
(o.data as Buffer).writeBigInt64LE(0n, 93);
|
|
320
|
+
},
|
|
321
|
+
(_, __, c) => {
|
|
322
|
+
c.publicKey = publicKey(PublicKey.default);
|
|
323
|
+
},
|
|
324
|
+
];
|
|
325
|
+
for (const mutate of mutations) {
|
|
326
|
+
const o = oracle(b.config.oracleKeys[0]);
|
|
327
|
+
const p = oracle(b.config.oracleKeys[1]);
|
|
328
|
+
const c = clock();
|
|
329
|
+
mutate(o, p, c);
|
|
330
|
+
assert.throws(() => getPythLstPriceData(b, o, p, c));
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
});
|