[Blockchain Watcher] (SOLANA) Mapped new attributes in solana redeem (#997)

* Rename and add new attributes

* Remove comment

* Resolve comment in PR

* Run prettier

* Resolve solana test

* Remove name in method mapper

* Run prettier

* Improve mapper method

* Remove toString in method mapper

* Resolve test error

* Resolve comment in PR

* Resolve comment in PR

* Revert name changes

---------

Co-authored-by: julian merlo <julianmerlo@julians-MacBook-Pro.local>
This commit is contained in:
Julian 2024-01-29 09:16:04 -03:00 committed by GitHub
parent 13ec7846c0
commit 59c2de9d0c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 82 additions and 6 deletions

View File

@ -69,3 +69,11 @@ export type TransactionFound = {
emitterChain?: number;
emitterAddress?: string;
};
export type InstructionFound = {
method: string;
status: string;
emitterChainId: number;
emitterAddress: string;
sequence: number;
};

View File

@ -0,0 +1,52 @@
import { solana } from "../../../domain/entities";
export const methodNameByInstructionMapper = (
instruction: solana.MessageCompiledInstruction,
programIdIndex: number
): Status => {
const data = instruction.data;
if (!programIdIndex || instruction.programIdIndex != Number(programIdIndex) || data.length == 0) {
return {
id: MethodID.unknownInstructionID,
method: Method.unknownInstruction,
};
}
const methodId = data[0];
const selectedMethod = methodsMapping[methodId]?.method || Method.unknownInstruction;
return {
id: methodId,
method: selectedMethod,
};
};
type Status = {
id: number;
method: string;
};
enum MethodID {
completeWrappedInstructionID = 3,
completeNativeInstructionID = 2,
unknownInstructionID = 0,
}
enum Method {
completeWrappedInstruction = "completeWrappedInstruction",
completeNativeInstruction = "completeNativeInstruction",
unknownInstruction = "unknownInstruction",
}
const methodsMapping: { [key: number]: { method: string } } = {
[MethodID.completeWrappedInstructionID]: {
method: Method.completeWrappedInstruction,
},
[MethodID.completeNativeInstructionID]: {
method: Method.completeNativeInstruction,
},
[MethodID.unknownInstructionID]: {
method: Method.unknownInstruction,
},
};

View File

@ -1,9 +1,10 @@
import { decode } from "bs58";
import { Connection, Commitment } from "@solana/web3.js";
import { solana, LogFoundEvent, TransferRedeemed } from "../../../domain/entities";
import { solana, TransactionFoundEvent, InstructionFound } from "../../../domain/entities";
import { CompiledInstruction, MessageCompiledInstruction } from "../../../domain/entities/solana";
import { configuration } from "../../config";
import { methodNameByInstructionMapper } from "./methodNameByInstructionMapper";
import { Connection, Commitment } from "@solana/web3.js";
import { getPostedMessage } from "@certusone/wormhole-sdk/lib/cjs/solana/wormhole";
import { configuration } from "../../config";
import { decode } from "bs58";
enum Instruction {
CompleteNativeTransfer = 0x02,
@ -12,12 +13,15 @@ enum Instruction {
CompleteWrappedWithPayload = 0x0a,
}
const TRANSACTION_STATUS_COMPLETED = "completed";
const TRANSACTION_STATUS_FAILED = "failed";
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>[]> => {
): Promise<TransactionFoundEvent<InstructionFound>[]> => {
if (!tx || !tx.blockTime) {
throw new Error(
`Block time is missing for tx ${tx?.transaction?.signatures} in slot ${tx?.slot}`
@ -36,7 +40,7 @@ export const solanaTransferRedeemedMapper = async (
.concat(instructions)
.filter((i) => i.programIdIndex === programIdIndex);
const results: LogFoundEvent<TransferRedeemed>[] = [];
const results: TransactionFoundEvent<InstructionFound>[] = [];
for (const instruction of whInstructions) {
if (isNotACompleteTransferInstruction(instruction.data)) {
continue;
@ -45,6 +49,7 @@ export const solanaTransferRedeemedMapper = async (
const accountAddress = accountKeys[instruction.accountKeyIndexes[2]];
const { message } = await getPostedMessage(connection, accountAddress, commitment);
const { sequence, emitterAddress, emitterChain } = message || {};
const methods = methodNameByInstructionMapper(instruction, programIdIndex);
results.push({
name: "transfer-redeemed",
@ -54,6 +59,8 @@ export const solanaTransferRedeemedMapper = async (
blockHeight: BigInt(tx.slot.toString()),
blockTime: tx.blockTime,
attributes: {
method: methods.method,
status: mappedStatus(tx),
emitterChainId: emitterChain,
emitterAddress: emitterAddress.toString("hex"),
sequence: Number(sequence),
@ -64,6 +71,11 @@ export const solanaTransferRedeemedMapper = async (
return results;
};
const mappedStatus = (tx: solana.Transaction): string => {
if (!tx.meta || tx.meta.err) TRANSACTION_STATUS_FAILED;
return TRANSACTION_STATUS_COMPLETED;
};
const normalizeCompileInstruction = (
instruction: CompiledInstruction | MessageCompiledInstruction
): MessageCompiledInstruction => {

View File

@ -145,6 +145,8 @@ describe("solanaTransferRedeemedMapper", () => {
expect(events[0].txHash).toBe(tx.transaction.signatures[0]);
expect(events[0].blockHeight).toBe(BigInt(tx.slot));
expect(events[0].blockTime).toBe(tx.blockTime);
expect(events[0].attributes.method).toBe("completeWrappedInstruction");
expect(events[0].attributes.status).toBe("completed");
});
it("should map a tx involving token bridge relayer (aka connect) to a transfer-redeemed event", async () => {
@ -352,5 +354,7 @@ describe("solanaTransferRedeemedMapper", () => {
expect(events[0].txHash).toBe(tx.transaction.signatures[0]);
expect(events[0].blockHeight).toBe(BigInt(tx.slot));
expect(events[0].blockTime).toBe(tx.blockTime);
expect(events[0].attributes.method).toBe("unknownInstruction");
expect(events[0].attributes.status).toBe("completed");
});
});