@ox-fun/drift-sdk
Version:
SDK for Drift Protocol
173 lines (148 loc) • 4.11 kB
text/typescript
import { Program } from '@coral-xyz/anchor';
import {
Connection,
Finality,
PublicKey,
TransactionResponse,
TransactionSignature,
VersionedTransactionResponse,
} from '@solana/web3.js';
import { WrappedEvents } from './types';
import { promiseTimeout } from '../util/promiseTimeout';
type Log = { txSig: TransactionSignature; slot: number; logs: string[] };
type FetchLogsResponse = {
earliestTx: string;
mostRecentTx: string;
earliestSlot: number;
mostRecentSlot: number;
transactionLogs: Log[];
mostRecentBlockTime: number | undefined;
};
function mapTransactionResponseToLog(
transaction: TransactionResponse | VersionedTransactionResponse
): Log {
return {
txSig: transaction.transaction.signatures[0],
slot: transaction.slot,
logs: transaction.meta.logMessages,
};
}
export async function fetchLogs(
connection: Connection,
address: PublicKey,
finality: Finality,
beforeTx?: TransactionSignature,
untilTx?: TransactionSignature,
limit?: number,
batchSize = 25
): Promise<FetchLogsResponse> {
const signatures = await connection.getSignaturesForAddress(
address,
{
before: beforeTx,
until: untilTx,
limit,
},
finality
);
const sortedSignatures = signatures.sort((a, b) =>
a.slot === b.slot ? 0 : a.slot < b.slot ? -1 : 1
);
const filteredSignatures = sortedSignatures.filter(
(signature) => !signature.err
);
if (filteredSignatures.length === 0) {
return undefined;
}
const chunkedSignatures = chunk(filteredSignatures, batchSize);
const transactionLogs = (
await Promise.all(
chunkedSignatures.map(async (chunk) => {
return await fetchTransactionLogs(
connection,
chunk.map((confirmedSignature) => confirmedSignature.signature),
finality
);
})
)
).flat();
const earliest = filteredSignatures[0];
const mostRecent = filteredSignatures[filteredSignatures.length - 1];
return {
transactionLogs: transactionLogs,
earliestTx: earliest.signature,
mostRecentTx: mostRecent.signature,
earliestSlot: earliest.slot,
mostRecentSlot: mostRecent.slot,
mostRecentBlockTime: mostRecent.blockTime,
};
}
export async function fetchTransactionLogs(
connection: Connection,
signatures: TransactionSignature[],
finality: Finality
): Promise<Log[]> {
const requests = new Array<{ methodName: string; args: any }>();
for (const signature of signatures) {
const args = [
signature,
{ commitment: finality, maxSupportedTransactionVersion: 0 },
];
requests.push({
methodName: 'getTransaction',
args,
});
}
const rpcResponses: any | null = await promiseTimeout(
// @ts-ignore
connection._rpcBatchRequest(requests),
10 * 1000 // 10 second timeout
);
if (rpcResponses === null) {
return Promise.reject('RPC request timed out fetching transactions');
}
const logs = new Array<Log>();
for (const rpcResponse of rpcResponses) {
if (rpcResponse.result) {
logs.push(mapTransactionResponseToLog(rpcResponse.result));
}
}
return logs;
}
function chunk<T>(array: readonly T[], size: number): T[][] {
return new Array(Math.ceil(array.length / size))
.fill(null)
.map((_, index) => index * size)
.map((begin) => array.slice(begin, begin + size));
}
export class LogParser {
private program: Program;
constructor(program: Program) {
this.program = program;
}
public parseEventsFromTransaction(
transaction: TransactionResponse
): WrappedEvents {
const transactionLogObject = mapTransactionResponseToLog(transaction);
return this.parseEventsFromLogs(transactionLogObject);
}
public parseEventsFromLogs(event: Log): WrappedEvents {
const records: WrappedEvents = [];
if (!event.logs) return records;
// @ts-ignore
const eventGenerator = this.program._events._eventParser.parseLogs(
event.logs,
false
);
let runningEventIndex = 0;
for (const eventLog of eventGenerator) {
eventLog.data.txSig = event.txSig;
eventLog.data.slot = event.slot;
eventLog.data.eventType = eventLog.name;
eventLog.data.txSigIndex = runningEventIndex;
records.push(eventLog.data);
runningEventIndex++;
}
return records;
}
}