@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.
Files changed (45) hide show
  1. package/dist/constants/switchboardConstants.d.ts.map +1 -1
  2. package/dist/constants/switchboardConstants.js +2 -25
  3. package/dist/externalSdks/marginfi/types/oracleSetup.d.ts +22 -1
  4. package/dist/externalSdks/marginfi/types/oracleSetup.d.ts.map +1 -1
  5. package/dist/externalSdks/marginfi/types/oracleSetup.js +21 -0
  6. package/dist/services/flashLoans/marginfiFlProvider.d.ts.map +1 -1
  7. package/dist/services/flashLoans/marginfiFlProvider.js +5 -8
  8. package/dist/services/solauto/solautoMarginfiClient.d.ts +3 -0
  9. package/dist/services/solauto/solautoMarginfiClient.d.ts.map +1 -1
  10. package/dist/services/solauto/solautoMarginfiClient.js +22 -12
  11. package/dist/services/transactions/manager/transactionsManager.d.ts.map +1 -1
  12. package/dist/services/transactions/manager/transactionsManager.js +2 -1
  13. package/dist/solautoPosition/marginfiSolautoPositionEx.d.ts.map +1 -1
  14. package/dist/solautoPosition/marginfiSolautoPositionEx.js +4 -2
  15. package/dist/utils/marginfi/data.d.ts.map +1 -1
  16. package/dist/utils/marginfi/data.js +9 -15
  17. package/dist/utils/marginfi/general.d.ts +5 -0
  18. package/dist/utils/marginfi/general.d.ts.map +1 -1
  19. package/dist/utils/marginfi/general.js +36 -8
  20. package/dist/utils/marginfi/index.d.ts +1 -0
  21. package/dist/utils/marginfi/index.d.ts.map +1 -1
  22. package/dist/utils/marginfi/index.js +1 -0
  23. package/dist/utils/marginfi/prices.d.ts +14 -0
  24. package/dist/utils/marginfi/prices.d.ts.map +1 -0
  25. package/dist/utils/marginfi/prices.js +119 -0
  26. package/dist/utils/switchboardUtils.d.ts +5 -3
  27. package/dist/utils/switchboardUtils.d.ts.map +1 -1
  28. package/dist/utils/switchboardUtils.js +35 -12
  29. package/local/txSandbox.ts +3 -3
  30. package/package.json +1 -1
  31. package/src/constants/switchboardConstants.ts +2 -27
  32. package/src/externalSdks/marginfi/types/oracleSetup.ts +21 -0
  33. package/src/services/flashLoans/marginfiFlProvider.ts +10 -11
  34. package/src/services/solauto/solautoMarginfiClient.ts +34 -12
  35. package/src/services/transactions/manager/transactionsManager.ts +4 -1
  36. package/src/solautoPosition/marginfiSolautoPositionEx.ts +14 -3
  37. package/src/utils/marginfi/data.ts +16 -16
  38. package/src/utils/marginfi/general.ts +46 -12
  39. package/src/utils/marginfi/index.ts +2 -1
  40. package/src/utils/marginfi/prices.ts +165 -0
  41. package/src/utils/switchboardUtils.ts +55 -17
  42. package/tests/fixtures/README.md +17 -0
  43. package/tests/fixtures/marginfiOracles.json +82 -0
  44. package/tests/unit/marginfiOracles.ts +333 -0
  45. 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
- export declare function getPullFeed(conn: Connection, mint: PublicKey, wallet?: PublicKey): Promise<{
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,EAAE,MAAM,EAAsB,MAAM,0BAA0B,CAAC;AAGtE,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;AAE7D,wBAAsB,WAAW,CAC/B,IAAI,EAAE,UAAU,EAChB,IAAI,EAAE,SAAS,EACf,MAAM,CAAC,EAAE,SAAS;;;GAgCnB;AAED,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,UAAU,EAChB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,SAAS,GACd,OAAO,CAAC,qBAAqB,GAAG,SAAS,CAAC,CAyE5C;AAED,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,UAAU,EAChB,KAAK,EAAE,SAAS,EAAE,GACjB,OAAO,CAAC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,EAAE,CAAC,CAuB/D;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,SAAS,GAAG,MAAM,WAEzD;AAED,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,aAAa,EACrB,GAAG,EAAE,eAAe,EAAE,iBAkCvB"}
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
- async function getPullFeed(conn, mint, wallet) {
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:", constants_1.SWITCHBOARD_PRICE_FEED_IDS[mint.toString()].feedId);
70
+ (0, generalUtils_1.consoleLog)("Feed id:", address);
64
71
  return {
65
72
  crossbar,
66
- feed: new PullFeed(sbProgram, new web3_js_1.PublicKey(constants_1.SWITCHBOARD_PRICE_FEED_IDS[mint.toString()].feedId)),
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
- ...(isSwitchboardMint(client.pos.supplyMint)
163
+ ...(feedIds[client.pos.supplyMint.toString()]
141
164
  ? [client.pos.supplyMint]
142
165
  : []),
143
- ...(isSwitchboardMint(client.pos.debtMint) ? [client.pos.debtMint] : []),
166
+ ...(feedIds[client.pos.debtMint.toString()] ? [client.pos.debtMint] : []),
144
167
  ];
145
- if (txs.find((x) => x.oracleInteractor) && switchboardMints.length) {
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
  }
@@ -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: npx ts-node local/txSandbox.ts [mint]
124
- // mint defaults to SOL; accepts a raw mint address or a shorthand below.
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 NATIVE_MINT;
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,6 +1,6 @@
1
1
  {
2
2
  "name": "@haven-fi/solauto-sdk",
3
- "version": "1.0.835",
3
+ "version": "1.0.837",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "description": "Typescript SDK for the Solauto program on the Solana blockchain",
@@ -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
- getAccountMeta(
134
- toWeb3JsPublicKey(this.supplyBankLiquiditySource.publicKey)
135
- ),
136
- getAccountMeta(supplyPriceOracle),
137
- ];
138
- this.debtBankHealthAccounts = [
139
- getAccountMeta(toWeb3JsPublicKey(this.debtBankLiquiditySource.publicKey)),
140
- getAccountMeta(debtPriceOracle),
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 [...super.lutAccountsToAdd(), this.marginfiAccountPk];
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 (!itemSet || !this.shouldProceedToSend([itemSet], attemptNum)) {
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 [supplyPrice] = await fetchTokenPrices([this.supplyMint]);
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
  }