wormhole-explorer/blockchain-watcher/src/infrastructure/mappers/solana/solanaTransferRedeemedMappe...

94 lines
3.1 KiB
TypeScript

import { decode } from "bs58";
import { Connection, Commitment } from "@solana/web3.js";
import { solana, LogFoundEvent, TransferRedeemed } from "../../../domain/entities";
import { CompiledInstruction, MessageCompiledInstruction } from "../../../domain/entities/solana";
import { configuration } from "../../config";
import { getPostedMessage } from "@certusone/wormhole-sdk/lib/cjs/solana/wormhole";
enum Instruction {
CompleteNativeTransfer = 0x02,
CompleteWrappedTransfer = 0x03,
CompleteNativeWithPayload = 0x09,
CompleteWrappedWithPayload = 0x0a,
}
const connection = new Connection(configuration.chains.solana.rpcs[0]);
export const solanaTransferRedeemedMapper = async (
tx: solana.Transaction,
{ programId, commitment }: { programId: string; commitment?: Commitment }
): Promise<LogFoundEvent<TransferRedeemed>[]> => {
if (!tx || !tx.blockTime) {
throw new Error(
`Block time is missing for tx ${tx?.transaction?.signatures} in slot ${tx?.slot}`
);
}
const message = tx.transaction.message;
const accountKeys = message.accountKeys;
const programIdIndex = accountKeys.findIndex((i) => i === programId);
const instructions = message.compiledInstructions;
const innerInstructions =
tx.meta?.innerInstructions?.flatMap((i) => i.instructions.map(normalizeCompileInstruction)) ||
[];
const whInstructions = innerInstructions
.concat(instructions)
.filter((i) => i.programIdIndex === programIdIndex);
const results: LogFoundEvent<TransferRedeemed>[] = [];
for (const instruction of whInstructions) {
if (isNotACompleteTransferInstruction(instruction.data)) {
continue;
}
const accountAddress = accountKeys[instruction.accountKeyIndexes[2]];
const { message } = await getPostedMessage(connection, accountAddress, commitment);
const { sequence, emitterAddress, emitterChain } = message || {};
results.push({
name: "transfer-redeemed",
address: programId,
chainId: 1,
txHash: tx.transaction.signatures[0],
blockHeight: BigInt(tx.slot.toString()),
blockTime: tx.blockTime,
attributes: {
emitterChainId: emitterChain,
emitterAddress: emitterAddress.toString("hex"),
sequence: Number(sequence),
},
});
}
return results;
};
const normalizeCompileInstruction = (
instruction: CompiledInstruction | MessageCompiledInstruction
): MessageCompiledInstruction => {
if ("accounts" in instruction) {
return {
accountKeyIndexes: instruction.accounts,
data: decode(instruction.data),
programIdIndex: instruction.programIdIndex,
};
} else {
return instruction;
}
};
/**
* Checks if the instruction is not to complete a transfer.
* @param instructionId - the instruction id
* @returns true if the instruction is valid, false otherwise
*/
const isNotACompleteTransferInstruction = (instructionId: Uint8Array): boolean => {
return (
instructionId[0] !== Instruction.CompleteNativeTransfer &&
instructionId[0] !== Instruction.CompleteWrappedTransfer &&
instructionId[0] !== Instruction.CompleteNativeWithPayload &&
instructionId[0] !== Instruction.CompleteWrappedWithPayload
);
};