wormhole-explorer/blockchain-watcher/src/infrastructure/mappers/evm/evmRedeemedTransactionFound...

143 lines
4.2 KiB
TypeScript

import {
EvmTransaction,
EvmTransactionFoundAttributes,
TransactionFound,
TransactionFoundEvent,
TxStatus,
} from "../../../domain/entities";
import { Protocol, contractsMapperConfig } from "../contractsMapper";
import winston from "../../log";
const TX_STATUS_CONFIRMED = "0x1";
const TX_STATUS_FAILED = "0x0";
const TOKEN_BRIDGE_TOPIC = "0xcaf280c8cfeba144da67230d9b009c8f868a75bac9a528fa0474be1ba317c169";
const CCTP_TOPIC = "0xf02867db6908ee5f81fd178573ae9385837f0a0a72553f8c08306759a7e0f00e";
let logger: winston.Logger;
logger = winston.child({ module: "evmRedeemedTransactionFoundMapper" });
export const evmRedeemedTransactionFoundMapper = (
transaction: EvmTransaction
): TransactionFoundEvent<EvmTransactionFoundAttributes> | undefined => {
const protocol = findProtocol(
transaction.chain,
transaction.to,
transaction.input,
transaction.hash
);
const vaaInformation = mappedVaaInformation(transaction.logs);
const status = mappedStatus(transaction.status);
const emitterAddress = vaaInformation?.emitterAddress;
const emitterChain = vaaInformation?.emitterChain;
const sequence = vaaInformation?.sequence;
if (protocol && protocol.type && protocol.method) {
logger.debug(
`[${transaction.chain}] Transaction info: [hash: ${transaction.hash}][VAA: ${emitterChain}/${emitterAddress}/${sequence}][protocol: ${protocol.type}/${protocol.method}]`
);
return {
name: "transfer-redeemed",
address: transaction.to,
chainId: transaction.chainId,
txHash: transaction.hash,
blockHeight: BigInt(transaction.blockNumber),
blockTime: transaction.timestamp,
attributes: {
from: transaction.from,
to: transaction.to,
status: status,
blockNumber: transaction.blockNumber,
input: transaction.input,
methodsByAddress: protocol.method,
timestamp: transaction.timestamp,
blockHash: transaction.blockHash,
gas: transaction.gas,
gasPrice: transaction.gasPrice,
maxFeePerGas: transaction.maxFeePerGas,
maxPriorityFeePerGas: transaction.maxPriorityFeePerGas,
nonce: transaction.nonce,
r: transaction.r,
s: transaction.s,
transactionIndex: transaction.transactionIndex,
type: transaction.type,
v: transaction.v,
value: transaction.value,
sequence: sequence,
emitterAddress: emitterAddress,
emitterChain: emitterChain,
protocol: protocol.type,
},
};
}
};
const mappedVaaInformation = (
logs: { address: string; topics: string[] }[]
): VaaInformation | undefined => {
const log = logs.find((log) => {
if (log.topics.includes(CCTP_TOPIC) || log.topics.includes(TOKEN_BRIDGE_TOPIC)) return log;
});
const vaaInformation = log
? {
emitterChain: Number(log.topics[1]),
emitterAddress: BigInt(log.topics[2])?.toString(16)?.toUpperCase()?.padStart(64, "0"),
sequence: Number(log.topics[3]),
}
: undefined;
return vaaInformation;
};
const mappedStatus = (txStatus: string | undefined): string => {
switch (txStatus) {
case TX_STATUS_CONFIRMED:
return TxStatus.Confirmed;
case TX_STATUS_FAILED:
return TxStatus.Failed;
default:
return TxStatus.Unkonwn;
}
};
const findProtocol = (
chain: string,
address: string,
input: string,
hash: string
): Protocol | undefined => {
const first10Characters = input.slice(0, 10);
for (const contract of contractsMapperConfig.contracts) {
if (contract.chain === chain) {
const foundProtocol = contract.protocols.find((protocol) =>
protocol.addresses.some((addr) => addr.toLowerCase() === address.toLowerCase())
);
const foundMethod = foundProtocol?.methods.find(
(method) => method.methodId === first10Characters
);
if (foundMethod && foundProtocol) {
return {
method: foundMethod.method,
type: foundProtocol.type,
};
}
}
}
logger.warn(
`[${chain}] Protocol not found, [tx hash: ${hash}][address: ${address}][input: ${first10Characters}]`
);
};
type VaaInformation = {
emitterChain?: number;
emitterAddress?: string;
sequence?: number;
};