Last active
January 5, 2024 07:41
-
-
Save losman0s/a77935fd1f02b960c9184d6c0581d78d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import fs from "fs"; | |
import { AccountInfo, Connection, PublicKey } from "@solana/web3.js"; | |
import { getConfig, MarginfiAccountWrapper, MarginfiClient } from "@mrgnlabs/marginfi-client-v2"; | |
const BANK_TOKEN = "SOL"; | |
// const BANK_TOKEN_MINT = "So11111111111111111111111111111111111111112"; | |
interface BankDepositor { | |
wallet: string; | |
userAccount: string; | |
amount: number; | |
} | |
async function main() { | |
console.log(`Initializing marginfi client...`) | |
const connection = new Connection("<RPC ENDPOINT>", "confirmed"); | |
const config = await getConfig("production"); | |
const client = await MarginfiClient.fetch(config, {} as any, connection, undefined, true); | |
const targetBank = client.getBankByTokenSymbol(BANK_TOKEN); | |
// const targetBank = client.getBankByMint(BANK_TOKEN_MINT); | |
if (!targetBank) { | |
throw new Error(`Bank ${BANK_TOKEN} not found`); | |
} | |
console.log(`Fetching all marginfi accounts...`) | |
const marginfiAccountAddresses = await client.getAllMarginfiAccountAddresses(); | |
console.log(`Found ${marginfiAccountAddresses.length} marginfi accounts`); | |
const addressBatches = chunks(marginfiAccountAddresses, 25_000); // To avoid blowing memory | |
const depositorFileName = `./marginfi_depositors_${BANK_TOKEN}_${Date.now()}.csv`; | |
fs.writeFileSync(depositorFileName, "authority,user_account,amount\n"); | |
for (let i = 0; i < addressBatches.length; i++) { | |
const addressBatch = addressBatches[i]; | |
console.log(`Processing batch ${i + 1}/${addressBatches.length} of ${addressBatch.length} addresses`); | |
const [_, accountInfoMap] = await chunkedGetRawMultipleAccountInfos( | |
connection, | |
addressBatch.map((pk) => pk.toBase58()) | |
); | |
let depositors: BankDepositor[] = []; | |
for (const [address, accountInfo] of accountInfoMap) { | |
const data = Buffer.from(accountInfo.data[0], "base64"); | |
const marginfiAccount = MarginfiAccountWrapper.fromAccountDataRaw(new PublicKey(address), client, data); | |
const depositAmount = marginfiAccount.balances | |
.find((b) => b.active && b.bankPk.equals(targetBank.address) && b.assetShares.gt(0)) | |
?.computeQuantityUi(targetBank).assets; | |
if (depositAmount) { | |
depositors.push({ | |
authority: marginfiAccount.authority.toString(), | |
userAccount: marginfiAccount.address.toString(), | |
amount: depositAmount.toNumber(), | |
}); | |
} | |
} | |
const csvContent = depositors.map(depositor => `${depositor.wallet},${depositor.userAccount},${depositor.amount}`).join('\n'); | |
fs.appendFileSync(depositorFileName, csvContent); | |
} | |
} | |
main(); | |
// ----------------------------------------------------------------------------------------------- | |
// Helpers | |
// ----------------------------------------------------------------------------------------------- | |
function chunks<T>(array: T[], size: number): T[][] { | |
return Array.apply(0, new Array(Math.ceil(array.length / size))).map((_, index) => | |
array.slice(index * size, (index + 1) * size) | |
); | |
} | |
interface Result { | |
jsonrpc: string; | |
result: { | |
context: { slot: number }; | |
value: Array<AccountInfo<string[]> | null>; | |
}; | |
} | |
async function chunkedGetRawMultipleAccountInfos( | |
connection: Connection, | |
pks: string[], | |
batchChunkSize: number = 1000, | |
maxAccountsChunkSize: number = 100 | |
): Promise<[number, Map<string, AccountInfo<string[]>>]> { | |
const accountInfoMap = new Map<string, AccountInfo<string[]>>(); | |
let contextSlot = 0; | |
const batches = chunkArray(pks, batchChunkSize); | |
for (let i = 0; i < batches.length; i++) { | |
const batch = batches[i]; | |
const batchRequest = chunkArray(batch, maxAccountsChunkSize).map((pubkeys) => ({ | |
methodName: "getMultipleAccounts", | |
args: connection._buildArgs([pubkeys], "confirmed", "base64"), | |
})); | |
let accountInfos: Array<AccountInfo<string[]> | null> = []; | |
let retries = 0; | |
const maxRetries = 3; | |
while (retries < maxRetries && accountInfos.length === 0) { | |
try { | |
accountInfos = await connection | |
// @ts-ignore | |
._rpcBatchRequest(batchRequest) | |
.then((batchResults: Result[]) => { | |
contextSlot = Math.max(...batchResults.map((res) => res.result.context.slot)); | |
const accounts = batchResults.reduce((acc, res) => { | |
acc.push(...res.result.value); | |
return acc; | |
}, [] as Result["result"]["value"]); | |
return accounts; | |
}); | |
} catch (error) { | |
retries++; | |
} | |
} | |
if (accountInfos.length === 0) { | |
throw new Error(`Failed to fetch account infos after ${maxRetries} retries`); | |
} | |
accountInfos.forEach((item, index) => { | |
const publicKey = batch[index]; | |
if (item) { | |
accountInfoMap.set(publicKey, item); | |
} | |
}); | |
} | |
return [contextSlot, accountInfoMap]; | |
} | |
function chunkArray<T>(array: T[], chunkSize: number): T[][] { | |
const chunks: T[][] = []; | |
for (let i = 0; i < array.length; i += chunkSize) { | |
chunks.push(array.slice(i, i + chunkSize)); | |
} | |
return chunks; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment