@haven-fi/solauto-sdk 1.0.835 → 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/services/transactions/manager/transactionsManager.d.ts.map +1 -1
- package/dist/services/transactions/manager/transactionsManager.js +2 -1
- 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/services/transactions/manager/transactionsManager.ts +4 -1
- 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
- package/tests/unit/transactionsManager.ts +44 -1
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getPythLstPriceData = getPythLstPriceData;
|
|
4
|
+
exports.safeGetMarginfiBankPrice = safeGetMarginfiBankPrice;
|
|
5
|
+
exports.getMarginfiBankPrice = getMarginfiBankPrice;
|
|
6
|
+
const web3_js_1 = require("@solana/web3.js");
|
|
7
|
+
const umi_1 = require("@metaplex-foundation/umi");
|
|
8
|
+
const marginfi_1 = require("../../externalSdks/marginfi");
|
|
9
|
+
const pyth_1 = require("../../externalSdks/pyth");
|
|
10
|
+
const generated_1 = require("../../generated");
|
|
11
|
+
const priceUtils_1 = require("../priceUtils");
|
|
12
|
+
const numberUtils_1 = require("../numberUtils");
|
|
13
|
+
const STAKE_POOL_PROGRAMS = new Set([
|
|
14
|
+
"SPoo1Ku8WFXoNDMHPsrGSTSG1Y47rzgn41SLUNakuHy",
|
|
15
|
+
"SP12tWFxD9oJsVWNavTTBZvMbA6gkAmxtVgxdqvyvhY",
|
|
16
|
+
"SPMBzsVUuoHA4Jm6KunbsotaahvVikZs1JyTW6iJvbn",
|
|
17
|
+
]);
|
|
18
|
+
const PYTH_RECEIVER = "rec2HHDDnjLfj4kE7VyEtFA1HPGQLK33259532cRyHp";
|
|
19
|
+
// Bank prices include protocol-specific multipliers and must not be overwritten
|
|
20
|
+
// by the generic mint/USD market-price cache.
|
|
21
|
+
const bankPrices = new Map();
|
|
22
|
+
function bankPriceKey(bank) {
|
|
23
|
+
return [
|
|
24
|
+
bank.publicKey,
|
|
25
|
+
bank.config.oracleSetup,
|
|
26
|
+
...bank.config.oracleKeys,
|
|
27
|
+
...bank.config.padding.slice(6, 22),
|
|
28
|
+
].join(":");
|
|
29
|
+
}
|
|
30
|
+
function getPythLstPriceData(bank, oracle, stakePool, clock) {
|
|
31
|
+
const pool = Buffer.from(stakePool.data);
|
|
32
|
+
if (stakePool.publicKey !== bank.config.oracleKeys[1] ||
|
|
33
|
+
!STAKE_POOL_PROGRAMS.has(stakePool.owner) ||
|
|
34
|
+
pool.length < 282 ||
|
|
35
|
+
pool[0] !== 1 ||
|
|
36
|
+
new web3_js_1.PublicKey(pool.subarray(162, 194)).toString() !== bank.mint) {
|
|
37
|
+
throw new Error(`Invalid PythLST stake pool for bank ${bank.publicKey}`);
|
|
38
|
+
}
|
|
39
|
+
const clockData = Buffer.from(clock.data);
|
|
40
|
+
if (clock.publicKey !== web3_js_1.SYSVAR_CLOCK_PUBKEY.toString() ||
|
|
41
|
+
clockData.length < 40) {
|
|
42
|
+
throw new Error("Invalid clock account");
|
|
43
|
+
}
|
|
44
|
+
const epoch = clockData.readBigUInt64LE(16);
|
|
45
|
+
const now = clockData.readBigInt64LE(32);
|
|
46
|
+
const supply = pool.readBigUInt64LE(266);
|
|
47
|
+
if (supply === 0n || epoch > pool.readBigUInt64LE(274) + 1n) {
|
|
48
|
+
throw new Error("PythLST stake pool has zero supply or is stale");
|
|
49
|
+
}
|
|
50
|
+
const rate = (pool.readBigUInt64LE(258) << 48n) / supply;
|
|
51
|
+
if (rate <= 0n || rate >= 3n << 48n) {
|
|
52
|
+
throw new Error("Invalid PythLST exchange rate");
|
|
53
|
+
}
|
|
54
|
+
const data = Buffer.from(oracle.data);
|
|
55
|
+
if (oracle.publicKey !== bank.config.oracleKeys[0] ||
|
|
56
|
+
oracle.owner !== PYTH_RECEIVER ||
|
|
57
|
+
data.length < 133 ||
|
|
58
|
+
data.subarray(0, 8).toString("hex") !== "22f123639d7ef4cd" ||
|
|
59
|
+
data[40] !== 1 // VerificationLevel::Full
|
|
60
|
+
) {
|
|
61
|
+
throw new Error("Invalid PythLST base oracle");
|
|
62
|
+
}
|
|
63
|
+
const [price] = (0, pyth_1.getPriceMessageSerializer)().deserialize(data, 41);
|
|
64
|
+
if (now > price.publishTime + BigInt(bank.config.oracleMaxAge)) {
|
|
65
|
+
throw new Error("PythLST base oracle is stale");
|
|
66
|
+
}
|
|
67
|
+
if (price.price <= 0n || price.emaPrice <= 0n) {
|
|
68
|
+
throw new Error("Invalid PythLST base price");
|
|
69
|
+
}
|
|
70
|
+
// Match Marginfi's I80F48 multiplier and integer rounding before applying expo.
|
|
71
|
+
const scaled = (value) => Number((value * rate) >> 48n) * 10 ** price.exponent;
|
|
72
|
+
return {
|
|
73
|
+
realtimePrice: scaled(price.price),
|
|
74
|
+
emaPrice: scaled(price.emaPrice),
|
|
75
|
+
confInterval: scaled(price.conf) * 2.12,
|
|
76
|
+
emaConfInterval: scaled(price.emaConf) * 2.12,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function selectPrice(data, type = generated_1.PriceType.Realtime, bias) {
|
|
80
|
+
const price = type === generated_1.PriceType.Ema ? data.emaPrice : data.realtimePrice;
|
|
81
|
+
const conf = Math.min(type === generated_1.PriceType.Ema ? data.emaConfInterval : data.confInterval, price * 0.05);
|
|
82
|
+
return bias === undefined
|
|
83
|
+
? price
|
|
84
|
+
: price + (bias === marginfi_1.PriceBias.Low ? -conf : conf);
|
|
85
|
+
}
|
|
86
|
+
function safeGetMarginfiBankPrice(bank, type, bias) {
|
|
87
|
+
const data = bank ? bankPrices.get(bankPriceKey(bank)) : undefined;
|
|
88
|
+
return data ? selectPrice(data, type, bias) : undefined;
|
|
89
|
+
}
|
|
90
|
+
async function getMarginfiBankPrice(umi, bank, type, bias) {
|
|
91
|
+
let data;
|
|
92
|
+
if (bank.config.oracleSetup === marginfi_1.OracleSetup.PythLST) {
|
|
93
|
+
const accounts = await umi.rpc.getAccounts([
|
|
94
|
+
bank.config.oracleKeys[0],
|
|
95
|
+
bank.config.oracleKeys[1],
|
|
96
|
+
(0, umi_1.publicKey)(web3_js_1.SYSVAR_CLOCK_PUBKEY),
|
|
97
|
+
], { commitment: "confirmed" });
|
|
98
|
+
for (const account of accounts)
|
|
99
|
+
(0, umi_1.assertAccountExists)(account);
|
|
100
|
+
data = getPythLstPriceData(bank, accounts[0], accounts[1], accounts[2]);
|
|
101
|
+
}
|
|
102
|
+
else if (bank.config.oracleSetup === marginfi_1.OracleSetup.Fixed) {
|
|
103
|
+
// BankConfig.fixed_price occupies bytes 808..824 of the bank, formerly padding.
|
|
104
|
+
const price = (0, numberUtils_1.bytesToI80F48)(bank.config.padding.slice(6, 22));
|
|
105
|
+
if (!Number.isFinite(price) || price <= 0)
|
|
106
|
+
throw new Error("Invalid fixed bank price");
|
|
107
|
+
data = {
|
|
108
|
+
realtimePrice: price,
|
|
109
|
+
emaPrice: price,
|
|
110
|
+
confInterval: 0,
|
|
111
|
+
emaConfInterval: 0,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
return (await (0, priceUtils_1.fetchTokenPrices)([new web3_js_1.PublicKey(bank.mint)], type, bias))[0];
|
|
116
|
+
}
|
|
117
|
+
bankPrices.set(bankPriceKey(bank), data);
|
|
118
|
+
return selectPrice(data, type, bias);
|
|
119
|
+
}
|
|
@@ -4,16 +4,18 @@ import * as OnDemand from "@switchboard-xyz/on-demand";
|
|
|
4
4
|
import { TransactionItemInputs } from "../types";
|
|
5
5
|
import { CrossbarClient } from "@switchboard-xyz/common";
|
|
6
6
|
import { SolautoClient, TransactionItem } from "../services";
|
|
7
|
-
|
|
7
|
+
import { Bank } from "../externalSdks/marginfi";
|
|
8
|
+
export declare function getPullFeed(conn: Connection, mint: PublicKey, wallet?: PublicKey, feedId?: string): Promise<{
|
|
8
9
|
crossbar: CrossbarClient;
|
|
9
10
|
feed: OnDemand.PullFeed;
|
|
10
11
|
}>;
|
|
11
|
-
export declare function buildSwbSubmitResponseTx(conn: Connection, signer: Signer, mint: PublicKey): Promise<TransactionItemInputs | undefined>;
|
|
12
|
-
export declare function getSwitchboardFeedData(conn: Connection, mints: PublicKey[]): Promise<{
|
|
12
|
+
export declare function buildSwbSubmitResponseTx(conn: Connection, signer: Signer, mint: PublicKey, feedId?: string): Promise<TransactionItemInputs | undefined>;
|
|
13
|
+
export declare function getSwitchboardFeedData(conn: Connection, mints: PublicKey[], feedIds?: Record<string, string>): Promise<{
|
|
13
14
|
mint: PublicKey;
|
|
14
15
|
price: number;
|
|
15
16
|
stale: boolean;
|
|
16
17
|
}[]>;
|
|
17
18
|
export declare function isSwitchboardMint(mint: PublicKey | string): boolean;
|
|
19
|
+
export declare function getMarginfiSwitchboardFeeds(banks: Bank[]): Record<string, string>;
|
|
18
20
|
export declare function addSwbOraclePullTxs(client: SolautoClient, txs: TransactionItem[]): Promise<void>;
|
|
19
21
|
//# sourceMappingURL=switchboardUtils.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"switchboardUtils.d.ts","sourceRoot":"","sources":["../../src/utils/switchboardUtils.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,SAAS,EAGV,MAAM,iBAAiB,CAAC;AACzB,OAAO,
|
|
1
|
+
{"version":3,"file":"switchboardUtils.d.ts","sourceRoot":"","sources":["../../src/utils/switchboardUtils.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,SAAS,EAGV,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAa,MAAM,EAAsB,MAAM,0BAA0B,CAAC;AAGjF,OAAO,KAAK,QAAQ,MAAM,4BAA4B,CAAC;AAQvD,OAAO,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AAOjD,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AACzD,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,EAAE,IAAI,EAA6B,MAAM,0BAA0B,CAAC;AAG3E,wBAAsB,WAAW,CAC/B,IAAI,EAAE,UAAU,EAChB,IAAI,EAAE,SAAS,EACf,MAAM,CAAC,EAAE,SAAS,EAClB,MAAM,CAAC,EAAE,MAAM;;;GAiChB;AAED,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,UAAU,EAChB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,SAAS,EACf,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,qBAAqB,GAAG,SAAS,CAAC,CA0E5C;AAED,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,UAAU,EAChB,KAAK,EAAE,SAAS,EAAE,EAClB,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GACnC,OAAO,CAAC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,EAAE,CAAC,CAyB/D;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,WAEzD;AAED,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,IAAI,EAAE,GACZ,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAQxB;AAED,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,aAAa,EACrB,GAAG,EAAE,eAAe,EAAE,iBAmDvB"}
|
|
@@ -37,6 +37,7 @@ exports.getPullFeed = getPullFeed;
|
|
|
37
37
|
exports.buildSwbSubmitResponseTx = buildSwbSubmitResponseTx;
|
|
38
38
|
exports.getSwitchboardFeedData = getSwitchboardFeedData;
|
|
39
39
|
exports.isSwitchboardMint = isSwitchboardMint;
|
|
40
|
+
exports.getMarginfiSwitchboardFeeds = getMarginfiSwitchboardFeeds;
|
|
40
41
|
exports.addSwbOraclePullTxs = addSwbOraclePullTxs;
|
|
41
42
|
const web3_js_1 = require("@solana/web3.js");
|
|
42
43
|
const umi_1 = require("@metaplex-foundation/umi");
|
|
@@ -48,7 +49,13 @@ const generalUtils_1 = require("./generalUtils");
|
|
|
48
49
|
const solanaUtils_1 = require("./solanaUtils");
|
|
49
50
|
const common_1 = require("@switchboard-xyz/common");
|
|
50
51
|
const services_1 = require("../services");
|
|
51
|
-
|
|
52
|
+
const marginfi_1 = require("../externalSdks/marginfi");
|
|
53
|
+
const generated_1 = require("../generated");
|
|
54
|
+
async function getPullFeed(conn, mint, wallet, feedId) {
|
|
55
|
+
const address = feedId ?? constants_1.SWITCHBOARD_PRICE_FEED_IDS[mint.toString()]?.feedId;
|
|
56
|
+
if (!address) {
|
|
57
|
+
throw new Error(`No Switchboard feed configured for ${mint}`);
|
|
58
|
+
}
|
|
52
59
|
const dummyWallet = {
|
|
53
60
|
publicKey: wallet ?? new web3_js_1.PublicKey("11111111111111111111111111111111"),
|
|
54
61
|
signTransaction: async (tx) => tx,
|
|
@@ -60,14 +67,14 @@ async function getPullFeed(conn, mint, wallet) {
|
|
|
60
67
|
const sbProgram = await anchor_1.Program.at(ON_DEMAND_MAINNET_PID, provider);
|
|
61
68
|
const crossbar = new common_1.CrossbarClient(constants_1.CROSSBAR_URL);
|
|
62
69
|
(0, generalUtils_1.consoleLog)("Pulled SWB program!");
|
|
63
|
-
(0, generalUtils_1.consoleLog)("Feed id:",
|
|
70
|
+
(0, generalUtils_1.consoleLog)("Feed id:", address);
|
|
64
71
|
return {
|
|
65
72
|
crossbar,
|
|
66
|
-
feed: new PullFeed(sbProgram, new web3_js_1.PublicKey(
|
|
73
|
+
feed: new PullFeed(sbProgram, new web3_js_1.PublicKey(address)),
|
|
67
74
|
};
|
|
68
75
|
}
|
|
69
|
-
async function buildSwbSubmitResponseTx(conn, signer, mint) {
|
|
70
|
-
const { feed, crossbar } = await getPullFeed(conn, mint, (0, umi_web3js_adapters_1.toWeb3JsPublicKey)(signer.publicKey));
|
|
76
|
+
async function buildSwbSubmitResponseTx(conn, signer, mint, feedId) {
|
|
77
|
+
const { feed, crossbar } = await getPullFeed(conn, mint, (0, umi_web3js_adapters_1.toWeb3JsPublicKey)(signer.publicKey), feedId);
|
|
71
78
|
(0, generalUtils_1.consoleLog)("Fetching crank IX...");
|
|
72
79
|
const [pullIxs, responses, , luts] = await (0, generalUtils_1.retryWithExponentialBackoff)(async () => {
|
|
73
80
|
const res = await feed.fetchUpdateIx({
|
|
@@ -118,13 +125,13 @@ async function buildSwbSubmitResponseTx(conn, signer, mint) {
|
|
|
118
125
|
orderPrio: -2,
|
|
119
126
|
};
|
|
120
127
|
}
|
|
121
|
-
async function getSwitchboardFeedData(conn, mints) {
|
|
128
|
+
async function getSwitchboardFeedData(conn, mints, feedIds = {}) {
|
|
122
129
|
if (mints.length === 0) {
|
|
123
130
|
return [];
|
|
124
131
|
}
|
|
125
132
|
const currSlot = await (0, generalUtils_1.retryWithExponentialBackoff)(async () => await conn.getSlot("confirmed"), 5);
|
|
126
133
|
const results = await Promise.all(mints.map(async (mint) => {
|
|
127
|
-
const { feed } = await getPullFeed(conn, mint);
|
|
134
|
+
const { feed } = await getPullFeed(conn, mint, undefined, feedIds[mint.toString()]);
|
|
128
135
|
const result = await feed.loadData();
|
|
129
136
|
const price = Number(result.result.value) / Math.pow(10, 18);
|
|
130
137
|
const stale = currSlot > result.result.slot.toNumber() + result.maxStaleness;
|
|
@@ -135,19 +142,35 @@ async function getSwitchboardFeedData(conn, mints) {
|
|
|
135
142
|
function isSwitchboardMint(mint) {
|
|
136
143
|
return Object.keys(constants_1.SWITCHBOARD_PRICE_FEED_IDS).includes(mint.toString());
|
|
137
144
|
}
|
|
145
|
+
function getMarginfiSwitchboardFeeds(banks) {
|
|
146
|
+
return Object.fromEntries(banks
|
|
147
|
+
.filter((bank) => bank.config.oracleSetup === marginfi_1.OracleSetup.SwitchboardPull)
|
|
148
|
+
.map((bank) => [
|
|
149
|
+
bank.mint.toString(), bank.config.oracleKeys[0].toString(),
|
|
150
|
+
]));
|
|
151
|
+
}
|
|
138
152
|
async function addSwbOraclePullTxs(client, txs) {
|
|
153
|
+
if (!txs.some((tx) => tx.oracleInteractor)) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
const feedIds = client.lendingPlatform === generated_1.LendingPlatform.Marginfi
|
|
157
|
+
? getMarginfiSwitchboardFeeds(await (0, marginfi_1.fetchAllBank)(client.umi, [
|
|
158
|
+
(0, umi_1.publicKey)(client.pos.lpSupplyAccount),
|
|
159
|
+
(0, umi_1.publicKey)(client.pos.lpDebtAccount),
|
|
160
|
+
]))
|
|
161
|
+
: Object.fromEntries(Object.entries(constants_1.SWITCHBOARD_PRICE_FEED_IDS).map(([mint, feed]) => [mint, feed.feedId]));
|
|
139
162
|
const switchboardMints = [
|
|
140
|
-
...(
|
|
163
|
+
...(feedIds[client.pos.supplyMint.toString()]
|
|
141
164
|
? [client.pos.supplyMint]
|
|
142
165
|
: []),
|
|
143
|
-
...(
|
|
166
|
+
...(feedIds[client.pos.debtMint.toString()] ? [client.pos.debtMint] : []),
|
|
144
167
|
];
|
|
145
|
-
if (
|
|
168
|
+
if (switchboardMints.length) {
|
|
146
169
|
(0, generalUtils_1.consoleLog)("Checking if oracle update(s) needed...");
|
|
147
|
-
const staleOracles = (await getSwitchboardFeedData(client.connection, switchboardMints)).filter((x) => x.stale).length > 0;
|
|
170
|
+
const staleOracles = (await getSwitchboardFeedData(client.connection, switchboardMints, feedIds)).filter((x) => x.stale).length > 0;
|
|
148
171
|
if (staleOracles) {
|
|
149
172
|
(0, generalUtils_1.consoleLog)("Requires oracle update(s)...");
|
|
150
|
-
const oracleTxs = switchboardMints.map((x) => new services_1.TransactionItem(async () => await buildSwbSubmitResponseTx(client.connection, client.signer, x), constants_1.UPDATE_ORACLE_TX_NAME));
|
|
173
|
+
const oracleTxs = switchboardMints.map((x) => new services_1.TransactionItem(async () => await buildSwbSubmitResponseTx(client.connection, client.signer, x, feedIds[x.toString()]), constants_1.UPDATE_ORACLE_TX_NAME));
|
|
151
174
|
(0, generalUtils_1.consoleLog)("Set crank IXs in TX");
|
|
152
175
|
txs.unshift(...oracleTxs);
|
|
153
176
|
}
|
package/local/txSandbox.ts
CHANGED
|
@@ -120,15 +120,15 @@ async function refreshAll() {
|
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
// Crank a Switchboard pull feed for a single mint, as one standalone tx (no bundle).
|
|
123
|
-
// Usage:
|
|
124
|
-
// mint defaults to
|
|
123
|
+
// Usage: pnpm sandbox [mint]
|
|
124
|
+
// mint defaults to JitoSOL; accepts a raw mint address or a shorthand below.
|
|
125
125
|
const MINT_SHORTHANDS: Record<string, string> = {
|
|
126
126
|
sol: NATIVE_MINT.toString(),
|
|
127
127
|
jitosol: JITO_SOL,
|
|
128
128
|
};
|
|
129
129
|
|
|
130
130
|
function resolveMint(arg?: string): PublicKey {
|
|
131
|
-
if (!arg) return
|
|
131
|
+
if (!arg) return new PublicKey(JITO_SOL);
|
|
132
132
|
const shorthand = MINT_SHORTHANDS[arg.toLowerCase()];
|
|
133
133
|
return new PublicKey(shorthand ?? arg);
|
|
134
134
|
}
|
package/package.json
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { NATIVE_MINT } from "@solana/spl-token";
|
|
2
1
|
import * as tokens from "./tokenConstants";
|
|
3
2
|
|
|
4
3
|
interface SwitchboardFeed {
|
|
@@ -7,33 +6,9 @@ interface SwitchboardFeed {
|
|
|
7
6
|
}
|
|
8
7
|
|
|
9
8
|
// https://beta.ondemand.switchboard.xyz/solana/mainnet
|
|
9
|
+
// SOL, JitoSOL, JTO, LST, hSOL and JupSOL banks have migrated to Pyth.
|
|
10
|
+
// Marginfi refreshes must still select the feed from the bank's live configuration.
|
|
10
11
|
export const SWITCHBOARD_PRICE_FEED_IDS: { [key: string]: SwitchboardFeed } = {
|
|
11
|
-
[NATIVE_MINT.toString()]: {
|
|
12
|
-
feedId: "4Hmd6PdjVA9auCoScE12iaBogfwS4ZXQ6VZoBeqanwWW",
|
|
13
|
-
feedHash: "0x19089ab8a78c2d6e1b0b17ed50db7d99d7a05260160dfdf727746f656e879539",
|
|
14
|
-
},
|
|
15
|
-
[tokens.JUP_SOL]: {
|
|
16
|
-
feedId: "BRCWKZ1PevwTFzBL2MLZM5hMwCNMWE8NYuS6zFPaXZ6y",
|
|
17
|
-
feedHash:
|
|
18
|
-
"0xd126e3a968e310748ad925d7d728f3e779121b0c26b7d1a3d061cc2da3093005",
|
|
19
|
-
},
|
|
20
|
-
[tokens.JITO_SOL]: {
|
|
21
|
-
feedId: "5htZ4vPKPjAEg8EJv6JHcaCetMM4XehZo8znQvrp6Ur3",
|
|
22
|
-
feedHash: "0x44f5a7676baf4c6e5eb26fdde2695e7e3e0971071b76ddc622fa71528092ca25"
|
|
23
|
-
},
|
|
24
|
-
[tokens.LST]: {
|
|
25
|
-
feedId: "BWK8Wnybb7rPteNMqJs9uWoqdfYApNym6WgE59BwLe1v",
|
|
26
|
-
feedHash: "0x74e140c452fe29cced80898a51961ed1410c1fe0efbdc970fe5aac986427419e"
|
|
27
|
-
},
|
|
28
|
-
[tokens.JTO]: {
|
|
29
|
-
feedId: "V9o7SWeCFpGpKbWwVQD9x5RnpUEMh8sUAwqi1XnCReu",
|
|
30
|
-
feedHash: "0x1ac1e88ed90c78e2514aa346ba1c90807d12524089920348f29b725832f4e070"
|
|
31
|
-
},
|
|
32
|
-
[tokens.H_SOL]: {
|
|
33
|
-
feedId: "3zCUYzDhuyZREWgngzJfUt2UzQ5SzKCUK5AdwiB2HYmU",
|
|
34
|
-
feedHash:
|
|
35
|
-
"0x1b8b079fe96e1749f6418405e91407790689623dee8dc123ae26eb833c9d9bd5",
|
|
36
|
-
},
|
|
37
12
|
[tokens.POPCAT]: {
|
|
38
13
|
feedId: "FTcFqwCjAgv2VMaowQ9XSBcesVzFzZkaju25nKio6bki",
|
|
39
14
|
feedHash:
|
|
@@ -15,6 +15,27 @@ export enum OracleSetup {
|
|
|
15
15
|
PythPushOracle,
|
|
16
16
|
SwitchboardPull,
|
|
17
17
|
StakedWithPythPush,
|
|
18
|
+
KaminoPythPush,
|
|
19
|
+
KaminoSwitchboardPull,
|
|
20
|
+
Fixed,
|
|
21
|
+
DriftPythPull,
|
|
22
|
+
DriftSwitchboardPull,
|
|
23
|
+
SolendPythPull,
|
|
24
|
+
SolendSwitchboardPull,
|
|
25
|
+
FixedKamino,
|
|
26
|
+
FixedDrift,
|
|
27
|
+
JuplendPythPull,
|
|
28
|
+
JuplendSwitchboardPull,
|
|
29
|
+
FixedJuplend,
|
|
30
|
+
Scope,
|
|
31
|
+
PythMSOL,
|
|
32
|
+
KaminoMSOL,
|
|
33
|
+
JuplendMSOL,
|
|
34
|
+
PythLST,
|
|
35
|
+
KaminoLST,
|
|
36
|
+
JuplendLST,
|
|
37
|
+
PTPyth,
|
|
38
|
+
PTFixed,
|
|
18
39
|
}
|
|
19
40
|
|
|
20
41
|
export type OracleSetupArgs = OracleSetup;
|
|
@@ -30,8 +30,8 @@ import {
|
|
|
30
30
|
fromBaseUnit,
|
|
31
31
|
getBankLiquidityAvailableBaseUnit,
|
|
32
32
|
getEmptyMarginfiAccountsByAuthority,
|
|
33
|
-
getAccountMeta,
|
|
34
33
|
getMarginfiPriceOracle,
|
|
34
|
+
getMarginfiBankHealthAccounts,
|
|
35
35
|
getRemainingAccountsForMarginfiHealthCheck,
|
|
36
36
|
getTokenAccount,
|
|
37
37
|
rpcAccountCreated,
|
|
@@ -129,16 +129,15 @@ export class MarginfiFlProvider extends FlProviderBase {
|
|
|
129
129
|
data: this.debtBankLiquiditySource,
|
|
130
130
|
}),
|
|
131
131
|
]);
|
|
132
|
-
this.supplyBankHealthAccounts =
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
];
|
|
132
|
+
[this.supplyBankHealthAccounts, this.debtBankHealthAccounts] =
|
|
133
|
+
await Promise.all([
|
|
134
|
+
getMarginfiBankHealthAccounts(
|
|
135
|
+
this.umi, this.supplyBankLiquiditySource, supplyPriceOracle
|
|
136
|
+
),
|
|
137
|
+
getMarginfiBankHealthAccounts(
|
|
138
|
+
this.umi, this.debtBankLiquiditySource, debtPriceOracle
|
|
139
|
+
),
|
|
140
|
+
]);
|
|
142
141
|
}
|
|
143
142
|
|
|
144
143
|
private async setIntermediaryAccount(sources: TokenType[]) {
|
|
@@ -38,6 +38,8 @@ import {
|
|
|
38
38
|
getRemainingAccountsForMarginfiHealthCheck,
|
|
39
39
|
getAccountMeta,
|
|
40
40
|
composeRemainingAccounts,
|
|
41
|
+
getMarginfiBankHealthAccounts,
|
|
42
|
+
getMarginfiAuxiliaryOracleAccounts,
|
|
41
43
|
} from "../../utils";
|
|
42
44
|
import {
|
|
43
45
|
Bank,
|
|
@@ -48,6 +50,7 @@ import {
|
|
|
48
50
|
lendingAccountWithdraw,
|
|
49
51
|
marginfiAccountInitialize,
|
|
50
52
|
safeFetchAllMarginfiAccount,
|
|
53
|
+
fetchAllBank,
|
|
51
54
|
} from "../../externalSdks/marginfi";
|
|
52
55
|
import { SolautoClient, SolautoClientArgs } from "./solautoClient";
|
|
53
56
|
|
|
@@ -70,6 +73,9 @@ export class SolautoMarginfiClient extends SolautoClient {
|
|
|
70
73
|
|
|
71
74
|
public supplyPriceOracle!: PublicKey;
|
|
72
75
|
public debtPriceOracle!: PublicKey;
|
|
76
|
+
private supplyBankHealthAccounts!: AccountMeta[];
|
|
77
|
+
private debtBankHealthAccounts!: AccountMeta[];
|
|
78
|
+
private auxiliaryOracleAccounts: AccountMeta[] = [];
|
|
73
79
|
|
|
74
80
|
async initialize(args: SolautoClientArgs) {
|
|
75
81
|
await super.initialize(args);
|
|
@@ -144,6 +150,22 @@ export class SolautoMarginfiClient extends SolautoClient {
|
|
|
144
150
|
[this.supplyPriceOracle, this.debtPriceOracle] =
|
|
145
151
|
await this.pos.priceOracles();
|
|
146
152
|
|
|
153
|
+
const banks = await fetchAllBank(this.umi, [
|
|
154
|
+
publicKey(this.marginfiSupplyAccounts.bank),
|
|
155
|
+
publicKey(this.marginfiDebtAccounts.bank),
|
|
156
|
+
]);
|
|
157
|
+
[this.supplyBankHealthAccounts, this.debtBankHealthAccounts] =
|
|
158
|
+
await Promise.all([
|
|
159
|
+
getMarginfiBankHealthAccounts(this.umi, banks[0], this.supplyPriceOracle),
|
|
160
|
+
getMarginfiBankHealthAccounts(this.umi, banks[1], this.debtPriceOracle),
|
|
161
|
+
]);
|
|
162
|
+
this.auxiliaryOracleAccounts = Array.from(
|
|
163
|
+
new Set(
|
|
164
|
+
banks.flatMap(getMarginfiAuxiliaryOracleAccounts)
|
|
165
|
+
.map((key) => key.toString())
|
|
166
|
+
)
|
|
167
|
+
).map((key) => getAccountMeta(new PublicKey(key)));
|
|
168
|
+
|
|
147
169
|
this.log("Marginfi account:", this.marginfiAccountPk.toString());
|
|
148
170
|
this.log("Supply price oracle:", this.supplyPriceOracle.toString());
|
|
149
171
|
this.log("Debt price oracle:", this.debtPriceOracle.toString());
|
|
@@ -157,7 +179,13 @@ export class SolautoMarginfiClient extends SolautoClient {
|
|
|
157
179
|
}
|
|
158
180
|
|
|
159
181
|
lutAccountsToAdd(): PublicKey[] {
|
|
160
|
-
return [
|
|
182
|
+
return [
|
|
183
|
+
...super.lutAccountsToAdd(),
|
|
184
|
+
this.marginfiAccountPk,
|
|
185
|
+
...this.auxiliaryOracleAccounts.map((account) =>
|
|
186
|
+
toWeb3JsPublicKey(account.pubkey)
|
|
187
|
+
),
|
|
188
|
+
];
|
|
161
189
|
}
|
|
162
190
|
|
|
163
191
|
marginfiAccountInitialize(marginfiAccount: Signer): TransactionBuilder {
|
|
@@ -244,7 +272,7 @@ export class SolautoMarginfiClient extends SolautoClient {
|
|
|
244
272
|
debtPriceOracle: publicKey(this.debtPriceOracle),
|
|
245
273
|
solautoPosition: publicKey(this.pos.publicKey),
|
|
246
274
|
priceType: priceType ?? PriceType.Realtime,
|
|
247
|
-
});
|
|
275
|
+
}).addRemainingAccounts(this.auxiliaryOracleAccounts);
|
|
248
276
|
}
|
|
249
277
|
|
|
250
278
|
protocolInteractionIx(args: SolautoActionArgs): TransactionBuilder {
|
|
@@ -266,10 +294,7 @@ export class SolautoMarginfiClient extends SolautoClient {
|
|
|
266
294
|
.includes(this.marginfiSupplyAccounts.bank)
|
|
267
295
|
) {
|
|
268
296
|
this.healthCheckRemainingAccounts.push(
|
|
269
|
-
...
|
|
270
|
-
getAccountMeta(new PublicKey(this.marginfiSupplyAccounts.bank)),
|
|
271
|
-
getAccountMeta(this.supplyPriceOracle),
|
|
272
|
-
]
|
|
297
|
+
...this.supplyBankHealthAccounts
|
|
273
298
|
);
|
|
274
299
|
}
|
|
275
300
|
return lendingAccountDeposit(this.umi, {
|
|
@@ -294,10 +319,7 @@ export class SolautoMarginfiClient extends SolautoClient {
|
|
|
294
319
|
)
|
|
295
320
|
) {
|
|
296
321
|
remainingAccounts.push(
|
|
297
|
-
...
|
|
298
|
-
getAccountMeta(new PublicKey(this.marginfiDebtAccounts.bank)),
|
|
299
|
-
getAccountMeta(this.debtPriceOracle),
|
|
300
|
-
]
|
|
322
|
+
...this.debtBankHealthAccounts
|
|
301
323
|
);
|
|
302
324
|
}
|
|
303
325
|
|
|
@@ -409,7 +431,7 @@ export class SolautoMarginfiClient extends SolautoClient {
|
|
|
409
431
|
vaultDebtTa,
|
|
410
432
|
debtVaultAuthority,
|
|
411
433
|
solautoAction: args,
|
|
412
|
-
});
|
|
434
|
+
}).addRemainingAccounts(this.auxiliaryOracleAccounts);
|
|
413
435
|
}
|
|
414
436
|
|
|
415
437
|
rebalanceIx(
|
|
@@ -489,6 +511,6 @@ export class SolautoMarginfiClient extends SolautoClient {
|
|
|
489
511
|
data.flashLoan?.flFeeBps && isFirstRebalance
|
|
490
512
|
? data.flashLoan.flFeeBps
|
|
491
513
|
: null,
|
|
492
|
-
});
|
|
514
|
+
}).addRemainingAccounts(this.auxiliaryOracleAccounts);
|
|
493
515
|
}
|
|
494
516
|
}
|
|
@@ -531,7 +531,10 @@ export class TransactionsManager<T extends TxHandler> {
|
|
|
531
531
|
);
|
|
532
532
|
itemSet = refreshedSets ? refreshedSets[0] : undefined;
|
|
533
533
|
}
|
|
534
|
-
if (
|
|
534
|
+
if (
|
|
535
|
+
!itemSet ||
|
|
536
|
+
!this.shouldProceedToSend(itemSets.slice(currentIndex), attemptNum)
|
|
537
|
+
) {
|
|
535
538
|
return;
|
|
536
539
|
}
|
|
537
540
|
|
|
@@ -3,7 +3,6 @@ import { Bank, PriceBias, safeFetchAllBank } from "../externalSdks/marginfi";
|
|
|
3
3
|
import { publicKey } from "@metaplex-foundation/umi";
|
|
4
4
|
import {
|
|
5
5
|
calcMarginfiMaxLtvAndLiqThresholdBps,
|
|
6
|
-
fetchTokenPrices,
|
|
7
6
|
fromBaseUnit,
|
|
8
7
|
getBankLiquidityAvailableBaseUnit,
|
|
9
8
|
getBankLiquidityUsedBaseUnit,
|
|
@@ -13,6 +12,8 @@ import {
|
|
|
13
12
|
tokenInfo,
|
|
14
13
|
toRoundedUsdValue,
|
|
15
14
|
validPubkey,
|
|
15
|
+
getMarginfiBankPrice,
|
|
16
|
+
safeGetMarginfiBankPrice,
|
|
16
17
|
} from "../utils";
|
|
17
18
|
import { getMarginfiAccounts } from "../constants";
|
|
18
19
|
import { SolautoPositionEx } from "./solautoPositionEx";
|
|
@@ -31,6 +32,11 @@ export class MarginfiSolautoPositionEx extends SolautoPositionEx {
|
|
|
31
32
|
): number | undefined {
|
|
32
33
|
return (
|
|
33
34
|
this._supplyPrice ??
|
|
35
|
+
safeGetMarginfiBankPrice(
|
|
36
|
+
this.supplyBank,
|
|
37
|
+
priceType,
|
|
38
|
+
!withoutBias ? PriceBias.Low : undefined
|
|
39
|
+
) ??
|
|
34
40
|
safeGetPrice(
|
|
35
41
|
this.supplyMint,
|
|
36
42
|
priceType,
|
|
@@ -42,6 +48,11 @@ export class MarginfiSolautoPositionEx extends SolautoPositionEx {
|
|
|
42
48
|
debtPrice(priceType?: PriceType, withoutBias?: boolean): number | undefined {
|
|
43
49
|
return (
|
|
44
50
|
this._debtPrice ??
|
|
51
|
+
safeGetMarginfiBankPrice(
|
|
52
|
+
this.debtBank,
|
|
53
|
+
priceType,
|
|
54
|
+
!withoutBias ? PriceBias.High : undefined
|
|
55
|
+
) ??
|
|
45
56
|
safeGetPrice(
|
|
46
57
|
this.debtMint,
|
|
47
58
|
priceType,
|
|
@@ -79,7 +90,7 @@ export class MarginfiSolautoPositionEx extends SolautoPositionEx {
|
|
|
79
90
|
async maxLtvAndLiqThresholdBps(): Promise<[number, number]> {
|
|
80
91
|
const [supplyBank, debtBank] = await this.getBanks();
|
|
81
92
|
|
|
82
|
-
const
|
|
93
|
+
const supplyPrice = await getMarginfiBankPrice(this.umi, supplyBank);
|
|
83
94
|
const [maxLtvBps, liqThresholdBps] = calcMarginfiMaxLtvAndLiqThresholdBps(
|
|
84
95
|
supplyBank,
|
|
85
96
|
debtBank,
|
|
@@ -108,7 +119,7 @@ export class MarginfiSolautoPositionEx extends SolautoPositionEx {
|
|
|
108
119
|
baseUnit: baseUnit,
|
|
109
120
|
baseAmountUsdValue: toRoundedUsdValue(
|
|
110
121
|
fromBaseUnit(baseUnit, tokenInfo(mint).decimals) *
|
|
111
|
-
(safeGetPrice(mint) ?? 0)
|
|
122
|
+
(safeGetMarginfiBankPrice(bank ?? null) ?? safeGetPrice(mint) ?? 0)
|
|
112
123
|
),
|
|
113
124
|
};
|
|
114
125
|
}
|