[Blockchain Watcher] (EVM) Support evm fail redeem (#919)
* feature-823/support-evm-fail-redeem * feature-823/support-evm-fail-redeem * Ad handler fo transactions flow * Integrate test and handler * Run prettier * Create unit test for methodNameByAddressMapper and GetEvmTransactions class * Improve names * Add logger info * Improve log name and mapped methods * Run prettier * Implement strategy to process message * Run prettier * Run prettier * Resolve method test * Mapped timestamp value * Change string to number in chainId property * Improve names and mappers * Run prettier * Create interface for strategy * Resolve method GetEvmTransactions test * Resolve comment in PR * Resolve comment in PR * Rename redeem-failed * Improve rage value * Run prettier * Reduce mapper in one evmTransferFoundMapper * Mapped standar relay name * Improve readme * Change evmTransactionFoundMapper name * Change evm mapper name in object return * Mapped evm mapper test * Map EvmTransactionFound in asyncapi docs * Improve name in methods mapper * Implement batch request for getTransactionReceipt method * Add error manage in getTransactionReceipt method * Rename protocol MethodCompleteTransferWithRelay --------- Co-authored-by: julian merlo <julianmerlo@julians-MacBook-Pro.local>
This commit is contained in:
parent
076338f63c
commit
d7dae2413e
|
@ -43,7 +43,7 @@ Example:
|
|||
"id": "poll-log-message-published-ethereum",
|
||||
"chain": "ethereum",
|
||||
"source": {
|
||||
"action": "PollEvmLogs",
|
||||
"action": "PollEvm",
|
||||
"config": {
|
||||
"fromBlock": "10012499",
|
||||
"blockBatchSize": 100,
|
||||
|
@ -70,7 +70,7 @@ Example:
|
|||
{
|
||||
"action": "HandleEvmLogs",
|
||||
"target": "sns",
|
||||
"mapper": "evmTransferRedeemedMapper",
|
||||
"mapper": "evmTransactionFoundMapper",
|
||||
"config": {
|
||||
"abi": "event TransferRedeemed(uint16 indexed emitterChainId, bytes32 indexed emitterAddress, uint64 indexed sequence)",
|
||||
"filter": {
|
||||
|
@ -82,7 +82,7 @@ Example:
|
|||
{
|
||||
"action": "HandleEvmLogs",
|
||||
"target": "sns",
|
||||
"mapper": "evmStandardRelayDelivered",
|
||||
"mapper": "evmTransactionFoundMapper",
|
||||
"config": {
|
||||
"abi": "event Delivery(address indexed recipientContract, uint16 indexed sourceChain, uint64 indexed sequence, bytes32 deliveryVaaHash, uint8 status, uint256 gasUsed, uint8 refundStatus, bytes additionalStatusInfo, bytes overridesInfo)",
|
||||
"filter": {
|
||||
|
@ -127,5 +127,5 @@ Example:
|
|||
|
||||
Currently, jobs are read and loaded based on a JSON file.
|
||||
Each job has a source, and one or more handlers.
|
||||
Each handler has an action, a mapper and a target. For example, you can choose to use PollEvmLogs as an action and HandleEvmLogs as a handler. For this handler you need to set a mapper like evmLogMessagePublishedMapper.
|
||||
Each handler has an action, a mapper and a target. For example, you can choose to use PollEvm (GetEvmLogs) as an action and HandleEvmLogs as a handler. For this handler you need to set a mapper like evmLogMessagePublishedMapper.
|
||||
The target can be sns, or a fake one if dryRun is enabled.
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"environment": "mainnet",
|
||||
"chains": {
|
||||
"solana": {
|
||||
"network": "mainnet-beta",
|
||||
|
|
|
@ -8,6 +8,15 @@ servers:
|
|||
staging-testnet:
|
||||
url: notification-chain-events-dev-testnet.fifo
|
||||
protocol: sns
|
||||
staging-mainnet:
|
||||
url: notification-chain-events-dev-mainnet.fifo
|
||||
protocol: sns
|
||||
prod-testnet:
|
||||
url: notification-chain-events-prod-testnet.fifo
|
||||
protocol: sns
|
||||
prod-mainnet:
|
||||
url: notification-chain-events-prod-mainnet.fifo
|
||||
protocol: sns
|
||||
defaultContentType: application/json
|
||||
channels:
|
||||
LogMessagePublished:
|
||||
|
@ -15,11 +24,11 @@ channels:
|
|||
subscribe:
|
||||
message:
|
||||
$ref: "#/components/messages/logMessagePublished"
|
||||
TransferRedeemed:
|
||||
EvmTransactionFound:
|
||||
description: Token bridge emitted event
|
||||
subscribe:
|
||||
message:
|
||||
$ref: "#/components/messages/transferRedeemed"
|
||||
$ref: "#/components/messages/evmTransactionFound"
|
||||
components:
|
||||
messages:
|
||||
logMessagePublished:
|
||||
|
@ -28,50 +37,42 @@ components:
|
|||
contentType: application/json
|
||||
payload:
|
||||
$ref: "#/components/schemas/logMessagePublished"
|
||||
transferRedeemed:
|
||||
name: TransferRedeemed
|
||||
title: TransferRedeemed
|
||||
evmTransactionFound:
|
||||
name: EvmTransactionFound
|
||||
title: EvmTransactionFound
|
||||
contentType: application/json
|
||||
payload:
|
||||
$ref: "#/components/schemas/transferRedeemed"
|
||||
$ref: "#/components/schemas/evmTransactionFound"
|
||||
examples:
|
||||
- name: TransferRedeemed in Solana from Ethereum
|
||||
- name: EvmTransactionFound from Ethereum
|
||||
payload:
|
||||
name: "transfer-redeemed"
|
||||
address: wormDTUJ6AWPNvk59vGQbDvGJmqbDTdgWgAqcLBCgUb
|
||||
name: "evm-transaction-found"
|
||||
address: "wormDTUJ6AWPNvk59vGQbDvGJmqbDTdgWgAqcLBCgUb"
|
||||
chainId: 1
|
||||
txHash: 3FySmshUgVCM2N158oNYbeTfZt2typEU32c9ZxdAXiXURFHuTmeJHhc7cSUtqHdwAsbVWWvEsEddWNAKzkjVPSg2
|
||||
txHash: "3FySmshUgVCM2N158oNYbeTfZt2typEU32c9ZxdAXiXURFHuTmeJHhc7cSUtqHdwAsbVWWvEsEddWNAKzkjVPSg2"
|
||||
blockHeight: 234015120
|
||||
blockTime: 1701724272
|
||||
attributes:
|
||||
emitterChainId: 2
|
||||
emitterAddress: "0000000000000000000000003ee18b2214aff97000d974cf647e7c347e8fa585"
|
||||
sequence: 144500
|
||||
standardRelayDelivered:
|
||||
name: StandardRelayDelivered
|
||||
title: StandardRelayDelivered
|
||||
contentType: application/json
|
||||
payload:
|
||||
$ref: "#/components/schemas/standardRelayDelivered"
|
||||
examples:
|
||||
- name: StandardRelayDelivered from in Ethereum from Base
|
||||
payload:
|
||||
name: "standard-relay-delivered"
|
||||
address: "0x27428dd2d3dd32a4d7f7c497eaaa23130d894911"
|
||||
chainId: 2
|
||||
txHash: "0xcbdefc83080a8f60cbde7785eb2978548fd5c1f7d0ea2c024cce537845d339c7"
|
||||
blockHeight: 18708316n
|
||||
blockTime: 1699443287
|
||||
attributes:
|
||||
recipientContract: "0xF80cf52922B512B22D46aA8916BD7767524305d9"
|
||||
sourceChain: 30
|
||||
sequence: 2304
|
||||
deliveryVaaHash: "0xf29cac97156fa11c205eda95c0655e4a6e2a9c247245bab4d3d8257c41fc11d2"
|
||||
status: 0
|
||||
gasUsed: 80521
|
||||
refundStatus: 0
|
||||
additionalStatusInfo: "0x"
|
||||
overridesInfo: "0x"
|
||||
blockHash: "0x1359819238ea89f49c20e42eb5603bf0541589d838d971984b60c7cdb391d9c2"
|
||||
blockNumber: 0x11ec2bc
|
||||
from: 0xfb070adcd21361a3946a0584dc84a7b89faa68e3
|
||||
gas: 0x14485
|
||||
gasPrice: xfc518561e
|
||||
input: "0x9981509f000000000000"
|
||||
maxFeePerGas: 0x1610f75b9a
|
||||
maxPriorityFeePerGas: 0x5f5e100
|
||||
methodsByAddress: MethodCompleteTransfer
|
||||
name: transfer-redeemed
|
||||
nonce: 0x1
|
||||
r: 0xf5794b0970386d73b693b17f147fae0427db278e951e45465ac2c9835537e5a9
|
||||
s: 0x6dccc8cfee216bc43a9d66525fa94905da234ad32d6cc3220845bef78f25dd42
|
||||
status: 0x1
|
||||
timestamp: 1702663079
|
||||
to: 0x3ee18b2214aff97000d974cf647e7c347e8fa585
|
||||
transactionIndex: 0x6f
|
||||
type: 0x2
|
||||
v: 0x1
|
||||
value: 0x5b09cd3e5e90000
|
||||
schemas:
|
||||
base:
|
||||
type: object
|
||||
|
@ -122,7 +123,7 @@ components:
|
|||
type: string
|
||||
consistencyLevel:
|
||||
type: number
|
||||
transferRedeemed:
|
||||
evmTransactionFound:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/base"
|
||||
type: object
|
||||
|
@ -135,42 +136,45 @@ components:
|
|||
attributes:
|
||||
type: object
|
||||
properties:
|
||||
emitterChainId:
|
||||
type: number
|
||||
emitterAddress:
|
||||
name:
|
||||
type: string
|
||||
sequence:
|
||||
type: number
|
||||
standardRelayDelivered:
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/base"
|
||||
type: object
|
||||
properties:
|
||||
data:
|
||||
type: object
|
||||
allOf:
|
||||
- $ref: "#/components/schemas/chainEventBase"
|
||||
properties:
|
||||
attributes:
|
||||
type: object
|
||||
properties:
|
||||
recipientContract:
|
||||
from:
|
||||
type: string
|
||||
sourceChain:
|
||||
type: number
|
||||
sequence:
|
||||
type: number
|
||||
deliveryVaaHash:
|
||||
to:
|
||||
type: string
|
||||
status:
|
||||
type: number
|
||||
gasUsed:
|
||||
type: number
|
||||
refundStatus:
|
||||
type: number
|
||||
additionalStatusInfo:
|
||||
type: string
|
||||
overridesInfo:
|
||||
blockNumber:
|
||||
type: number
|
||||
input:
|
||||
type: string
|
||||
methodsByAddress:
|
||||
type: string
|
||||
timestamp:
|
||||
type: number
|
||||
blockHash:
|
||||
type: string
|
||||
gas:
|
||||
type: string
|
||||
gasPrice:
|
||||
type: string
|
||||
maxFeePerGas:
|
||||
type: string
|
||||
maxPriorityFeePerGas:
|
||||
type: string
|
||||
nonce:
|
||||
type: string
|
||||
r:
|
||||
type: string
|
||||
s:
|
||||
type: string
|
||||
transactionIndex:
|
||||
type: string
|
||||
type:
|
||||
type: string
|
||||
v:
|
||||
type: string
|
||||
value:
|
||||
type: string
|
||||
sentAt:
|
||||
type: string
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
"scripts": {
|
||||
"start": "node lib/start.js",
|
||||
"start:ncc": "node lib/index.js",
|
||||
"test": "jest",
|
||||
"test": "jest --collectCoverage=false",
|
||||
"test:coverage": "jest --collectCoverage=true",
|
||||
"build": "tsc",
|
||||
"build:ncc": "ncc build src/start.ts -o lib",
|
||||
|
@ -74,7 +74,7 @@
|
|||
"coverageDirectory": "./coverage",
|
||||
"coverageThreshold": {
|
||||
"global": {
|
||||
"lines": 73
|
||||
"lines": 74
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,17 +11,18 @@ export class GetEvmLogs {
|
|||
this.logger = winston.child({ module: "GetEvmLogs" });
|
||||
}
|
||||
|
||||
async execute(range: Range, opts: GetEvmLogsOpts): Promise<EvmLog[]> {
|
||||
if (range.fromBlock > range.toBlock) {
|
||||
this.logger.info(
|
||||
`[exec] Invalid range [fromBlock: ${range.fromBlock} - toBlock: ${range.toBlock}]`
|
||||
);
|
||||
async execute(range: Range, opts: GetEvmOpts): Promise<EvmLog[]> {
|
||||
const fromBlock = range.fromBlock;
|
||||
const toBlock = range.toBlock;
|
||||
|
||||
if (fromBlock > toBlock) {
|
||||
this.logger.info(`[exec] Invalid range [fromBlock: ${fromBlock} - toBlock: ${toBlock}]`);
|
||||
return [];
|
||||
}
|
||||
|
||||
const logs = await this.blockRepo.getFilteredLogs(opts.chain, {
|
||||
fromBlock: range.fromBlock,
|
||||
toBlock: range.toBlock,
|
||||
fromBlock,
|
||||
toBlock,
|
||||
addresses: opts.addresses ?? [], // Works when sending multiple addresses, but not multiple topics.
|
||||
topics: opts.topics ?? [],
|
||||
});
|
||||
|
@ -42,8 +43,9 @@ type Range = {
|
|||
toBlock: bigint;
|
||||
};
|
||||
|
||||
export interface GetEvmLogsOpts {
|
||||
export type GetEvmOpts = {
|
||||
addresses?: string[];
|
||||
topics?: string[];
|
||||
chain: string;
|
||||
}
|
||||
environment: string;
|
||||
};
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
import { methodNameByAddressMapper } from "./mappers/methodNameByAddressMapper";
|
||||
import { EvmBlock, EvmTransaction } from "../../entities";
|
||||
import { EvmBlockRepository } from "../../repositories";
|
||||
import { GetEvmOpts } from "./GetEvmLogs";
|
||||
import winston from "winston";
|
||||
|
||||
export class GetEvmTransactions {
|
||||
private readonly blockRepo: EvmBlockRepository;
|
||||
protected readonly logger: winston.Logger;
|
||||
|
||||
constructor(blockRepo: EvmBlockRepository) {
|
||||
this.logger = winston.child({ module: "GetEvmTransactions" });
|
||||
this.blockRepo = blockRepo;
|
||||
}
|
||||
|
||||
async execute(range: Range, opts: GetEvmOpts): Promise<EvmTransaction[]> {
|
||||
const fromBlock = range.fromBlock;
|
||||
const toBlock = range.toBlock;
|
||||
|
||||
if (fromBlock > toBlock) {
|
||||
this.logger.info(`[exec] Invalid range [fromBlock: ${fromBlock} - toBlock: ${toBlock}]`);
|
||||
return [];
|
||||
}
|
||||
|
||||
let populateTransactions: EvmTransaction[] = [];
|
||||
const environment = opts.environment;
|
||||
const isTransactionsPresent = true;
|
||||
const chain = opts.chain;
|
||||
|
||||
for (let block = fromBlock; block <= toBlock; block++) {
|
||||
const evmBlock = await this.blockRepo.getBlock(chain, block, isTransactionsPresent);
|
||||
const transactions = evmBlock.transactions ?? [];
|
||||
|
||||
// Only process transactions to the contract address
|
||||
const transactionsFilter = transactions.filter(
|
||||
(transaction) =>
|
||||
opts.addresses?.includes(String(transaction.to).toLowerCase()) ||
|
||||
opts.addresses?.includes(String(transaction.from).toLowerCase())
|
||||
);
|
||||
|
||||
if (transactionsFilter.length > 0) {
|
||||
populateTransactions = await this.populateTransaction(
|
||||
chain,
|
||||
environment,
|
||||
evmBlock,
|
||||
transactionsFilter
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.info(
|
||||
`[${chain}][exec] Got ${
|
||||
populateTransactions?.length
|
||||
} transactions to process for ${this.populateLog(opts, fromBlock, toBlock)}`
|
||||
);
|
||||
return populateTransactions;
|
||||
}
|
||||
|
||||
private async populateTransaction(
|
||||
chain: string,
|
||||
environment: string,
|
||||
evmBlock: EvmBlock,
|
||||
transactionsFilter: EvmTransaction[]
|
||||
): Promise<EvmTransaction[]> {
|
||||
const hashNumbers = new Set(transactionsFilter.map((transaction) => transaction.hash));
|
||||
const receiptTransaction = await this.blockRepo.getTransactionReceipt(chain, hashNumbers);
|
||||
|
||||
transactionsFilter.forEach((transaction) => {
|
||||
transaction.chainId = Number(transaction.chainId);
|
||||
transaction.timestamp = evmBlock.timestamp;
|
||||
transaction.status = receiptTransaction[transaction.hash].status;
|
||||
transaction.environment = environment;
|
||||
transaction.chain = chain;
|
||||
});
|
||||
|
||||
return transactionsFilter;
|
||||
}
|
||||
|
||||
private populateLog(opts: GetEvmOpts, fromBlock: bigint, toBlock: bigint): string {
|
||||
return `[addresses:${opts.addresses}][topics:${opts.topics}][blocks:${fromBlock} - ${toBlock}]`;
|
||||
}
|
||||
}
|
||||
|
||||
type Range = {
|
||||
fromBlock: bigint;
|
||||
toBlock: bigint;
|
||||
};
|
|
@ -0,0 +1,47 @@
|
|||
import { HandleEvmLogsConfig } from "./HandleEvmLogs";
|
||||
import { EvmTransaction, TransactionFound } from "../../entities";
|
||||
|
||||
/**
|
||||
* Handling means mapping and forward to a given target.
|
||||
* As of today, we have mapped this event evmFailedRedeemed, evmStandardRelayDelivered and evmTransferRedeemed.
|
||||
*/
|
||||
export class HandleEvmTransactions<T> {
|
||||
cfg: HandleEvmLogsConfig;
|
||||
mapper: (log: EvmTransaction) => T;
|
||||
target: (parsed: T[]) => Promise<void>;
|
||||
|
||||
constructor(
|
||||
cfg: HandleEvmLogsConfig,
|
||||
mapper: (log: EvmTransaction) => T,
|
||||
target: (parsed: T[]) => Promise<void>
|
||||
) {
|
||||
this.cfg = this.normalizeCfg(cfg);
|
||||
this.mapper = mapper;
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
public async handle(transactions: EvmTransaction[]): Promise<T[]> {
|
||||
const mappedItems = transactions.map((transaction) => {
|
||||
return this.mapper(transaction);
|
||||
}) as TransactionFound[];
|
||||
|
||||
const filterItems = mappedItems.filter(
|
||||
(transaction) => transaction.methodsByAddress || transaction.name
|
||||
) as T[];
|
||||
|
||||
await this.target(filterItems);
|
||||
|
||||
// TODO: return a result specifying failures if any
|
||||
return filterItems;
|
||||
}
|
||||
|
||||
private normalizeCfg(cfg: HandleEvmLogsConfig): HandleEvmLogsConfig {
|
||||
return {
|
||||
filter: {
|
||||
addresses: cfg.filter.addresses.map((addr) => addr.toLowerCase()),
|
||||
topics: cfg.filter.topics.map((topic) => topic.toLowerCase()),
|
||||
},
|
||||
abi: cfg.abi,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,40 +1,46 @@
|
|||
import { EvmLog } from "../../entities";
|
||||
import { EvmLog, EvmTransaction } from "../../entities";
|
||||
import { RunPollingJob } from "../RunPollingJob";
|
||||
import { GetEvmLogs } from "./GetEvmLogs";
|
||||
import { EvmBlockRepository, MetadataRepository, StatRepository } from "../../repositories";
|
||||
import winston from "winston";
|
||||
import { GetEvmTransactions } from "./GetEvmTransactions";
|
||||
|
||||
const ID = "watch-evm-logs";
|
||||
|
||||
/**
|
||||
* PollEvmLogs is an action that watches for new blocks and extracts logs from them.
|
||||
* PollEvm is an action that watches for new blocks and extracts logs from them.
|
||||
*/
|
||||
export class PollEvmLogs extends RunPollingJob {
|
||||
export class PollEvm extends RunPollingJob {
|
||||
protected readonly logger: winston.Logger;
|
||||
|
||||
private readonly blockRepo: EvmBlockRepository;
|
||||
private readonly metadataRepo: MetadataRepository<PollEvmLogsMetadata>;
|
||||
private readonly statsRepository: StatRepository;
|
||||
private readonly getEvmLogs: GetEvmLogs;
|
||||
private cfg: PollEvmLogsConfig;
|
||||
private readonly getEvm: GetEvmLogs;
|
||||
|
||||
private cfg: PollEvmLogsConfig;
|
||||
private latestBlockHeight?: bigint;
|
||||
private blockHeightCursor?: bigint;
|
||||
private lastRange?: { fromBlock: bigint; toBlock: bigint };
|
||||
private getEvmRecords: { [key: string]: any } = {
|
||||
GetEvmLogs,
|
||||
GetEvmTransactions,
|
||||
};
|
||||
|
||||
constructor(
|
||||
blockRepo: EvmBlockRepository,
|
||||
metadataRepo: MetadataRepository<PollEvmLogsMetadata>,
|
||||
statsRepository: StatRepository,
|
||||
cfg: PollEvmLogsConfig
|
||||
cfg: PollEvmLogsConfig,
|
||||
getEvm: string
|
||||
) {
|
||||
super(cfg.interval ?? 1_000, cfg.id, statsRepository);
|
||||
this.blockRepo = blockRepo;
|
||||
this.metadataRepo = metadataRepo;
|
||||
this.statsRepository = statsRepository;
|
||||
this.cfg = cfg;
|
||||
this.getEvmLogs = new GetEvmLogs(blockRepo);
|
||||
this.logger = winston.child({ module: "PollEvmLogs", label: this.cfg.id });
|
||||
this.logger = winston.child({ module: "PollEvm", label: this.cfg.id });
|
||||
this.getEvm = new this.getEvmRecords[getEvm ?? "GetEvmLogs"](blockRepo);
|
||||
}
|
||||
|
||||
protected async preHook(): Promise<void> {
|
||||
|
@ -48,14 +54,14 @@ export class PollEvmLogs extends RunPollingJob {
|
|||
const hasFinished = this.cfg.hasFinished(this.blockHeightCursor);
|
||||
if (hasFinished) {
|
||||
this.logger.info(
|
||||
`[hasNext] PollEvmLogs: (${this.cfg.id}) Finished processing all blocks from ${this.cfg.fromBlock} to ${this.cfg.toBlock}`
|
||||
`[hasNext] PollEvm: (${this.cfg.id}) Finished processing all blocks from ${this.cfg.fromBlock} to ${this.cfg.toBlock}`
|
||||
);
|
||||
}
|
||||
|
||||
return !hasFinished;
|
||||
}
|
||||
|
||||
protected async get(): Promise<EvmLog[]> {
|
||||
protected async get(): Promise<EvmLog[] | EvmTransaction[]> {
|
||||
this.report();
|
||||
|
||||
this.latestBlockHeight = await this.blockRepo.getBlockHeight(
|
||||
|
@ -65,15 +71,16 @@ export class PollEvmLogs extends RunPollingJob {
|
|||
|
||||
const range = this.getBlockRange(this.latestBlockHeight);
|
||||
|
||||
const logs = await this.getEvmLogs.execute(range, {
|
||||
const records = await this.getEvm.execute(range, {
|
||||
chain: this.cfg.chain,
|
||||
addresses: this.cfg.addresses,
|
||||
topics: this.cfg.topics,
|
||||
environment: this.cfg.environment,
|
||||
});
|
||||
|
||||
this.lastRange = range;
|
||||
|
||||
return logs;
|
||||
return records;
|
||||
}
|
||||
|
||||
protected async persist(): Promise<void> {
|
||||
|
@ -149,6 +156,7 @@ export interface PollEvmLogsConfigProps {
|
|||
topics: string[];
|
||||
id?: string;
|
||||
chain: string;
|
||||
environment: string;
|
||||
}
|
||||
|
||||
export class PollEvmLogsConfig {
|
||||
|
@ -210,7 +218,11 @@ export class PollEvmLogsConfig {
|
|||
return this.props.chain;
|
||||
}
|
||||
|
||||
public get environment() {
|
||||
return this.props.environment;
|
||||
}
|
||||
|
||||
static fromBlock(chain: string, fromBlock: bigint) {
|
||||
return new PollEvmLogsConfig({ chain, fromBlock, addresses: [], topics: [] });
|
||||
return new PollEvmLogsConfig({ chain, fromBlock, addresses: [], topics: [], environment: "" });
|
||||
}
|
||||
}
|
|
@ -0,0 +1,304 @@
|
|||
import { EvmTransaction } from "../../../entities";
|
||||
|
||||
const TESTNET_ENVIRONMENT = "testnet";
|
||||
|
||||
export const methodNameByAddressMapper = (
|
||||
chain: string,
|
||||
environment: string,
|
||||
transaction: EvmTransaction
|
||||
): Protocol | undefined => {
|
||||
const address = transaction.to;
|
||||
const input = transaction.input;
|
||||
|
||||
if (environment == TESTNET_ENVIRONMENT) {
|
||||
return methodsByAddressTestnet(chain, address, input);
|
||||
} else {
|
||||
return methodsByAddressMainnet(chain, address, input);
|
||||
}
|
||||
};
|
||||
|
||||
const methodsByAddressTestnet = (
|
||||
chain: string,
|
||||
address: string,
|
||||
input: string
|
||||
): Protocol | undefined => {
|
||||
const testnet: MethodsByAddress = {
|
||||
ethereum: [
|
||||
{
|
||||
[String("0xF890982f9310df57d00f659cf4fd87e65adEd8d7").toLowerCase()]: ethBase,
|
||||
},
|
||||
{
|
||||
[String("0x9563a59C15842a6f322B10f69d1dD88b41f2E97B").toLowerCase()]:
|
||||
completeTransferWithRelay,
|
||||
},
|
||||
],
|
||||
polygon: [
|
||||
{
|
||||
[String("0x377D55a7928c046E18eEbb61977e714d2a76472a").toLowerCase()]: ethBase,
|
||||
},
|
||||
{
|
||||
[String("0x9563a59C15842a6f322B10f69d1dD88b41f2E97B").toLowerCase()]:
|
||||
completeTransferWithRelay,
|
||||
},
|
||||
{
|
||||
[String("0xc3D46e0266d95215589DE639cC4E93b79f88fc6C").toLowerCase()]: receiveTbtc,
|
||||
},
|
||||
],
|
||||
bsc: [
|
||||
{
|
||||
[String("0x9dcF9D205C9De35334D646BeE44b2D2859712A09").toLowerCase()]: ethBase,
|
||||
},
|
||||
{
|
||||
[String("0x9563a59C15842a6f322B10f69d1dD88b41f2E97B").toLowerCase()]:
|
||||
completeTransferWithRelay,
|
||||
},
|
||||
],
|
||||
fantom: [
|
||||
{
|
||||
[String("0x599CEa2204B4FaECd584Ab1F2b6aCA137a0afbE8").toLowerCase()]: ethBase,
|
||||
},
|
||||
{
|
||||
[String("0x9563a59C15842a6f322B10f69d1dD88b41f2E97B").toLowerCase()]:
|
||||
completeTransferWithRelay,
|
||||
},
|
||||
],
|
||||
avalanche: [
|
||||
{
|
||||
[String("0x61E44E506Ca5659E6c0bba9b678586fA2d729756").toLowerCase()]: ethBase,
|
||||
},
|
||||
{
|
||||
[String("0x9563a59C15842a6f322B10f69d1dD88b41f2E97B").toLowerCase()]:
|
||||
completeTransferWithRelay,
|
||||
},
|
||||
],
|
||||
oasis: [
|
||||
{
|
||||
[String("0x88d8004A9BdbfD9D28090A02010C19897a29605c").toLowerCase()]: ethBase,
|
||||
},
|
||||
],
|
||||
moonbean: [
|
||||
{
|
||||
[String("0xbc976D4b9D57E57c3cA52e1Fd136C45FF7955A96").toLowerCase()]: ethBase,
|
||||
},
|
||||
{
|
||||
[String("0x9563a59C15842a6f322B10f69d1dD88b41f2E97B").toLowerCase()]:
|
||||
completeTransferWithRelay,
|
||||
},
|
||||
],
|
||||
celo: [
|
||||
{
|
||||
[String("0x05ca6037eC51F8b712eD2E6Fa72219FEaE74E153").toLowerCase()]: ethBase,
|
||||
},
|
||||
{
|
||||
[String("0x9563a59C15842a6f322B10f69d1dD88b41f2E97B").toLowerCase()]:
|
||||
completeTransferWithRelay,
|
||||
},
|
||||
],
|
||||
arbitrum: [
|
||||
{
|
||||
[String("0xe3e0511EEbD87F08FbaE4486419cb5dFB06e1343").toLowerCase()]: receiveTbtc,
|
||||
},
|
||||
],
|
||||
optimism: [
|
||||
{
|
||||
[String("0xc3D46e0266d95215589DE639cC4E93b79f88fc6C").toLowerCase()]: receiveTbtc,
|
||||
},
|
||||
],
|
||||
base: [
|
||||
{
|
||||
[String("0xA31aa3FDb7aF7Db93d18DDA4e19F811342EDF780").toLowerCase()]: base,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return findMethodName(testnet, chain, address, input);
|
||||
};
|
||||
|
||||
const methodsByAddressMainnet = (
|
||||
chain: string,
|
||||
address: string,
|
||||
input: string
|
||||
): Protocol | undefined => {
|
||||
const mainnet: MethodsByAddress = {
|
||||
ethereum: [
|
||||
{
|
||||
[String("0x3ee18B2214AFF97000D974cf647E7C347E8fa585").toLowerCase()]: ethBase,
|
||||
},
|
||||
{
|
||||
[String("0xcafd2f0a35a4459fa40c0517e17e6fa2939441ca").toLowerCase()]:
|
||||
completeTransferWithRelay,
|
||||
},
|
||||
{
|
||||
[String("0xd8E1465908103eD5fd28e381920575fb09beb264").toLowerCase()]: receiveMessageAndSwap,
|
||||
},
|
||||
],
|
||||
polygon: [
|
||||
{
|
||||
[String("0x5a58505a96D1dbf8dF91cB21B54419FC36e93fdE").toLowerCase()]: ethBase,
|
||||
},
|
||||
{
|
||||
[String("0xcafd2f0a35a4459fa40c0517e17e6fa2939441ca").toLowerCase()]:
|
||||
completeTransferWithRelay,
|
||||
},
|
||||
{
|
||||
[String("0x09959798B95d00a3183d20FaC298E4594E599eab").toLowerCase()]: receiveTbtc,
|
||||
},
|
||||
{
|
||||
[String("0xf6C5FD2C8Ecba25420859f61Be0331e68316Ba01").toLowerCase()]: receiveMessageAndSwap,
|
||||
},
|
||||
],
|
||||
bsc: [
|
||||
{
|
||||
[String("0xB6F6D86a8f9879A9c87f643768d9efc38c1Da6E7").toLowerCase()]: ethBase,
|
||||
},
|
||||
{
|
||||
[String("0xcafd2f0a35a4459fa40c0517e17e6fa2939441ca").toLowerCase()]:
|
||||
completeTransferWithRelay,
|
||||
},
|
||||
],
|
||||
fantom: [
|
||||
{
|
||||
[String("0x7C9Fc5741288cDFdD83CeB07f3ea7e22618D79D2").toLowerCase()]: ethBase,
|
||||
},
|
||||
{
|
||||
[String("0xcafd2f0a35a4459fa40c0517e17e6fa2939441ca").toLowerCase()]:
|
||||
completeTransferWithRelay,
|
||||
},
|
||||
],
|
||||
avalanche: [
|
||||
{
|
||||
[String("0x0e082F06FF657D94310cB8cE8B0D9a04541d8052").toLowerCase()]: ethBase,
|
||||
},
|
||||
{
|
||||
[String("0xcafd2f0a35a4459fa40c0517e17e6fa2939441ca").toLowerCase()]:
|
||||
completeTransferWithRelay,
|
||||
},
|
||||
],
|
||||
oasis: [
|
||||
{
|
||||
[String("0x5848C791e09901b40A9Ef749f2a6735b418d7564").toLowerCase()]: ethBase,
|
||||
},
|
||||
],
|
||||
moonbean: [
|
||||
{
|
||||
[String("0xb1731c586ca89a23809861c6103f0b96b3f57d92").toLowerCase()]: ethBase,
|
||||
},
|
||||
{
|
||||
[String("0xcafd2f0a35a4459fa40c0517e17e6fa2939441ca").toLowerCase()]:
|
||||
completeTransferWithRelay,
|
||||
},
|
||||
],
|
||||
celo: [
|
||||
{
|
||||
[String("0x796Dff6D74F3E27060B71255Fe517BFb23C93eed").toLowerCase()]: ethBase,
|
||||
},
|
||||
{
|
||||
[String("0xcafd2f0a35a4459fa40c0517e17e6fa2939441ca").toLowerCase()]:
|
||||
completeTransferWithRelay,
|
||||
},
|
||||
],
|
||||
arbitrum: [
|
||||
{
|
||||
[String("0x1293a54e160D1cd7075487898d65266081A15458").toLowerCase()]: receiveTbtc,
|
||||
},
|
||||
{
|
||||
[String("0xf8497FE5B0C5373778BFa0a001d476A21e01f09b").toLowerCase()]: receiveMessageAndSwap,
|
||||
},
|
||||
],
|
||||
optimism: [
|
||||
{
|
||||
[String("0x1293a54e160D1cd7075487898d65266081A15458").toLowerCase()]: receiveTbtc,
|
||||
},
|
||||
{
|
||||
[String("0xcF205Fa51D33280D9B70321Ae6a3686FB2c178b2").toLowerCase()]: receiveMessageAndSwap,
|
||||
},
|
||||
],
|
||||
base: [
|
||||
{
|
||||
[String("0x8d2de8d2f73F1F4cAB472AC9A881C9b123C79627").toLowerCase()]: base,
|
||||
},
|
||||
{
|
||||
[String("0x9816d7C448f79CdD4aF18c4Ae1726A14299E8C75").toLowerCase()]: receiveMessageAndSwap,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
return findMethodName(mainnet, chain, address, input);
|
||||
};
|
||||
|
||||
const findMethodName = (
|
||||
environment: MethodsByAddress,
|
||||
chain: string,
|
||||
address: string,
|
||||
input: string
|
||||
): Protocol | undefined => {
|
||||
const first10Characters = input.slice(0, 10);
|
||||
let protocol: Protocol | undefined;
|
||||
|
||||
environment[chain]?.find((addresses) => {
|
||||
const protocols = addresses[address];
|
||||
const foundProtocol = protocols?.get(first10Characters);
|
||||
protocol = foundProtocol;
|
||||
return foundProtocol;
|
||||
});
|
||||
|
||||
return protocol;
|
||||
};
|
||||
|
||||
export enum MethodID {
|
||||
// Method ids for wormhole token bridge contract
|
||||
MethodIDCompleteTransfer = "0xc6878519",
|
||||
MethodIDWrapAndTransfer = "0x9981509f",
|
||||
MethodIDTransferTokens = "0x0f5287b0",
|
||||
MethodIDAttestToken = "0xc48fa115",
|
||||
MethodIDCompleteAndUnwrapETH = "0xff200cde",
|
||||
MethodIDCreateWrapped = "0xe8059810",
|
||||
MethodIDUpdateWrapped = "0xf768441f",
|
||||
// Method id for wormhole connect wrapped contract.
|
||||
MethodCompleteTransferWithRelay = "0x2f25e25f",
|
||||
// Method id for wormhole tBTC gateway
|
||||
MethodIDReceiveTbtc = "0x5d21a596",
|
||||
// Method id for Portico contract
|
||||
MethodIDReceiveMessageAndSwap = "0x3d528f35",
|
||||
}
|
||||
|
||||
const ethBase = new Map<string, Protocol>([
|
||||
[
|
||||
MethodID.MethodIDCompleteTransfer,
|
||||
{ method: "MethodCompleteTransfer", name: "transfer-redeemed" },
|
||||
],
|
||||
[
|
||||
MethodID.MethodIDCompleteAndUnwrapETH,
|
||||
{ method: "MethodCompleteAndUnwrapETH", name: "transfer-redeemed" },
|
||||
],
|
||||
[MethodID.MethodIDCreateWrapped, { method: "MethodCreateWrapped", name: "transfer-redeemed" }],
|
||||
[MethodID.MethodIDUpdateWrapped, { method: "MethodUpdateWrapped", name: "transfer-redeemed" }],
|
||||
]);
|
||||
|
||||
const completeTransferWithRelay = new Map<string, Protocol>([
|
||||
[
|
||||
MethodID.MethodCompleteTransferWithRelay,
|
||||
{ method: "MethodCompleteTransferWithRelay", name: "standard-relay-delivered" },
|
||||
],
|
||||
]);
|
||||
|
||||
const receiveMessageAndSwap = new Map<string, Protocol>([
|
||||
[MethodID.MethodIDReceiveMessageAndSwap, { method: "MethodReceiveMessageAndSwap", name: "" }], // TODO: When active this protocol set the name
|
||||
]);
|
||||
|
||||
const receiveTbtc = new Map<string, Protocol>([
|
||||
[MethodID.MethodIDReceiveTbtc, { method: "MethodReceiveTbtc", name: "" }], // TODO: When active this protocol set the name
|
||||
]);
|
||||
|
||||
const base = new Map<string, Protocol>([...ethBase, ...completeTransferWithRelay]);
|
||||
|
||||
type MethodsByAddress = {
|
||||
[chain: string]: {
|
||||
[address: string]: Map<string, Protocol>;
|
||||
}[];
|
||||
};
|
||||
|
||||
type Protocol = {
|
||||
method: string;
|
||||
name: string;
|
||||
};
|
|
@ -1,6 +1,7 @@
|
|||
export * from "./evm/HandleEvmLogs";
|
||||
export * from "./evm/HandleEvmTransactions";
|
||||
export * from "./evm/GetEvmLogs";
|
||||
export * from "./evm/PollEvmLogs";
|
||||
export * from "./evm/PollEvm";
|
||||
export * from "./solana/GetSolanaTransactions";
|
||||
export * from "./solana/PollSolanaTransactions";
|
||||
export * from "./RunPollingJob";
|
||||
|
|
|
@ -33,3 +33,35 @@ export type StandardRelayDelivered = {
|
|||
additionalStatusInfo: string;
|
||||
overridesInfo: string;
|
||||
};
|
||||
|
||||
export type TransactionFoundEvent<T> = {
|
||||
name: string;
|
||||
address: string;
|
||||
txHash: string;
|
||||
blockHeight: bigint;
|
||||
chainId: number;
|
||||
attributes: T;
|
||||
};
|
||||
|
||||
export type TransactionFound = {
|
||||
name?: string;
|
||||
from: string;
|
||||
to: string;
|
||||
status?: string;
|
||||
blockNumber: bigint;
|
||||
input: string;
|
||||
methodsByAddress?: string;
|
||||
timestamp: number;
|
||||
blockHash: string;
|
||||
gas: string;
|
||||
gasPrice: string;
|
||||
maxFeePerGas: string;
|
||||
maxPriorityFeePerGas: string;
|
||||
nonce: string;
|
||||
r: string;
|
||||
s: string;
|
||||
transactionIndex: string;
|
||||
type: string;
|
||||
v: string;
|
||||
value: string;
|
||||
};
|
||||
|
|
|
@ -2,6 +2,7 @@ export type EvmBlock = {
|
|||
number: bigint;
|
||||
hash: string;
|
||||
timestamp: number; // epoch seconds
|
||||
transactions?: EvmTransaction[];
|
||||
};
|
||||
|
||||
export type EvmLog = {
|
||||
|
@ -18,6 +19,31 @@ export type EvmLog = {
|
|||
chainId: number;
|
||||
};
|
||||
|
||||
export type EvmTransaction = {
|
||||
blockHash: string;
|
||||
blockNumber: bigint;
|
||||
chainId: number;
|
||||
from: string;
|
||||
gas: string;
|
||||
gasPrice: string;
|
||||
hash: string;
|
||||
input: string;
|
||||
maxFeePerGas: string;
|
||||
maxPriorityFeePerGas: string;
|
||||
nonce: string;
|
||||
r: string;
|
||||
s: string;
|
||||
status?: string;
|
||||
to: string;
|
||||
transactionIndex: string;
|
||||
type: string;
|
||||
v: string;
|
||||
value: string;
|
||||
timestamp: number;
|
||||
environment: string;
|
||||
chain: string;
|
||||
};
|
||||
|
||||
export type EvmTag = "finalized" | "latest" | "safe";
|
||||
|
||||
export type EvmTopicFilter = {
|
||||
|
@ -31,3 +57,8 @@ export type EvmLogFilter = {
|
|||
addresses: string[];
|
||||
topics: string[];
|
||||
};
|
||||
|
||||
export type ReceiptTransaction = {
|
||||
status: string;
|
||||
transactionHash: string;
|
||||
};
|
||||
|
|
|
@ -3,6 +3,7 @@ export class JobDefinition {
|
|||
chain: string;
|
||||
source: {
|
||||
action: string;
|
||||
records: string;
|
||||
config: Record<string, any>;
|
||||
};
|
||||
handlers: {
|
||||
|
@ -15,7 +16,7 @@ export class JobDefinition {
|
|||
constructor(
|
||||
id: string,
|
||||
chain: string,
|
||||
source: { action: string; config: Record<string, any> },
|
||||
source: { action: string; records: string; config: Record<string, any> },
|
||||
handlers: { action: string; target: string; mapper: string; config: Record<string, any> }[]
|
||||
) {
|
||||
this.id = id;
|
||||
|
|
|
@ -1,5 +1,14 @@
|
|||
import { RunPollingJob } from "./actions/RunPollingJob";
|
||||
import { EvmBlock, EvmLog, EvmLogFilter, Handler, JobDefinition, solana } from "./entities";
|
||||
import {
|
||||
EvmBlock,
|
||||
EvmLog,
|
||||
EvmLogFilter,
|
||||
EvmTag,
|
||||
Handler,
|
||||
JobDefinition,
|
||||
ReceiptTransaction,
|
||||
solana,
|
||||
} from "./entities";
|
||||
import { ConfirmedSignatureInfo } from "./entities/solana";
|
||||
import { Fallible, SolanaFailure } from "./errors";
|
||||
|
||||
|
@ -7,6 +16,15 @@ export interface EvmBlockRepository {
|
|||
getBlockHeight(chain: string, finality: string): Promise<bigint>;
|
||||
getBlocks(chain: string, blockNumbers: Set<bigint>): Promise<Record<string, EvmBlock>>;
|
||||
getFilteredLogs(chain: string, filter: EvmLogFilter): Promise<EvmLog[]>;
|
||||
getTransactionReceipt(
|
||||
chain: string,
|
||||
hashNumbers: Set<string>
|
||||
): Promise<Record<string, ReceiptTransaction>>;
|
||||
getBlock(
|
||||
chain: string,
|
||||
blockNumberOrTag: EvmTag | bigint,
|
||||
isTransactionsPresent: boolean
|
||||
): Promise<EvmBlock>;
|
||||
}
|
||||
|
||||
export interface SolanaSlotRepository {
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
import { BigNumber } from "ethers";
|
||||
import { EvmLog, LogFoundEvent, StandardRelayDelivered } from "../../domain/entities";
|
||||
|
||||
/*
|
||||
* Delivery (index_topic_1 address recipientContract, index_topic_2 uint16 sourceChain, index_topic_3 uint64 sequence, bytes32 deliveryVaaHash, uint8 status, uint256 gasUsed, uint8 refundStatus, bytes additionalStatusInfo, bytes overridesInfo)
|
||||
*/
|
||||
export const evmStandardRelayDelivered = (
|
||||
log: EvmLog,
|
||||
args: ReadonlyArray<any>
|
||||
): LogFoundEvent<StandardRelayDelivered> => {
|
||||
if (!log.blockTime) {
|
||||
throw new Error(`Block time is missing for log ${log.logIndex} in tx ${log.transactionHash}`);
|
||||
}
|
||||
|
||||
return {
|
||||
name: "standard-relay-delivered",
|
||||
address: log.address,
|
||||
chainId: log.chainId,
|
||||
txHash: log.transactionHash,
|
||||
blockHeight: log.blockNumber,
|
||||
blockTime: log.blockTime,
|
||||
attributes: {
|
||||
recipientContract: args[0],
|
||||
sourceChain: BigNumber.from(args[1]).toNumber(),
|
||||
sequence: BigNumber.from(args[2]).toNumber(),
|
||||
deliveryVaaHash: args[3],
|
||||
status: BigNumber.from(args[4]).toNumber(),
|
||||
gasUsed: BigNumber.from(args[5]).toNumber(),
|
||||
refundStatus: BigNumber.from(args[6]).toNumber(),
|
||||
additionalStatusInfo: args[7],
|
||||
overridesInfo: args[8],
|
||||
},
|
||||
};
|
||||
};
|
|
@ -0,0 +1,42 @@
|
|||
import { methodNameByAddressMapper } from "../../domain/actions/evm/mappers/methodNameByAddressMapper";
|
||||
import { EvmTransaction, TransactionFound, TransactionFoundEvent } from "../../domain/entities";
|
||||
|
||||
export const evmTransactionFoundMapper = (
|
||||
transaction: EvmTransaction
|
||||
): TransactionFoundEvent<TransactionFound> => {
|
||||
const protocol = methodNameByAddressMapper(
|
||||
transaction.chain,
|
||||
transaction.environment,
|
||||
transaction
|
||||
);
|
||||
|
||||
return {
|
||||
name: "evm-transaction-found",
|
||||
address: transaction.to,
|
||||
chainId: transaction.chainId,
|
||||
txHash: transaction.hash,
|
||||
blockHeight: BigInt(transaction.blockNumber),
|
||||
attributes: {
|
||||
name: protocol?.name,
|
||||
from: transaction.from,
|
||||
to: transaction.to,
|
||||
status: transaction.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,
|
||||
},
|
||||
};
|
||||
};
|
|
@ -1,25 +0,0 @@
|
|||
import { BigNumber } from "ethers";
|
||||
import { EvmLog, LogFoundEvent, TransferRedeemed } from "../../domain/entities";
|
||||
|
||||
export const evmTransferRedeemedMapper = (
|
||||
log: EvmLog,
|
||||
_: ReadonlyArray<any>
|
||||
): LogFoundEvent<TransferRedeemed> => {
|
||||
if (!log.blockTime) {
|
||||
throw new Error(`Block time is missing for log ${log.logIndex} in tx ${log.transactionHash}`);
|
||||
}
|
||||
|
||||
return {
|
||||
name: "transfer-redeemed",
|
||||
address: log.address,
|
||||
chainId: log.chainId,
|
||||
txHash: log.transactionHash,
|
||||
blockHeight: log.blockNumber,
|
||||
blockTime: log.blockTime,
|
||||
attributes: {
|
||||
emitterChainId: Number(log.topics[1]),
|
||||
emitterAddress: log.topics[2],
|
||||
sequence: BigNumber.from(log.topics[3]).toNumber(),
|
||||
},
|
||||
};
|
||||
};
|
|
@ -1,5 +1,4 @@
|
|||
export * from "./evmLogMessagePublishedMapper";
|
||||
export * from "./evmTransferRedeemedMapper";
|
||||
export * from "./evmStandardRelayDelivered";
|
||||
export * from "./evmTransactionFoundMapper";
|
||||
export * from "./solanaLogMessagePublishedMapper";
|
||||
export * from "./solanaTransferRedeemedMapper";
|
||||
|
|
|
@ -93,6 +93,7 @@ export class RepositoriesBuilder {
|
|||
this.repositories.set(
|
||||
"jobs",
|
||||
new StaticJobRepository(
|
||||
this.cfg.environment,
|
||||
this.cfg.jobs.dir,
|
||||
this.cfg.dryRun,
|
||||
(chain: string) => this.getEvmBlockRepository(chain),
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import {
|
||||
HandleEvmLogs,
|
||||
PollEvmLogs,
|
||||
PollEvm,
|
||||
PollEvmLogsConfig,
|
||||
PollEvmLogsConfigProps,
|
||||
PollSolanaTransactions,
|
||||
|
@ -21,13 +21,14 @@ import {
|
|||
solanaLogMessagePublishedMapper,
|
||||
solanaTransferRedeemedMapper,
|
||||
evmLogMessagePublishedMapper,
|
||||
evmStandardRelayDelivered,
|
||||
evmTransferRedeemedMapper,
|
||||
evmTransactionFoundMapper,
|
||||
} from "../mappers";
|
||||
import log from "../log";
|
||||
import { HandleEvmTransactions } from "../../domain/actions/evm/HandleEvmTransactions";
|
||||
|
||||
export class StaticJobRepository implements JobRepository {
|
||||
private fileRepo: FileMetadataRepository;
|
||||
private environment: string;
|
||||
private dryRun: boolean = false;
|
||||
private sources: Map<string, (def: JobDefinition) => RunPollingJob> = new Map();
|
||||
private handlers: Map<string, (cfg: any, target: string, mapper: any) => Promise<Handler>> =
|
||||
|
@ -41,6 +42,7 @@ export class StaticJobRepository implements JobRepository {
|
|||
private solanaSlotRepo: SolanaSlotRepository;
|
||||
|
||||
constructor(
|
||||
environment: string,
|
||||
path: string,
|
||||
dryRun: boolean,
|
||||
blockRepoProvider: (chain: string) => EvmBlockRepository,
|
||||
|
@ -57,6 +59,7 @@ export class StaticJobRepository implements JobRepository {
|
|||
this.statsRepo = repos.statsRepo;
|
||||
this.snsRepo = repos.snsRepo;
|
||||
this.solanaSlotRepo = repos.solanaSlotRepo;
|
||||
this.environment = environment;
|
||||
this.dryRun = dryRun;
|
||||
this.fill();
|
||||
}
|
||||
|
@ -98,28 +101,29 @@ export class StaticJobRepository implements JobRepository {
|
|||
|
||||
private fill() {
|
||||
// Actions
|
||||
const pollEvmLogs = (jobDef: JobDefinition) =>
|
||||
new PollEvmLogs(
|
||||
const pollEvm = (jobDef: JobDefinition) =>
|
||||
new PollEvm(
|
||||
this.blockRepoProvider(jobDef.source.config.chain),
|
||||
this.metadataRepo,
|
||||
this.statsRepo,
|
||||
new PollEvmLogsConfig({
|
||||
...(jobDef.source.config as PollEvmLogsConfigProps),
|
||||
id: jobDef.id,
|
||||
})
|
||||
environment: this.environment,
|
||||
}),
|
||||
jobDef.source.records
|
||||
);
|
||||
const pollSolanaTransactions = (jobDef: JobDefinition) =>
|
||||
new PollSolanaTransactions(this.metadataRepo, this.solanaSlotRepo, this.statsRepo, {
|
||||
...(jobDef.source.config as PollSolanaTransactionsConfig),
|
||||
id: jobDef.id,
|
||||
});
|
||||
this.sources.set("PollEvmLogs", pollEvmLogs);
|
||||
this.sources.set("PollEvm", pollEvm);
|
||||
this.sources.set("PollSolanaTransactions", pollSolanaTransactions);
|
||||
|
||||
// Mappers
|
||||
this.mappers.set("evmLogMessagePublishedMapper", evmLogMessagePublishedMapper);
|
||||
this.mappers.set("evmStandardRelayDelivered", evmStandardRelayDelivered);
|
||||
this.mappers.set("evmTransferRedeemedMapper", evmTransferRedeemedMapper);
|
||||
this.mappers.set("evmTransactionFoundMapper", evmTransactionFoundMapper);
|
||||
this.mappers.set("solanaLogMessagePublishedMapper", solanaLogMessagePublishedMapper);
|
||||
this.mappers.set("solanaTransferRedeemedMapper", solanaTransferRedeemedMapper);
|
||||
|
||||
|
@ -141,12 +145,22 @@ export class StaticJobRepository implements JobRepository {
|
|||
|
||||
return instance.handle.bind(instance);
|
||||
};
|
||||
const handleEvmTransactions = async (config: any, target: string, mapper: any) => {
|
||||
const instance = new HandleEvmTransactions<LogFoundEvent<any>>(
|
||||
config,
|
||||
mapper,
|
||||
await this.targets.get(this.dryRun ? "dummy" : target)!()
|
||||
);
|
||||
|
||||
return instance.handle.bind(instance);
|
||||
};
|
||||
const handleSolanaTx = async (config: any, target: string, mapper: any) => {
|
||||
const instance = new HandleSolanaTransactions(config, mapper, await this.getTarget(target));
|
||||
|
||||
return instance.handle.bind(instance);
|
||||
};
|
||||
this.handlers.set("HandleEvmLogs", handleEvmLogs);
|
||||
this.handlers.set("HandleEvmTransactions", handleEvmTransactions);
|
||||
this.handlers.set("HandleSolanaTransactions", handleSolanaTx);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
import { EvmBlock, EvmLogFilter, EvmLog, EvmTag } from "../../../domain/entities";
|
||||
import {
|
||||
EvmBlock,
|
||||
EvmLogFilter,
|
||||
EvmLog,
|
||||
EvmTag,
|
||||
ReceiptTransaction,
|
||||
} from "../../../domain/entities";
|
||||
import { EvmBlockRepository } from "../../../domain/repositories";
|
||||
import winston from "../../log";
|
||||
import { HttpClient } from "../../rpc/http/HttpClient";
|
||||
|
@ -173,7 +179,11 @@ export class EvmJsonRPCBlockRepository implements EvmBlockRepository {
|
|||
/**
|
||||
* Loosely based on the wormhole-dashboard implementation (minus some specially crafted blocks when null result is obtained)
|
||||
*/
|
||||
protected async getBlock(chain: string, blockNumberOrTag: EvmTag | bigint): Promise<EvmBlock> {
|
||||
async getBlock(
|
||||
chain: string,
|
||||
blockNumberOrTag: EvmTag | bigint,
|
||||
isTransactionsPresent: boolean = false
|
||||
): Promise<EvmBlock> {
|
||||
const blockNumberParam =
|
||||
typeof blockNumberOrTag === "bigint"
|
||||
? `${HEXADECIMAL_PREFIX}${blockNumberOrTag.toString(16)}`
|
||||
|
@ -187,7 +197,7 @@ export class EvmJsonRPCBlockRepository implements EvmBlockRepository {
|
|||
{
|
||||
jsonrpc: "2.0",
|
||||
method: "eth_getBlockByNumber",
|
||||
params: [blockNumberParam, false], // this means we'll get a light block (no txs)
|
||||
params: [blockNumberParam, isTransactionsPresent], // this means we'll get a light block (no txs)
|
||||
id: 1,
|
||||
},
|
||||
{ timeout: chainCfg.timeout, retries: chainCfg.retries }
|
||||
|
@ -205,6 +215,7 @@ export class EvmJsonRPCBlockRepository implements EvmBlockRepository {
|
|||
number: BigInt(result.number),
|
||||
timestamp: Number(result.timestamp),
|
||||
hash: result.hash,
|
||||
transactions: result.transactions,
|
||||
};
|
||||
}
|
||||
throw new Error(
|
||||
|
@ -212,6 +223,67 @@ export class EvmJsonRPCBlockRepository implements EvmBlockRepository {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the transaction ReceiptTransaction. Hash param refers to transaction hash
|
||||
*/
|
||||
async getTransactionReceipt(
|
||||
chain: string,
|
||||
hashNumbers: Set<string>
|
||||
): Promise<Record<string, ReceiptTransaction>> {
|
||||
const chainCfg = this.getCurrentChain(chain);
|
||||
let results: { result: ReceiptTransaction; error?: ErrorBlock }[];
|
||||
|
||||
const reqs: any[] = [];
|
||||
for (let hash of hashNumbers) {
|
||||
reqs.push({
|
||||
jsonrpc: "2.0",
|
||||
id: 1,
|
||||
method: "eth_getTransactionReceipt",
|
||||
params: [hash],
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
results = await this.httpClient.post<typeof results>(chainCfg.rpc.href, reqs, {
|
||||
timeout: chainCfg.timeout,
|
||||
retries: chainCfg.retries,
|
||||
});
|
||||
} catch (e: HttpClientError | any) {
|
||||
this.handleError(chain, e, "getTransactionReceipt", "eth_getTransactionReceipt");
|
||||
throw e;
|
||||
}
|
||||
|
||||
if (results && results.length) {
|
||||
return results
|
||||
.map((response) => {
|
||||
if (response.result?.status && response.result?.transactionHash) {
|
||||
return {
|
||||
status: response.result.status,
|
||||
transactionHash: response.result.transactionHash,
|
||||
};
|
||||
}
|
||||
|
||||
const msg = `[${chain}][getTransactionReceipt] Got error ${response?.error} for eth_getTransactionReceipt for ${hashNumbers} on ${chainCfg.rpc.hostname}`;
|
||||
|
||||
this.logger.error(msg);
|
||||
|
||||
throw new Error(
|
||||
`Unable to parse result of eth_getTransactionReceipt[${chain}] for ${response?.result}: ${msg}`
|
||||
);
|
||||
})
|
||||
.reduce(
|
||||
(acc: Record<string, ReceiptTransaction>, receiptTransaction: ReceiptTransaction) => {
|
||||
acc[receiptTransaction.transactionHash] = receiptTransaction;
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
);
|
||||
}
|
||||
throw new Error(
|
||||
`Unable to parse result of eth_getTransactionReceipt for ${hashNumbers} on ${chainCfg.rpc}`
|
||||
);
|
||||
}
|
||||
|
||||
protected handleError(chain: string, e: any, method: string, apiMethod: string) {
|
||||
const chainCfg = this.getCurrentChain(chain);
|
||||
if (e instanceof HttpClientError) {
|
||||
|
|
|
@ -53,7 +53,7 @@ export class MoonbeamEvmJsonRPCBlockRepository extends EvmJsonRPCBlockRepository
|
|||
}
|
||||
|
||||
if (attempts > MAX_ATTEMPTS) {
|
||||
this.logger.error(`[getBlockHeight] The block ${blockNumber} never ended`);
|
||||
this.logger.warn(`[getBlockHeight] The block ${blockNumber} never ended`);
|
||||
throw new Error(`The block ${blockNumber} never ended`);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,177 @@
|
|||
import { afterAll, afterEach, describe, it, expect, jest } from "@jest/globals";
|
||||
import { GetEvmTransactions } from "../../../../src/domain/actions/evm/GetEvmTransactions";
|
||||
import { EvmBlockRepository } from "../../../../src/domain/repositories";
|
||||
import { EvmBlock, EvmLog, ReceiptTransaction } from "../../../../src/domain/entities/evm";
|
||||
|
||||
let getTransactionReceipt: jest.SpiedFunction<EvmBlockRepository["getTransactionReceipt"]>;
|
||||
let getBlockSpy: jest.SpiedFunction<EvmBlockRepository["getBlock"]>;
|
||||
|
||||
let getEvmTransactions: GetEvmTransactions;
|
||||
let evmBlockRepo: EvmBlockRepository;
|
||||
|
||||
describe("GetEvmTransactions", () => {
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it("should be return empty array, because formBlock is higher than toBlock", async () => {
|
||||
// Given
|
||||
const range = {
|
||||
fromBlock: 10n,
|
||||
toBlock: 1n,
|
||||
};
|
||||
|
||||
const opts = {
|
||||
addresses: [],
|
||||
topics: [],
|
||||
chain: "ethereum",
|
||||
environment: "testnet",
|
||||
};
|
||||
|
||||
givenPollEvmLogs();
|
||||
|
||||
// When
|
||||
const result = getEvmTransactions.execute(range, opts);
|
||||
|
||||
// Then
|
||||
result.then((response) => {
|
||||
expect(response).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
it("should be return empty array, because do not match any contract address with transaction address", async () => {
|
||||
// Given
|
||||
const range = {
|
||||
fromBlock: 1n,
|
||||
toBlock: 1n,
|
||||
};
|
||||
|
||||
const opts = {
|
||||
addresses: ["0x1ee18b2214aff97000d974cf647e7c545e8fa585"],
|
||||
topics: [],
|
||||
chain: "ethereum",
|
||||
environment: "mainnet",
|
||||
};
|
||||
|
||||
givenEvmBlockRepository(range.fromBlock, range.toBlock);
|
||||
givenPollEvmLogs();
|
||||
|
||||
// When
|
||||
const result = getEvmTransactions.execute(range, opts);
|
||||
|
||||
// Then
|
||||
result.then((response) => {
|
||||
expect(response).toEqual([]);
|
||||
expect(getBlockSpy).toHaveReturnedTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
it("should be return array with one transaction filter and populated", async () => {
|
||||
// Given
|
||||
const range = {
|
||||
fromBlock: 1n,
|
||||
toBlock: 1n,
|
||||
};
|
||||
|
||||
const opts = {
|
||||
addresses: ["0x3ee18b2214aff97000d974cf647e7c347e8fa585"],
|
||||
topics: [],
|
||||
chain: "ethereum",
|
||||
environment: "mainnet",
|
||||
};
|
||||
|
||||
givenEvmBlockRepository(range.fromBlock, range.toBlock);
|
||||
givenPollEvmLogs();
|
||||
|
||||
// When
|
||||
const result = getEvmTransactions.execute(range, opts);
|
||||
|
||||
// Then
|
||||
result.then((response) => {
|
||||
expect(response.length).toEqual(1);
|
||||
expect(response[0].chainId).toEqual(1);
|
||||
expect(response[0].status).toEqual("0x1");
|
||||
expect(response[0].from).toEqual("0x3ee123456786797000d974cf647e7c347e8fa585");
|
||||
expect(response[0].to).toEqual("0x3ee18b2214aff97000d974cf647e7c347e8fa585");
|
||||
expect(getTransactionReceipt).toHaveReturnedTimes(1);
|
||||
expect(getBlockSpy).toHaveReturnedTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const givenEvmBlockRepository = (height?: bigint, blocksAhead?: bigint) => {
|
||||
const logsResponse: EvmLog[] = [];
|
||||
const blocksResponse: Record<string, EvmBlock> = {};
|
||||
const receiptResponse: Record<string, ReceiptTransaction> = {};
|
||||
if (height) {
|
||||
for (let index = 0n; index <= (blocksAhead ?? 1n); index++) {
|
||||
logsResponse.push({
|
||||
blockNumber: height + index,
|
||||
blockHash: `0x0${index}`,
|
||||
blockTime: 0,
|
||||
address: "",
|
||||
removed: false,
|
||||
data: "",
|
||||
transactionHash: "",
|
||||
transactionIndex: "",
|
||||
topics: [],
|
||||
logIndex: 0,
|
||||
chainId: 2,
|
||||
});
|
||||
blocksResponse[`0x0${index}`] = {
|
||||
timestamp: 0,
|
||||
hash: `huohugigiyyff6677rr657s7xr8copi`,
|
||||
number: height + index,
|
||||
transactions: [
|
||||
{
|
||||
blockHash: "0xf5794b0970386d7951e45465ac2c9835537e5a9",
|
||||
hash: "dasdasfpialsfijlasfsahuf",
|
||||
blockNumber: 1n,
|
||||
chainId: 1,
|
||||
from: "0x3ee123456786797000d974cf647e7c347e8fa585",
|
||||
gas: "0x14485",
|
||||
gasPrice: "0xfc518561e",
|
||||
input: "0xc687851912312444wadadswadwd",
|
||||
maxFeePerGas: "0x1610f75b9a",
|
||||
maxPriorityFeePerGas: "0x5f5e100",
|
||||
nonce: "0x1",
|
||||
r: "0xf5794b0970386d73b693b17f147fae0427db278e951e45465ac2c9835537e5a9",
|
||||
s: "0x6dccc8cfee216bc43a9d66525fa94905da234ad32d6cc3220845bef78f25dd42",
|
||||
status: "0x1",
|
||||
timestamp: 12313123,
|
||||
to: "0x3ee18b2214aff97000d974cf647e7c347e8fa585",
|
||||
transactionIndex: "0x6f",
|
||||
type: "0x2",
|
||||
v: "0x1",
|
||||
value: "0x5b09cd3e5e90000",
|
||||
environment: "testnet",
|
||||
chain: "ethereum",
|
||||
},
|
||||
],
|
||||
};
|
||||
receiptResponse["dasdasfpialsfijlasfsahuf"] = {
|
||||
status: "0x1",
|
||||
transactionHash: "dasdasfpialsfijlasfsahuf",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
evmBlockRepo = {
|
||||
getBlocks: () => Promise.resolve(blocksResponse),
|
||||
getBlockHeight: () => Promise.resolve(height ? height + (blocksAhead ?? 10n) : 10n),
|
||||
getFilteredLogs: () => Promise.resolve(logsResponse),
|
||||
getTransactionReceipt: () => Promise.resolve(receiptResponse),
|
||||
getBlock: () => Promise.resolve(blocksResponse[`0x01`]),
|
||||
};
|
||||
|
||||
getBlockSpy = jest.spyOn(evmBlockRepo, "getBlock");
|
||||
getTransactionReceipt = jest.spyOn(evmBlockRepo, "getTransactionReceipt");
|
||||
};
|
||||
|
||||
const givenPollEvmLogs = () => {
|
||||
getEvmTransactions = new GetEvmTransactions(evmBlockRepo);
|
||||
};
|
|
@ -1,16 +1,12 @@
|
|||
import { afterEach, describe, it, expect, jest } from "@jest/globals";
|
||||
import { setTimeout } from "timers/promises";
|
||||
import {
|
||||
PollEvmLogsMetadata,
|
||||
PollEvmLogs,
|
||||
PollEvmLogsConfig,
|
||||
} from "../../../../src/domain/actions";
|
||||
import { PollEvmLogsMetadata, PollEvm, PollEvmLogsConfig } from "../../../../src/domain/actions";
|
||||
import {
|
||||
EvmBlockRepository,
|
||||
MetadataRepository,
|
||||
StatRepository,
|
||||
} from "../../../../src/domain/repositories";
|
||||
import { EvmBlock, EvmLog } from "../../../../src/domain/entities";
|
||||
import { EvmBlock, EvmLog, ReceiptTransaction } from "../../../../src/domain/entities";
|
||||
|
||||
let cfg = PollEvmLogsConfig.fromBlock("acala", 0n);
|
||||
|
||||
|
@ -27,11 +23,11 @@ let handlers = {
|
|||
working: (logs: EvmLog[]) => Promise.resolve(),
|
||||
failing: (logs: EvmLog[]) => Promise.reject(),
|
||||
};
|
||||
let pollEvmLogs: PollEvmLogs;
|
||||
let pollEvm: PollEvm;
|
||||
|
||||
describe("PollEvmLogs", () => {
|
||||
describe("PollEvm", () => {
|
||||
afterEach(async () => {
|
||||
await pollEvmLogs.stop();
|
||||
await pollEvm.stop();
|
||||
});
|
||||
|
||||
it("should be able to read logs from latest block when no fromBlock is configured", async () => {
|
||||
|
@ -109,6 +105,7 @@ describe("PollEvmLogs", () => {
|
|||
const givenEvmBlockRepository = (height?: bigint, blocksAhead?: bigint) => {
|
||||
const logsResponse: EvmLog[] = [];
|
||||
const blocksResponse: Record<string, EvmBlock> = {};
|
||||
const receiptResponse: Record<string, ReceiptTransaction> = {};
|
||||
if (height) {
|
||||
for (let index = 0n; index <= (blocksAhead ?? 1n); index++) {
|
||||
logsResponse.push({
|
||||
|
@ -129,6 +126,10 @@ const givenEvmBlockRepository = (height?: bigint, blocksAhead?: bigint) => {
|
|||
hash: `0x0${index}`,
|
||||
number: height + index,
|
||||
};
|
||||
receiptResponse[`0x0${index}`] = {
|
||||
status: "0x1",
|
||||
transactionHash: `0x0${index}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -136,6 +137,8 @@ const givenEvmBlockRepository = (height?: bigint, blocksAhead?: bigint) => {
|
|||
getBlocks: () => Promise.resolve(blocksResponse),
|
||||
getBlockHeight: () => Promise.resolve(height ? height + (blocksAhead ?? 10n) : 10n),
|
||||
getFilteredLogs: () => Promise.resolve(logsResponse),
|
||||
getTransactionReceipt: () => Promise.resolve(receiptResponse),
|
||||
getBlock: () => Promise.resolve(blocksResponse[0]),
|
||||
};
|
||||
|
||||
getBlocksSpy = jest.spyOn(evmBlockRepo, "getBlocks");
|
||||
|
@ -161,11 +164,11 @@ const givenStatsRepository = () => {
|
|||
|
||||
const givenPollEvmLogs = (from?: bigint) => {
|
||||
cfg.setFromBlock(from);
|
||||
pollEvmLogs = new PollEvmLogs(evmBlockRepo, metadataRepo, statsRepo, cfg);
|
||||
pollEvm = new PollEvm(evmBlockRepo, metadataRepo, statsRepo, cfg, "GetEvmLogs");
|
||||
};
|
||||
|
||||
const whenPollEvmLogsStarts = async () => {
|
||||
pollEvmLogs.run([handlers.working]);
|
||||
pollEvm.run([handlers.working]);
|
||||
};
|
||||
|
||||
const thenWaitForAssertion = async (...assertions: (() => void)[]) => {
|
|
@ -0,0 +1,96 @@
|
|||
import { methodNameByAddressMapper } from "../../../../../src/domain/actions/evm/mappers/methodNameByAddressMapper";
|
||||
import { describe, it, expect } from "@jest/globals";
|
||||
|
||||
describe("methodNameByAddressMapper", () => {
|
||||
it("should be throw error because cannot find method name in testnet environment", async () => {
|
||||
// Given
|
||||
const transaction = getTransactions(
|
||||
"0xF890982f9310df57d00f659cf4fd87e65adEd8d7",
|
||||
"0xc65465587851912312421412124124"
|
||||
);
|
||||
const environment = "testnet";
|
||||
const chain = "ethereum";
|
||||
|
||||
// When
|
||||
const result = methodNameByAddressMapper(chain, environment, transaction);
|
||||
|
||||
// Then
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should be return a method name in testnet environment", async () => {
|
||||
// Given
|
||||
const transaction = getTransactions(
|
||||
"0xF890982f9310df57d00f659cf4fd87e65adEd8d7",
|
||||
"0xc687851912312421412124124"
|
||||
);
|
||||
const environment = "testnet";
|
||||
const chain = "ethereum";
|
||||
|
||||
// When
|
||||
const result = methodNameByAddressMapper(chain, environment, transaction);
|
||||
|
||||
// Then
|
||||
expect(result).toEqual({ method: "MethodCompleteTransfer", name: "transfer-redeemed" });
|
||||
});
|
||||
|
||||
it("should be throw error because cannot find method name in in mainnet environment", async () => {
|
||||
// Given
|
||||
const transaction = getTransactions(
|
||||
"0x3ee18B2214AFF97000D974cf647E7C347E8fa585",
|
||||
"0xc65465587851912312421412124124"
|
||||
);
|
||||
const environment = "mainnet";
|
||||
const chain = "ethereum";
|
||||
|
||||
// When
|
||||
const result = methodNameByAddressMapper(chain, environment, transaction);
|
||||
|
||||
// Then
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
it("should be return a method name in mainnet environment", async () => {
|
||||
// Given
|
||||
const transaction = getTransactions(
|
||||
"0x3ee18B2214AFF97000D974cf647E7C347E8fa585",
|
||||
"0xc687851912312421412124124"
|
||||
);
|
||||
const environment = "mainnet";
|
||||
const chain = "ethereum";
|
||||
|
||||
// When
|
||||
const result = methodNameByAddressMapper(chain, environment, transaction);
|
||||
|
||||
// Then
|
||||
expect(result).toEqual({ method: "MethodCompleteTransfer", name: "transfer-redeemed" });
|
||||
});
|
||||
});
|
||||
|
||||
const getTransactions = (to: string, input: string) => {
|
||||
return {
|
||||
blockHash: "0x1359819238ea89f49c20e42eb5603bf0541589d838d971984b60c7cdb391d9c2",
|
||||
blockNumber: 0x11ec2bcn,
|
||||
chainId: 1,
|
||||
from: "0xfb070adcd21361a3946a0584dc84a7b89faa68e3",
|
||||
gas: "0x14485",
|
||||
gasPrice: "0xfc518561e",
|
||||
hash: "0x612a35f6739f70a81dfc34448c68e99dbcfe8dafaf241edbaa204cf0e236494d",
|
||||
input: input.toLowerCase(),
|
||||
maxFeePerGas: "0x1610f75b9a",
|
||||
maxPriorityFeePerGas: "0x5f5e100",
|
||||
methodsByAddress: undefined,
|
||||
nonce: "0x1",
|
||||
r: "0xf5794b0970386d73b693b17f147fae0427db278e951e45465ac2c9835537e5a9",
|
||||
s: "0x6dccc8cfee216bc43a9d66525fa94905da234ad32d6cc3220845bef78f25dd42",
|
||||
status: "0x1",
|
||||
timestamp: 1702663079,
|
||||
to: to.toLowerCase(),
|
||||
transactionIndex: "0x6f",
|
||||
type: "0x2",
|
||||
v: "0x1",
|
||||
value: "0x5b09cd3e5e90000",
|
||||
environment: "testnet",
|
||||
chain: "ethereum",
|
||||
};
|
||||
};
|
|
@ -1,59 +0,0 @@
|
|||
import { describe, it, expect } from "@jest/globals";
|
||||
import { evmStandardRelayDelivered } from "../../../src/infrastructure/mappers/evmStandardRelayDelivered";
|
||||
import { HandleEvmLogs } from "../../../src/domain/actions";
|
||||
|
||||
const address = "0x27428dd2d3dd32a4d7f7c497eaaa23130d894911";
|
||||
const topic = "0xbccc00b713f54173962e7de6098f643d8ebf53d488d71f4b2a5171496d038f9e";
|
||||
const txHash = "0xcbdefc83080a8f60cbde7785eb2978548fd5c1f7d0ea2c024cce537845d339c7";
|
||||
|
||||
const handler = new HandleEvmLogs(
|
||||
{
|
||||
filter: { addresses: [address], topics: [topic] },
|
||||
abi: "event Delivery(address indexed recipientContract, uint16 indexed sourceChain, uint64 indexed sequence, bytes32 deliveryVaaHash, uint8 status, uint256 gasUsed, uint8 refundStatus, bytes additionalStatusInfo, bytes overridesInfo)",
|
||||
},
|
||||
evmStandardRelayDelivered,
|
||||
async () => {}
|
||||
);
|
||||
|
||||
describe("evmStandardRelayDelivered", () => {
|
||||
it("should be able to map log to TransferRedeeemed", async () => {
|
||||
const [result] = await handler.handle([
|
||||
{
|
||||
chainId: 2,
|
||||
address,
|
||||
blockTime: 1699443287,
|
||||
transactionHash: txHash,
|
||||
topics: [
|
||||
"0xbccc00b713f54173962e7de6098f643d8ebf53d488d71f4b2a5171496d038f9e",
|
||||
"0x000000000000000000000000f80cf52922b512b22d46aa8916bd7767524305d9",
|
||||
"0x000000000000000000000000000000000000000000000000000000000000001e",
|
||||
"0x0000000000000000000000000000000000000000000000000000000000000900",
|
||||
],
|
||||
data: "0xf29cac97156fa11c205eda95c0655e4a6e2a9c247245bab4d3d8257c41fc11d200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000013a89000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
||||
blockNumber: 18708316n,
|
||||
transactionIndex: "0x3b",
|
||||
blockHash: "0x8c55cbd97c96f8322bed4d1790c7ac4a84b1cff46c157bf86fc35eb5886be451",
|
||||
logIndex: 5,
|
||||
removed: false,
|
||||
},
|
||||
]);
|
||||
|
||||
expect(result.name).toBe("standard-relay-delivered");
|
||||
expect(result.chainId).toBe(2);
|
||||
expect(result.txHash).toBe(txHash);
|
||||
expect(result.blockHeight).toBe(18708316n);
|
||||
expect(result.blockTime).toBe(1699443287);
|
||||
|
||||
expect(result.attributes.recipientContract.toLowerCase()).toBe(
|
||||
"0xf80cf52922b512b22d46aa8916bd7767524305d9"
|
||||
);
|
||||
expect(result.attributes.sourceChain).toBe(30);
|
||||
expect(result.attributes.sequence).toBe(2304);
|
||||
expect(result.attributes.deliveryVaaHash.toLowerCase()).toBe(
|
||||
"0xf29cac97156fa11c205eda95c0655e4a6e2a9c247245bab4d3d8257c41fc11d2"
|
||||
);
|
||||
expect(result.attributes.status).toBe(0);
|
||||
expect(result.attributes.gasUsed).toBe(80521);
|
||||
expect(result.attributes.refundStatus).toBe(0);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,60 @@
|
|||
import { describe, it, expect } from "@jest/globals";
|
||||
import { evmTransactionFoundMapper } from "../../../src/infrastructure/mappers/evmTransactionFoundMapper";
|
||||
import { HandleEvmTransactions } from "../../../src/domain/actions";
|
||||
|
||||
const address = "0xf890982f9310df57d00f659cf4fd87e65aded8d7";
|
||||
const topic = "0xbccc00b713f54173962e7de6098f643d8ebf53d488d71f4b2a5171496d038f9e";
|
||||
const txHash = "0x612a35f6739f70a81dfc34448c68e99dbcfe8dafaf241edbaa204cf0e236494d";
|
||||
|
||||
const handler = new HandleEvmTransactions(
|
||||
{
|
||||
filter: { addresses: [address], topics: [topic] },
|
||||
abi: "event Delivery(address indexed recipientContract, uint16 indexed sourceChain, uint64 indexed sequence, bytes32 deliveryVaaHash, uint8 status, uint256 gasUsed, uint8 refundStatus, bytes additionalStatusInfo, bytes overridesInfo)",
|
||||
},
|
||||
evmTransactionFoundMapper,
|
||||
async () => {}
|
||||
);
|
||||
|
||||
describe("evmTransactionFoundMapper", () => {
|
||||
it("should be able to map log to evmTransactionFoundMapper", async () => {
|
||||
// When
|
||||
const [result] = await handler.handle([
|
||||
{
|
||||
blockHash: "0x612a35f6739f70a81dfc34448c68e99dbcfe8dafaf241edbaa204cf0e236494d",
|
||||
blockNumber: 0x11ec2bcn,
|
||||
chainId: 1,
|
||||
from: "0xfb070adcd21361a3946a0584dc84a7b89faa68e3",
|
||||
gas: "0x14485",
|
||||
gasPrice: "0xfc518561e",
|
||||
hash: "0x612a35f6739f70a81dfc34448c68e99dbcfe8dafaf241edbaa204cf0e236494d",
|
||||
input:
|
||||
"0xc68785190000000000000000000000000000000000000000000000000000000000000001637651ef71f834be28b8fab1dce9c228c2fe1813831bbc3673cfd3abde6dbb3d00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080420000",
|
||||
maxFeePerGas: "0x1610f75b9a",
|
||||
maxPriorityFeePerGas: "0x5f5e100",
|
||||
nonce: "0x1",
|
||||
r: "0xf5794b0970386d73b693b17f147fae0427db278e951e45465ac2c9835537e5a9",
|
||||
s: "0x6dccc8cfee216bc43a9d66525fa94905da234ad32d6cc3220845bef78f25dd42",
|
||||
status: "0x1",
|
||||
timestamp: 1702663079,
|
||||
to: "0xf890982f9310df57d00f659cf4fd87e65aded8d7",
|
||||
transactionIndex: "0x6f",
|
||||
type: "0x2",
|
||||
v: "0x1",
|
||||
value: "0x5b09cd3e5e90000",
|
||||
environment: "testnet",
|
||||
chain: "ethereum",
|
||||
},
|
||||
]);
|
||||
|
||||
// Then
|
||||
expect(result.name).toBe("evm-transaction-found");
|
||||
expect(result.chainId).toBe(1);
|
||||
expect(result.txHash).toBe(txHash);
|
||||
expect(result.blockHeight).toBe(18793148n);
|
||||
expect(result.attributes.blockNumber).toBe(18793148n);
|
||||
expect(result.attributes.from).toBe("0xfb070adcd21361a3946a0584dc84a7b89faa68e3");
|
||||
expect(result.attributes.to).toBe("0xf890982f9310df57d00f659cf4fd87e65aded8d7");
|
||||
expect(result.attributes.methodsByAddress).toBe("MethodCompleteTransfer");
|
||||
expect(result.attributes.name).toBe("transfer-redeemed");
|
||||
});
|
||||
});
|
|
@ -1,53 +0,0 @@
|
|||
import { describe, it, expect } from "@jest/globals";
|
||||
import { evmTransferRedeemedMapper } from "../../../src/infrastructure/mappers/evmTransferRedeemedMapper";
|
||||
import { HandleEvmLogs } from "../../../src/domain/actions";
|
||||
|
||||
const address = "0x98f3c9e6e3face36baad05fe09d375ef1464288b";
|
||||
const topic = "0xcaf280c8cfeba144da67230d9b009c8f868a75bac9a528fa0474be1ba317c169";
|
||||
const txHash = "0xcbdefc83080a8f60cbde7785eb2978548fd5c1f7d0ea2c024cce537845d339c7";
|
||||
|
||||
const handler = new HandleEvmLogs(
|
||||
{
|
||||
filter: { addresses: [address], topics: [topic] },
|
||||
abi: "event TransferRedeemed(uint16 indexed emitterChainId, bytes32 indexed emitterAddress, uint64 indexed sequence)",
|
||||
},
|
||||
evmTransferRedeemedMapper,
|
||||
async () => {}
|
||||
);
|
||||
|
||||
describe("evmTransferRedeemed", () => {
|
||||
it("should be able to map log to TransferRedeeemed", async () => {
|
||||
const [result] = await handler.handle([
|
||||
{
|
||||
chainId: 2,
|
||||
address,
|
||||
topics: [
|
||||
"0xcaf280c8cfeba144da67230d9b009c8f868a75bac9a528fa0474be1ba317c169",
|
||||
"0x0000000000000000000000000000000000000000000000000000000000000001",
|
||||
"0xec7372995d5cc8732397fb0ad35c0121e0eaa90d26f828a534cab54391b3a4f5",
|
||||
"0x0000000000000000000000000000000000000000000000000000000000052a3e",
|
||||
],
|
||||
data: "0x",
|
||||
blockNumber: 18708192n,
|
||||
blockTime: 1699443287,
|
||||
transactionHash: txHash,
|
||||
transactionIndex: "0x3e",
|
||||
blockHash: "0x241fa85f3494c654d59859b46af586bd43f37ec434f5cf0018a53e46c42da393",
|
||||
logIndex: 216,
|
||||
removed: false,
|
||||
},
|
||||
]);
|
||||
|
||||
expect(result.name).toBe("transfer-redeemed");
|
||||
expect(result.chainId).toBe(2);
|
||||
expect(result.txHash).toBe(txHash);
|
||||
expect(result.blockHeight).toBe(18708192n);
|
||||
expect(result.blockTime).toBe(1699443287);
|
||||
|
||||
expect(result.attributes.sequence).toBe(338494);
|
||||
expect(result.attributes.emitterAddress.toLowerCase()).toBe(
|
||||
"0xec7372995d5cc8732397fb0ad35c0121e0eaa90d26f828a534cab54391b3a4f5"
|
||||
);
|
||||
expect(result.attributes.emitterChainId).toBe(1);
|
||||
});
|
||||
});
|
|
@ -22,7 +22,7 @@ describe("StaticJobRepository", () => {
|
|||
if (fs.existsSync(dirPath)) {
|
||||
fs.rmSync(dirPath, { recursive: true, force: true });
|
||||
}
|
||||
repo = new StaticJobRepository(dirPath, false, () => blockRepo, {
|
||||
repo = new StaticJobRepository("testnet", dirPath, false, () => blockRepo, {
|
||||
metadataRepo,
|
||||
statsRepo,
|
||||
snsRepo,
|
||||
|
@ -49,7 +49,7 @@ const givenJobsPresent = () => {
|
|||
id: "poll-log-message-published-ethereum",
|
||||
chain: "ethereum",
|
||||
source: {
|
||||
action: "PollEvmLogs",
|
||||
action: "PollEvm",
|
||||
config: {
|
||||
fromBlock: 10012499n,
|
||||
blockBatchSize: 100,
|
||||
|
|
|
@ -40,7 +40,7 @@ data:
|
|||
"id": "poll-log-message-published-optimism",
|
||||
"chain": "optimism",
|
||||
"source": {
|
||||
"action": "PollEvmLogs",
|
||||
"action": "PollEvm",
|
||||
"config": {
|
||||
"blockBatchSize": 100,
|
||||
"commitment": "latest",
|
||||
|
@ -68,7 +68,7 @@ data:
|
|||
"id": "poll-log-message-published-base",
|
||||
"chain": "base",
|
||||
"source": {
|
||||
"action": "PollEvmLogs",
|
||||
"action": "PollEvm",
|
||||
"config": {
|
||||
"blockBatchSize": 100,
|
||||
"commitment": "finalized",
|
||||
|
@ -96,7 +96,7 @@ data:
|
|||
"id": "poll-log-message-published-celo",
|
||||
"chain": "celo",
|
||||
"source": {
|
||||
"action": "PollEvmLogs",
|
||||
"action": "PollEvm",
|
||||
"config": {
|
||||
"blockBatchSize": 100,
|
||||
"commitment": "latest",
|
||||
|
@ -124,7 +124,7 @@ data:
|
|||
"id": "poll-log-message-published-oasis",
|
||||
"chain": "oasis",
|
||||
"source": {
|
||||
"action": "PollEvmLogs",
|
||||
"action": "PollEvm",
|
||||
"config": {
|
||||
"blockBatchSize": 100,
|
||||
"commitment": "latest",
|
||||
|
@ -152,7 +152,7 @@ data:
|
|||
"id": "poll-log-message-published-klaytn",
|
||||
"chain": "klaytn",
|
||||
"source": {
|
||||
"action": "PollEvmLogs",
|
||||
"action": "PollEvm",
|
||||
"config": {
|
||||
"blockBatchSize": 100,
|
||||
"commitment": "latest",
|
||||
|
@ -180,7 +180,7 @@ data:
|
|||
"id": "poll-log-message-published-arbitrum",
|
||||
"chain": "arbitrum",
|
||||
"source": {
|
||||
"action": "PollEvmLogs",
|
||||
"action": "PollEvm",
|
||||
"config": {
|
||||
"blockBatchSize": 100,
|
||||
"commitment": "latest",
|
||||
|
@ -208,7 +208,7 @@ data:
|
|||
"id": "poll-log-message-published-polygon",
|
||||
"chain": "polygon",
|
||||
"source": {
|
||||
"action": "PollEvmLogs",
|
||||
"action": "PollEvm",
|
||||
"config": {
|
||||
"blockBatchSize": 100,
|
||||
"commitment": "latest",
|
||||
|
@ -239,7 +239,7 @@ data:
|
|||
"id": "poll-log-message-published-optimism",
|
||||
"chain": "optimism",
|
||||
"source": {
|
||||
"action": "PollEvmLogs",
|
||||
"action": "PollEvm",
|
||||
"config": {
|
||||
"blockBatchSize": 100,
|
||||
"commitment": "latest",
|
||||
|
@ -267,7 +267,7 @@ data:
|
|||
"id": "poll-log-message-published-base",
|
||||
"chain": "base",
|
||||
"source": {
|
||||
"action": "PollEvmLogs",
|
||||
"action": "PollEvm",
|
||||
"config": {
|
||||
"blockBatchSize": 100,
|
||||
"commitment": "finalized",
|
||||
|
@ -295,7 +295,7 @@ data:
|
|||
"id": "poll-log-message-published-celo",
|
||||
"chain": "celo",
|
||||
"source": {
|
||||
"action": "PollEvmLogs",
|
||||
"action": "PollEvm",
|
||||
"config": {
|
||||
"blockBatchSize": 100,
|
||||
"commitment": "latest",
|
||||
|
@ -323,7 +323,7 @@ data:
|
|||
"id": "poll-log-message-published-oasis",
|
||||
"chain": "oasis",
|
||||
"source": {
|
||||
"action": "PollEvmLogs",
|
||||
"action": "PollEvm",
|
||||
"config": {
|
||||
"blockBatchSize": 100,
|
||||
"commitment": "latest",
|
||||
|
@ -351,7 +351,7 @@ data:
|
|||
"id": "poll-log-message-published-klaytn",
|
||||
"chain": "klaytn",
|
||||
"source": {
|
||||
"action": "PollEvmLogs",
|
||||
"action": "PollEvm",
|
||||
"config": {
|
||||
"blockBatchSize": 100,
|
||||
"commitment": "latest",
|
||||
|
@ -379,7 +379,7 @@ data:
|
|||
"id": "poll-log-message-published-arbitrum",
|
||||
"chain": "arbitrum",
|
||||
"source": {
|
||||
"action": "PollEvmLogs",
|
||||
"action": "PollEvm",
|
||||
"config": {
|
||||
"blockBatchSize": 100,
|
||||
"commitment": "latest",
|
||||
|
@ -407,7 +407,7 @@ data:
|
|||
"id": "poll-log-message-published-polygon",
|
||||
"chain": "polygon",
|
||||
"source": {
|
||||
"action": "PollEvmLogs",
|
||||
"action": "PollEvm",
|
||||
"config": {
|
||||
"blockBatchSize": 100,
|
||||
"commitment": "latest",
|
||||
|
|
|
@ -40,7 +40,7 @@ data:
|
|||
"id": "poll-log-message-published-ethereum",
|
||||
"chain": "ethereum",
|
||||
"source": {
|
||||
"action": "PollEvmLogs",
|
||||
"action": "PollEvm",
|
||||
"config": {
|
||||
"fromBlock": "10012499",
|
||||
"blockBatchSize": 100,
|
||||
|
@ -69,7 +69,7 @@ data:
|
|||
"id": "poll-log-message-published-karura",
|
||||
"chain": "karura",
|
||||
"source": {
|
||||
"action": "PollEvmLogs",
|
||||
"action": "PollEvm",
|
||||
"config": {
|
||||
"blockBatchSize": 100,
|
||||
"commitment": "finalized",
|
||||
|
@ -103,7 +103,7 @@ data:
|
|||
"id": "poll-log-message-published-fantom",
|
||||
"chain": "fantom",
|
||||
"source": {
|
||||
"action": "PollEvmLogs",
|
||||
"action": "PollEvm",
|
||||
"config": {
|
||||
"blockBatchSize": 100,
|
||||
"commitment": "latest",
|
||||
|
@ -137,7 +137,7 @@ data:
|
|||
"id": "poll-log-message-published-acala",
|
||||
"chain": "acala",
|
||||
"source": {
|
||||
"action": "PollEvmLogs",
|
||||
"action": "PollEvm",
|
||||
"config": {
|
||||
"blockBatchSize": 100,
|
||||
"commitment": "finalized",
|
||||
|
@ -171,7 +171,7 @@ data:
|
|||
"id": "poll-log-message-published-avalanche",
|
||||
"chain": "avalanche",
|
||||
"source": {
|
||||
"action": "PollEvmLogs",
|
||||
"action": "PollEvm",
|
||||
"config": {
|
||||
"blockBatchSize": 100,
|
||||
"commitment": "finalized",
|
||||
|
@ -206,7 +206,7 @@ data:
|
|||
"id": "poll-log-message-published-bsc",
|
||||
"chain": "bsc",
|
||||
"source": {
|
||||
"action": "PollEvmLogs",
|
||||
"action": "PollEvm",
|
||||
"config": {
|
||||
"blockBatchSize": 100,
|
||||
"commitment": "latest",
|
||||
|
@ -234,7 +234,7 @@ data:
|
|||
"id": "poll-log-message-published-moonbeam",
|
||||
"chain": "moonbeam",
|
||||
"source": {
|
||||
"action": "PollEvmLogs",
|
||||
"action": "PollEvm",
|
||||
"config": {
|
||||
"blockBatchSize": 100,
|
||||
"commitment": "latest",
|
||||
|
@ -265,7 +265,7 @@ data:
|
|||
"id": "poll-log-message-published-ethereum",
|
||||
"chain": "ethereum",
|
||||
"source": {
|
||||
"action": "PollEvmLogs",
|
||||
"action": "PollEvm",
|
||||
"config": {
|
||||
"blockBatchSize": 100,
|
||||
"commitment": "latest",
|
||||
|
@ -293,7 +293,7 @@ data:
|
|||
"id": "poll-log-message-published-karura",
|
||||
"chain": "karura",
|
||||
"source": {
|
||||
"action": "PollEvmLogs",
|
||||
"action": "PollEvm",
|
||||
"config": {
|
||||
"blockBatchSize": 100,
|
||||
"commitment": "finalized",
|
||||
|
@ -327,7 +327,7 @@ data:
|
|||
"id": "poll-log-message-published-fantom",
|
||||
"chain": "fantom",
|
||||
"source": {
|
||||
"action": "PollEvmLogs",
|
||||
"action": "PollEvm",
|
||||
"config": {
|
||||
"blockBatchSize": 100,
|
||||
"commitment": "latest",
|
||||
|
@ -361,7 +361,7 @@ data:
|
|||
"id": "poll-log-message-published-acala",
|
||||
"chain": "acala",
|
||||
"source": {
|
||||
"action": "PollEvmLogs",
|
||||
"action": "PollEvm",
|
||||
"config": {
|
||||
"blockBatchSize": 100,
|
||||
"commitment": "finalized",
|
||||
|
@ -395,7 +395,7 @@ data:
|
|||
"id": "poll-log-message-published-avalanche",
|
||||
"chain": "avalanche",
|
||||
"source": {
|
||||
"action": "PollEvmLogs",
|
||||
"action": "PollEvm",
|
||||
"config": {
|
||||
"blockBatchSize": 100,
|
||||
"commitment": "finalized",
|
||||
|
@ -430,7 +430,7 @@ data:
|
|||
"id": "poll-log-message-published-bsc",
|
||||
"chain": "bsc",
|
||||
"source": {
|
||||
"action": "PollEvmLogs",
|
||||
"action": "PollEvm",
|
||||
"config": {
|
||||
"blockBatchSize": 100,
|
||||
"commitment": "latest",
|
||||
|
@ -458,7 +458,7 @@ data:
|
|||
"id": "poll-log-message-published-moonbeam",
|
||||
"chain": "moonbeam",
|
||||
"source": {
|
||||
"action": "PollEvmLogs",
|
||||
"action": "PollEvm",
|
||||
"config": {
|
||||
"blockBatchSize": 100,
|
||||
"commitment": "latest",
|
||||
|
|
Loading…
Reference in New Issue