From b2858be375bd6906cba5cb23eb5fc29b7109702b Mon Sep 17 00:00:00 2001 From: chambaz Date: Fri, 21 Feb 2025 21:14:08 -0500 Subject: [PATCH 1/6] fix: store account cache in tools root / update warning msg --- packages/tools/accounts/cache-accounts.ts | 2 +- packages/tools/lib/utils.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/tools/accounts/cache-accounts.ts b/packages/tools/accounts/cache-accounts.ts index 421f0200a..5f4f3eb5b 100644 --- a/packages/tools/accounts/cache-accounts.ts +++ b/packages/tools/accounts/cache-accounts.ts @@ -15,7 +15,7 @@ type AccountCache = { accounts: string[]; }; -const CACHE_FILE = path.join(__dirname, "./account-cache.json"); +const CACHE_FILE = path.join(__dirname, "../account-cache.json"); async function main() { const argv = getDefaultYargsOptions().parseSync(); diff --git a/packages/tools/lib/utils.ts b/packages/tools/lib/utils.ts index f38c26d29..971bda86c 100644 --- a/packages/tools/lib/utils.ts +++ b/packages/tools/lib/utils.ts @@ -23,7 +23,7 @@ export function getCachedAccounts(): PublicKey[] { const CACHE_FILE = path.join(__dirname, "../account-cache.json"); if (!fs.existsSync(CACHE_FILE)) { - throw new Error("Account cache not found. Please run 'yarn account:cache-all' first."); + throw new Error("Account cache not found. Please run 'pnpm accounts:cache' first."); } const cache: AccountCache = JSON.parse(fs.readFileSync(CACHE_FILE, "utf-8")); From 68734878d3197f84887fab63df778c1abc804f41 Mon Sep 17 00:00:00 2001 From: chambaz Date: Fri, 21 Feb 2025 21:42:56 -0500 Subject: [PATCH 2/6] chore: install dotenv --- packages/tools/package.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/tools/package.json b/packages/tools/package.json index 585b79e67..a91be308d 100644 --- a/packages/tools/package.json +++ b/packages/tools/package.json @@ -27,19 +27,20 @@ "@solana/web3.js": "^1.91.3", "bignumber.js": "^9.1.2", "decimal.js": "^10.4.3", + "dotenv": "^16.4.7", "mocha": "^10.2.0", "ts-mocha": "^10.0.0", "tsx": "^4.19.2" }, "devDependencies": { "@mrgnlabs/tsconfig": "workspace:*", + "@switchboard-xyz/on-demand": "^1.2.54", "@types/bn.js": "^5.1.0", "@types/chai": "^4.3.0", "@types/mocha": "^9.0.0", "big.js": "^6.2.1", "chai": "^4.3.4", "ts-node": "^10.9.1", - "typescript": "^4.3.5", - "@switchboard-xyz/on-demand": "^1.2.54" + "typescript": "^4.3.5" } } From 6d993b1d7c79fc76739ce9b419f319620232c358 Mon Sep 17 00:00:00 2001 From: chambaz Date: Fri, 21 Feb 2025 21:43:30 -0500 Subject: [PATCH 3/6] feat: update bank data, add pyth oracles --- packages/tools/banks/get-bank.ts | 22 +++++++++++++++++++--- packages/tools/lib/constants.ts | 5 +++++ packages/tools/lib/utils.ts | 26 ++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 3 deletions(-) create mode 100644 packages/tools/lib/constants.ts diff --git a/packages/tools/banks/get-bank.ts b/packages/tools/banks/get-bank.ts index fb7c54c00..ee10c7251 100644 --- a/packages/tools/banks/get-bank.ts +++ b/packages/tools/banks/get-bank.ts @@ -3,7 +3,7 @@ import { PublicKey } from "@solana/web3.js"; import { wrappedI80F48toBigNumber } from "@mrgnlabs/mrgn-common"; import { getDefaultYargsOptions, getMarginfiProgram } from "../lib/config"; import { Environment } from "../lib/types"; -import { formatNumber } from "../lib/utils"; +import { formatNumber, getPythPushOracleAddresses } from "../lib/utils"; dotenv.config(); @@ -61,6 +61,11 @@ async function main() { }); const oraclePriceData = await oraclePriceResponse.json(); + const assetWeightInit = wrappedI80F48toBigNumber(acc.config.assetWeightInit); + const assetWeightMaint = wrappedI80F48toBigNumber(acc.config.assetWeightMaint); + const liabilityWeightInit = wrappedI80F48toBigNumber(acc.config.liabilityWeightInit); + const liabilityWeightMaint = wrappedI80F48toBigNumber(acc.config.liabilityWeightMaint); + const totalAssetShares = wrappedI80F48toBigNumber(acc.totalAssetShares); const totalLiabilityShares = wrappedI80F48toBigNumber(acc.totalLiabilityShares); const assetShareValue = wrappedI80F48toBigNumber(acc.assetShareValue); @@ -70,15 +75,26 @@ async function main() { const totalAssetQuantity = totalAssetShares.times(assetShareValue).div(scaleFactor); const totalLiabilityQuantity = totalLiabilityShares.times(liabilityShareValue).div(scaleFactor); + const oracleType = acc.config.oracleSetup.pythPushOracle !== undefined ? "Pyth" : "Switchboard"; + const oracleKeys = acc.config.oracleKeys.filter((key) => !key.equals(PublicKey.default)); + const pythOracleAddresses = oracleKeys.map((key) => getPythPushOracleAddresses(key.toBuffer())); + const bankData = { Address: bankPubkey.toString(), Mint: acc.mint.toString(), Symbol: bankMeta?.tokenSymbol, Decimals: acc.mintDecimals, Price: `$${formatNumber(Number(oraclePriceData[0].priceRealtime.price))}`, + "Oracle Type": oracleType, + "Oracle Keys": oracleKeys.join(", "), + ...(oracleType === "Pyth" ? { "Pyth Oracle Addresses": pythOracleAddresses.join(", ") } : {}), "Asset Tag": acc.config.assetTag, - "Asset Share Value": formatNumber(assetShareValue), - "Liability Share Value": formatNumber(liabilityShareValue), + "Asset Weight Init": assetWeightInit.toNumber(), + "Asset Weight Maint": assetWeightMaint.toNumber(), + "Liability Weight Init": liabilityWeightInit.toNumber(), + "Liability Weight Maint": liabilityWeightMaint.toNumber(), + "Asset Share Value": assetShareValue.toNumber(), + "Liability Share Value": liabilityShareValue.toNumber(), "Asset Quantity": formatNumber(totalAssetQuantity), "Asset Value (USD)": `$${formatNumber(totalAssetQuantity.times(oraclePriceData[0].priceRealtime.price))}`, "Liability Quantity": formatNumber(totalLiabilityQuantity), diff --git a/packages/tools/lib/constants.ts b/packages/tools/lib/constants.ts new file mode 100644 index 000000000..659046381 --- /dev/null +++ b/packages/tools/lib/constants.ts @@ -0,0 +1,5 @@ +import { PublicKey } from "@solana/web3.js"; + +export const PYTH_PUSH_ORACLE_ID = new PublicKey("pythWSnswVUd12oZpeFP8e9CVaEqJg25g1Vtc2biRsT"); +export const PYTH_SPONSORED_SHARD_ID = 0; +export const MARGINFI_SPONSORED_SHARD_ID = 3301; diff --git a/packages/tools/lib/utils.ts b/packages/tools/lib/utils.ts index 971bda86c..61d8fa8c5 100644 --- a/packages/tools/lib/utils.ts +++ b/packages/tools/lib/utils.ts @@ -5,6 +5,7 @@ import BigNumber from "bignumber.js"; import { Keypair, PublicKey } from "@solana/web3.js"; import { groupedNumberFormatterDyn } from "@mrgnlabs/mrgn-common"; import { AccountCache } from "./types"; +import { PYTH_PUSH_ORACLE_ID, PYTH_SPONSORED_SHARD_ID, MARGINFI_SPONSORED_SHARD_ID } from "./constants"; dotenv.config(); @@ -16,6 +17,7 @@ export function loadKeypairFromFile(filePath: string): Keypair { export function formatNumber(num: number | BigNumber): string { const value = typeof num === "number" ? new BigNumber(num) : num; if (value.eq(0)) return "0"; + if (value.lt(1)) return value.toString(); return groupedNumberFormatterDyn.format(value.toNumber()); } @@ -30,3 +32,27 @@ export function getCachedAccounts(): PublicKey[] { const accounts = cache.accounts.map((addr) => new PublicKey(addr)); return accounts.sort(() => Math.random() - 0.5); } + +function u16ToArrayBufferLE(value: number): Uint8Array { + // Create a buffer of 2 bytes + const buffer = new ArrayBuffer(2); + const dataView = new DataView(buffer); + + // Set the Uint16 value in little-endian order + dataView.setUint16(0, value, true); + + // Return the buffer + return new Uint8Array(buffer); +} + +function findPythPushOracleAddress(feedId: Buffer, programId: PublicKey, shardId: number): PublicKey { + const shardBytes = u16ToArrayBufferLE(shardId); + return PublicKey.findProgramAddressSync([shardBytes, feedId], programId)[0]; +} + +export function getPythPushOracleAddresses(feedId: Buffer): PublicKey[] { + return [ + findPythPushOracleAddress(feedId, PYTH_PUSH_ORACLE_ID, PYTH_SPONSORED_SHARD_ID), + findPythPushOracleAddress(feedId, PYTH_PUSH_ORACLE_ID, MARGINFI_SPONSORED_SHARD_ID), + ]; +} From fb3c4bc3077b6e0cf50e09976e9fcaa69b29019e Mon Sep 17 00:00:00 2001 From: chambaz Date: Tue, 25 Feb 2025 09:23:39 -0500 Subject: [PATCH 4/6] chore: dotenv dep lock --- pnpm-lock.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index be897ad43..d17fa4eb0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1556,6 +1556,9 @@ importers: decimal.js: specifier: ^10.4.3 version: 10.5.0 + dotenv: + specifier: ^16.4.7 + version: 16.4.7 mocha: specifier: ^10.2.0 version: 10.8.2 From dc4aa0413f580cbe6d8bba387fa5cf98c0fe875b Mon Sep 17 00:00:00 2001 From: chambaz Date: Tue, 25 Feb 2025 09:23:51 -0500 Subject: [PATCH 5/6] feat: add fees / interest rates to bank output --- packages/tools/banks/get-bank.ts | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/packages/tools/banks/get-bank.ts b/packages/tools/banks/get-bank.ts index ee10c7251..811049f34 100644 --- a/packages/tools/banks/get-bank.ts +++ b/packages/tools/banks/get-bank.ts @@ -71,6 +71,14 @@ async function main() { const assetShareValue = wrappedI80F48toBigNumber(acc.assetShareValue); const liabilityShareValue = wrappedI80F48toBigNumber(acc.liabilityShareValue); + const insuranceIrFee = wrappedI80F48toBigNumber(acc.config.interestRateConfig.insuranceIrFee); + const insiranceFixedFee = wrappedI80F48toBigNumber(acc.config.interestRateConfig.insuranceFeeFixedApr); + const protocolIrFee = wrappedI80F48toBigNumber(acc.config.interestRateConfig.protocolIrFee); + const protocolFixedFee = wrappedI80F48toBigNumber(acc.config.interestRateConfig.protocolFixedFeeApr); + const maxInterestRate = wrappedI80F48toBigNumber(acc.config.interestRateConfig.maxInterestRate); + const plateauInterestRate = wrappedI80F48toBigNumber(acc.config.interestRateConfig.plateauInterestRate); + const optimalUtilizationRate = wrappedI80F48toBigNumber(acc.config.interestRateConfig.optimalUtilizationRate); + const scaleFactor = Math.pow(10, acc.mintDecimals); const totalAssetQuantity = totalAssetShares.times(assetShareValue).div(scaleFactor); const totalLiabilityQuantity = totalLiabilityShares.times(liabilityShareValue).div(scaleFactor); @@ -88,11 +96,18 @@ async function main() { "Oracle Type": oracleType, "Oracle Keys": oracleKeys.join(", "), ...(oracleType === "Pyth" ? { "Pyth Oracle Addresses": pythOracleAddresses.join(", ") } : {}), - "Asset Tag": acc.config.assetTag, + "Bank Type": acc.config.assetTag === 2 ? "Native Stake" : acc.config.riskTier.collateral ? "Global" : "Isolated", "Asset Weight Init": assetWeightInit.toNumber(), "Asset Weight Maint": assetWeightMaint.toNumber(), "Liability Weight Init": liabilityWeightInit.toNumber(), "Liability Weight Maint": liabilityWeightMaint.toNumber(), + "Insurance IR Fee": insuranceIrFee.toNumber(), + "Insurance Fixed Fee": insiranceFixedFee.toNumber(), + "Protocol IR Fee": protocolIrFee.toNumber(), + "Protocol Fixed Fee": protocolFixedFee.toNumber(), + "Max Interest Rate": maxInterestRate.toNumber(), + "Plateau Interest Rate": plateauInterestRate.toNumber(), + "Optimal Utilization Rate": optimalUtilizationRate.toNumber(), "Asset Share Value": assetShareValue.toNumber(), "Liability Share Value": liabilityShareValue.toNumber(), "Asset Quantity": formatNumber(totalAssetQuantity), From 6b7dc9ab08944b33a5f4f909a2fabe55b828aa3b Mon Sep 17 00:00:00 2001 From: chambaz Date: Tue, 25 Feb 2025 09:35:03 -0500 Subject: [PATCH 6/6] feat: add tools support for native stake banks --- packages/tools/accounts/get-account.ts | 5 ++--- packages/tools/banks/get-bank-accounts.ts | 6 ++---- packages/tools/banks/get-bank.ts | 6 ++---- packages/tools/banks/get-banks.ts | 10 ++-------- packages/tools/lib/utils.ts | 16 ++++++++++++++++ 5 files changed, 24 insertions(+), 19 deletions(-) diff --git a/packages/tools/accounts/get-account.ts b/packages/tools/accounts/get-account.ts index 049ec56c9..0fec0d80e 100644 --- a/packages/tools/accounts/get-account.ts +++ b/packages/tools/accounts/get-account.ts @@ -3,7 +3,7 @@ import { PublicKey } from "@solana/web3.js"; import { wrappedI80F48toBigNumber } from "@mrgnlabs/mrgn-common"; import { getDefaultYargsOptions, getMarginfiProgram } from "../lib/config"; import { Environment } from "../lib/types"; -import { formatNumber } from "../lib/utils"; +import { formatNumber, getBankMetadata } from "../lib/utils"; dotenv.config(); @@ -26,8 +26,7 @@ async function main() { const program = getMarginfiProgram(argv.env as Environment); // Fetch bank metadata - const bankMetadataResponse = await fetch("https://storage.googleapis.com/mrgn-public/mrgn-bank-metadata-cache.json"); - const bankMetadata = (await bankMetadataResponse.json()) as BankMetadata[]; + const bankMetadata = await getBankMetadata(); let acc = await program.account.marginfiAccount.fetch(accountPubkey); let balances = acc.lendingAccount.balances; diff --git a/packages/tools/banks/get-bank-accounts.ts b/packages/tools/banks/get-bank-accounts.ts index e42b535d2..ed6d0af63 100644 --- a/packages/tools/banks/get-bank-accounts.ts +++ b/packages/tools/banks/get-bank-accounts.ts @@ -3,7 +3,7 @@ import { PublicKey } from "@solana/web3.js"; import { chunkedGetRawMultipleAccountInfos, chunks, wrappedI80F48toBigNumber } from "@mrgnlabs/mrgn-common"; import { getDefaultYargsOptions, getMarginfiProgram } from "../lib/config"; import { Environment } from "../lib/types"; -import { formatNumber, getCachedAccounts } from "../lib/utils"; +import { formatNumber, getCachedAccounts, getBankMetadata } from "../lib/utils"; dotenv.config(); @@ -55,9 +55,7 @@ async function main() { .parseSync(); const program = getMarginfiProgram(argv.env as Environment); - - const bankMetadataResponse = await fetch("https://storage.googleapis.com/mrgn-public/mrgn-bank-metadata-cache.json"); - const bankMetadata = (await bankMetadataResponse.json()) as BankMetadata[]; + const bankMetadata = await getBankMetadata(); let bankPubkey: PublicKey; if (argv.address) { diff --git a/packages/tools/banks/get-bank.ts b/packages/tools/banks/get-bank.ts index 811049f34..dfcfc561f 100644 --- a/packages/tools/banks/get-bank.ts +++ b/packages/tools/banks/get-bank.ts @@ -3,7 +3,7 @@ import { PublicKey } from "@solana/web3.js"; import { wrappedI80F48toBigNumber } from "@mrgnlabs/mrgn-common"; import { getDefaultYargsOptions, getMarginfiProgram } from "../lib/config"; import { Environment } from "../lib/types"; -import { formatNumber, getPythPushOracleAddresses } from "../lib/utils"; +import { formatNumber, getPythPushOracleAddresses, getBankMetadata } from "../lib/utils"; dotenv.config(); @@ -36,9 +36,7 @@ async function main() { .parseSync(); const program = getMarginfiProgram(argv.env as Environment); - - const bankMetadataResponse = await fetch("https://storage.googleapis.com/mrgn-public/mrgn-bank-metadata-cache.json"); - const bankMetadata = (await bankMetadataResponse.json()) as BankMetadata[]; + const bankMetadata = await getBankMetadata(); let bankPubkey: PublicKey; if (argv.address) { diff --git a/packages/tools/banks/get-banks.ts b/packages/tools/banks/get-banks.ts index f053c7df7..03c87d349 100644 --- a/packages/tools/banks/get-banks.ts +++ b/packages/tools/banks/get-banks.ts @@ -2,21 +2,15 @@ import dotenv from "dotenv"; import { PublicKey } from "@solana/web3.js"; import { getDefaultYargsOptions, getMarginfiProgram } from "../lib/config"; import { Environment } from "../lib/types"; +import { getBankMetadata } from "../lib/utils"; dotenv.config(); -type BankMetadata = { - bankAddress: string; - tokenSymbol: string; -}; - async function main() { const argv = getDefaultYargsOptions().parseSync(); const program = getMarginfiProgram(argv.env as Environment); - const bankMetadataResponse = await fetch("https://storage.googleapis.com/mrgn-public/mrgn-bank-metadata-cache.json"); - const bankMetadata = (await bankMetadataResponse.json()) as BankMetadata[]; - + const bankMetadata = await getBankMetadata(); const bankAddresses = bankMetadata.map((meta) => new PublicKey(meta.bankAddress)); const banks = await program.account.bank.fetchMultiple(bankAddresses); diff --git a/packages/tools/lib/utils.ts b/packages/tools/lib/utils.ts index 61d8fa8c5..56332891f 100644 --- a/packages/tools/lib/utils.ts +++ b/packages/tools/lib/utils.ts @@ -56,3 +56,19 @@ export function getPythPushOracleAddresses(feedId: Buffer): PublicKey[] { findPythPushOracleAddress(feedId, PYTH_PUSH_ORACLE_ID, MARGINFI_SPONSORED_SHARD_ID), ]; } + +export async function getBankMetadata(): Promise { + const bankMetadataResponse = await fetch("https://storage.googleapis.com/mrgn-public/mrgn-bank-metadata-cache.json"); + const stakedBankMetadataResponse = await fetch( + "https://storage.googleapis.com/mrgn-public/mrgn-staked-bank-metadata-cache.json" + ); + const bankMetadata = (await bankMetadataResponse.json()) as BankMetadata[]; + const stakedBankMetadata = (await stakedBankMetadataResponse.json()) as BankMetadata[]; + + return [...bankMetadata, ...stakedBankMetadata]; +} + +export type BankMetadata = { + bankAddress: string; + tokenSymbol: string; +};