wormhole-explorer/blockchain-watcher/src/infrastructure/repositories/solana/Web3SolanaSlotRepository.ts

123 lines
3.6 KiB
TypeScript

import {
Commitment,
Connection,
PublicKey,
VersionedTransactionResponse,
SolanaJSONRPCError,
} from "@solana/web3.js";
import { solana } from "../../../domain/entities";
import { SolanaSlotRepository } from "../../../domain/repositories";
import { Fallible, SolanaFailure } from "../../../domain/errors";
const COMMITMENT_FINALIZED = "finalized";
const COMMITMENT_CONDIRMED = "confirmed";
const LEGACY_VERSION = "legacy";
export class Web3SolanaSlotRepository implements SolanaSlotRepository {
private connection: Connection;
constructor(connection: Connection) {
this.connection = connection;
}
getLatestSlot(commitment: string): Promise<number> {
return this.connection.getSlot(commitment as Commitment);
}
getBlock(slot: number, finality?: string): Promise<Fallible<solana.Block, SolanaFailure>> {
return this.connection
.getBlock(slot, {
maxSupportedTransactionVersion: 0,
commitment:
finality === COMMITMENT_FINALIZED || finality === COMMITMENT_CONDIRMED
? finality
: undefined,
})
.then((block) => {
if (block === null) {
return Fallible.error<solana.Block, SolanaFailure>(
new SolanaFailure(0, "Block not found")
);
}
return Fallible.ok<solana.Block, SolanaFailure>({
...block,
transactions: block.transactions.map((tx) => this.mapTx(tx, slot)),
});
})
.catch((err) => {
if (err instanceof SolanaJSONRPCError) {
return Fallible.error(new SolanaFailure(err.code, err.message));
}
throw err;
});
}
getSignaturesForAddress(
address: string,
beforeSig: string,
afterSig: string,
limit: number
): Promise<solana.ConfirmedSignatureInfo[]> {
return this.connection.getSignaturesForAddress(new PublicKey(address), {
limit: limit,
before: beforeSig,
until: afterSig,
});
}
async getTransactions(sigs: solana.ConfirmedSignatureInfo[]): Promise<solana.Transaction[]> {
const txs = await this.connection.getTransactions(
sigs.map((sig) => sig.signature),
{ maxSupportedTransactionVersion: 0 }
);
if (txs.length !== sigs.length) {
throw new Error(`Expected ${sigs.length} transactions, but got ${txs.length} instead`);
}
return txs
.filter((tx) => tx !== null)
.map((tx, i) => {
const message = tx?.transaction.message;
const accountKeys =
message?.version === LEGACY_VERSION
? message.accountKeys.map((key) => key.toBase58())
: message?.staticAccountKeys.map((key) => key.toBase58());
return {
...tx,
transaction: {
...tx?.transaction,
message: {
...tx?.transaction.message,
accountKeys,
compiledInstructions: message?.compiledInstructions ?? [],
},
},
} as solana.Transaction;
});
}
private mapTx(tx: Partial<VersionedTransactionResponse>, slot?: number): solana.Transaction {
const message = tx?.transaction?.message;
const accountKeys =
message?.version === LEGACY_VERSION
? message.accountKeys.map((key) => key.toBase58())
: message?.staticAccountKeys.map((key) => key.toBase58());
return {
...tx,
slot: tx.slot || slot,
transaction: {
...tx.transaction,
message: {
...tx?.transaction?.message,
accountKeys,
compiledInstructions: message?.compiledInstructions,
},
},
} as solana.Transaction;
}
}