@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,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
|
}
|
|
@@ -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
|
}
|
|
@@ -18,7 +18,6 @@ import {
|
|
|
18
18
|
safeFetchMarginfiAccount,
|
|
19
19
|
} from "../../externalSdks/marginfi";
|
|
20
20
|
import { ContextUpdates } from "../solautoUtils";
|
|
21
|
-
import { fetchTokenPrices, safeGetPrice } from "../priceUtils";
|
|
22
21
|
import { currentUnixSeconds, validPubkey } from "../generalUtils";
|
|
23
22
|
import {
|
|
24
23
|
bytesToI80F48,
|
|
@@ -32,6 +31,7 @@ import {
|
|
|
32
31
|
calcMarginfiMaxLtvAndLiqThresholdBps,
|
|
33
32
|
marginfiAccountEmpty,
|
|
34
33
|
} from "./general";
|
|
34
|
+
import { getMarginfiBankPrice } from "./prices";
|
|
35
35
|
|
|
36
36
|
export async function getMarginfiMaxLtvAndLiqThresholdBps(
|
|
37
37
|
umi: Umi,
|
|
@@ -76,10 +76,7 @@ export async function getMarginfiMaxLtvAndLiqThresholdBps(
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
if (!supplyPrice) {
|
|
79
|
-
|
|
80
|
-
toWeb3JsPublicKey(supply.bank!.mint),
|
|
81
|
-
]);
|
|
82
|
-
supplyPrice = price;
|
|
79
|
+
supplyPrice = await getMarginfiBankPrice(umi, supply.bank!);
|
|
83
80
|
}
|
|
84
81
|
|
|
85
82
|
if (!debt.bank || debt.bank === null) {
|
|
@@ -239,6 +236,7 @@ export function getBankLiquidityUsedBaseUnit(
|
|
|
239
236
|
}
|
|
240
237
|
|
|
241
238
|
async function getTokenUsage(
|
|
239
|
+
umi: Umi,
|
|
242
240
|
bank: Bank | null,
|
|
243
241
|
isAsset: boolean,
|
|
244
242
|
shares: number,
|
|
@@ -251,10 +249,7 @@ async function getTokenUsage(
|
|
|
251
249
|
let originationFee = 0;
|
|
252
250
|
|
|
253
251
|
if (bank !== null) {
|
|
254
|
-
|
|
255
|
-
[toWeb3JsPublicKey(bank.mint)],
|
|
256
|
-
priceType
|
|
257
|
-
);
|
|
252
|
+
marketPrice = await getMarginfiBankPrice(umi, bank, priceType);
|
|
258
253
|
const [assetShareValue, liabilityShareValue] = getUpToDateShareValues(bank);
|
|
259
254
|
const shareValue = isAsset ? assetShareValue : liabilityShareValue;
|
|
260
255
|
amountUsed = shares * shareValue + Number(amountUsedAdjustment ?? 0);
|
|
@@ -365,10 +360,6 @@ export async function getMarginfiAccountPositionState(
|
|
|
365
360
|
let supplyUsage: PositionTokenState | undefined = undefined;
|
|
366
361
|
let debtUsage: PositionTokenState | undefined = undefined;
|
|
367
362
|
|
|
368
|
-
if (supply.mint && debt.mint) {
|
|
369
|
-
await fetchTokenPrices([supply.mint, debt.mint]);
|
|
370
|
-
}
|
|
371
|
-
|
|
372
363
|
if (
|
|
373
364
|
marginfiAccount !== null &&
|
|
374
365
|
marginfiAccount.lendingAccount.balances.filter((x) => x.active).length > 0
|
|
@@ -397,6 +388,7 @@ export async function getMarginfiAccountPositionState(
|
|
|
397
388
|
supply.mint = toWeb3JsPublicKey(supplyBank!.mint);
|
|
398
389
|
}
|
|
399
390
|
supplyUsage = await getTokenUsage(
|
|
391
|
+
umi,
|
|
400
392
|
supplyBank!,
|
|
401
393
|
true,
|
|
402
394
|
bytesToI80F48(supplyBalances[0].assetShares.value),
|
|
@@ -415,6 +407,7 @@ export async function getMarginfiAccountPositionState(
|
|
|
415
407
|
debt.mint = toWeb3JsPublicKey(debtBank!.mint);
|
|
416
408
|
}
|
|
417
409
|
debtUsage = await getTokenUsage(
|
|
410
|
+
umi,
|
|
418
411
|
debtBank!,
|
|
419
412
|
false,
|
|
420
413
|
bytesToI80F48(debtBalances[0].liabilityShares.value),
|
|
@@ -430,10 +423,12 @@ export async function getMarginfiAccountPositionState(
|
|
|
430
423
|
|
|
431
424
|
if (!supplyUsage) {
|
|
432
425
|
supplyUsage = await getTokenUsage(
|
|
426
|
+
umi,
|
|
433
427
|
supplyBank,
|
|
434
428
|
true,
|
|
435
429
|
0,
|
|
436
|
-
contextUpdates?.supplyAdjustment
|
|
430
|
+
contextUpdates?.supplyAdjustment,
|
|
431
|
+
priceType
|
|
437
432
|
);
|
|
438
433
|
}
|
|
439
434
|
|
|
@@ -458,17 +453,22 @@ export async function getMarginfiAccountPositionState(
|
|
|
458
453
|
|
|
459
454
|
if (!debtUsage) {
|
|
460
455
|
debtUsage = await getTokenUsage(
|
|
456
|
+
umi,
|
|
461
457
|
debtBank,
|
|
462
458
|
false,
|
|
463
459
|
0,
|
|
464
|
-
contextUpdates?.debtAdjustment
|
|
460
|
+
contextUpdates?.debtAdjustment,
|
|
461
|
+
priceType
|
|
465
462
|
);
|
|
466
463
|
}
|
|
467
464
|
|
|
468
465
|
if (!marginfiGroup) {
|
|
469
466
|
marginfiGroup = toWeb3JsPublicKey(supplyBank.group);
|
|
470
467
|
}
|
|
471
|
-
const supplyPrice =
|
|
468
|
+
const supplyPrice = fromBaseUnit(
|
|
469
|
+
supplyUsage.baseAmountMarketPriceUsd,
|
|
470
|
+
USD_DECIMALS
|
|
471
|
+
);
|
|
472
472
|
let [maxLtvBps, liqThresholdBps] = await getMarginfiMaxLtvAndLiqThresholdBps(
|
|
473
473
|
umi,
|
|
474
474
|
marginfiGroup,
|