Skip to content

Instantly share code, notes, and snippets.

@losman0s
Last active January 5, 2024 07:41
Show Gist options
  • Save losman0s/a77935fd1f02b960c9184d6c0581d78d to your computer and use it in GitHub Desktop.
Save losman0s/a77935fd1f02b960c9184d6c0581d78d to your computer and use it in GitHub Desktop.
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