[Blockchain Watcher] (ALGORAND) Map algorand events (source and target) (#1505)
* Start implement algorand events * Create handler and improve repository * Create mapper for redeem events * Resolve some //TODO comments * Support source events * Improve application id name * Validate payload length * Validate logs property * Add all test for infrastructere and domain * Improve code * Remove any type * Remove throw for wormchain * Resolve comment in PR --------- Co-authored-by: julian merlo <julianmerlo@julians-MacBook-Pro-2.local>
This commit is contained in:
parent
2646baa9f1
commit
6f7d457386
|
@ -242,6 +242,13 @@
|
|||
"__name": "SEI_RPCS",
|
||||
"__format": "json"
|
||||
}
|
||||
},
|
||||
"algorand": {
|
||||
"network": "ALGORAND_NETWORK",
|
||||
"rpcs": {
|
||||
"__name": "ALGORAND_RPCS",
|
||||
"__format": "json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"port": 9090,
|
||||
"logLevel": "debug",
|
||||
"dryRun": true,
|
||||
"enabledPlatforms": ["solana", "evm", "sui", "aptos", "wormchain", "sei"],
|
||||
"enabledPlatforms": ["solana", "evm", "sui", "aptos", "wormchain", "sei", "algorand"],
|
||||
"sns": {
|
||||
"topicArn": "arn:aws:sns:us-east-1:000000000000:localstack-topic.fifo",
|
||||
"region": "us-east-1",
|
||||
|
@ -70,6 +70,13 @@
|
|||
"rpcs": ["https://testnet.emerald.oasis.dev"],
|
||||
"timeout": 10000
|
||||
},
|
||||
"algorand": {
|
||||
"name": "algorand",
|
||||
"network": "testnet",
|
||||
"chainId": 8,
|
||||
"rpcs": [["https://testnet-api.algonode.cloud"], ["https://testnet-idx.algonode.cloud"]],
|
||||
"timeout": 10000
|
||||
},
|
||||
"fantom": {
|
||||
"name": "fantom",
|
||||
"network": "testnet",
|
||||
|
|
|
@ -48,6 +48,11 @@
|
|||
"chainId": 7,
|
||||
"rpcs": ["https://emerald.oasis.dev"]
|
||||
},
|
||||
"algorand": {
|
||||
"network": "mainnet",
|
||||
"chainId": 8,
|
||||
"rpcs": [["https://mainnet-api.algonode.cloud"], ["https://mainnet-idx.algonode.cloud"]]
|
||||
},
|
||||
"fantom": {
|
||||
"network": "mainnet",
|
||||
"chainId": 10,
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
"@cosmjs/proto-signing": "^0.32.3",
|
||||
"@mysten/sui.js": "^0.49.1",
|
||||
"@xlabs/rpc-pool": "^0.0.4",
|
||||
"algosdk": "^2.8.0",
|
||||
"axios": "^1.6.0",
|
||||
"bs58": "^5.0.0",
|
||||
"config": "^3.3.9",
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
"@cosmjs/proto-signing": "^0.32.3",
|
||||
"@mysten/sui.js": "^0.49.1",
|
||||
"@xlabs/rpc-pool": "^0.0.4",
|
||||
"algosdk": "^2.8.0",
|
||||
"axios": "^1.6.0",
|
||||
"bs58": "^5.0.0",
|
||||
"config": "^3.3.9",
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
import { AlgorandTransaction } from "../../entities/algorand";
|
||||
import { AlgorandRepository } from "../../repositories";
|
||||
import { GetAlgorandOpts } from "./PollAlgorand";
|
||||
import winston from "winston";
|
||||
|
||||
export class GetAlgorandTransactions {
|
||||
private readonly blockRepo: AlgorandRepository;
|
||||
protected readonly logger: winston.Logger;
|
||||
|
||||
constructor(blockRepo: AlgorandRepository) {
|
||||
this.logger = winston.child({ module: "GetAlgorandTransactions" });
|
||||
this.blockRepo = blockRepo;
|
||||
}
|
||||
|
||||
async execute(range: Range, opts: GetAlgorandOpts): Promise<AlgorandTransaction[]> {
|
||||
const { fromBlock, toBlock } = range;
|
||||
const chain = opts.chain;
|
||||
|
||||
if (fromBlock > toBlock) {
|
||||
this.logger.info(
|
||||
`[${chain}][exec] Invalid range [fromBlock: ${fromBlock} - toBlock: ${toBlock}]`
|
||||
);
|
||||
return [];
|
||||
}
|
||||
this.logger.info(
|
||||
`[${chain}][exec] Processing blocks [fromBlock: ${fromBlock} - toBlock: ${toBlock}]`
|
||||
);
|
||||
|
||||
const txs = await this.blockRepo.getTransactions(opts.applicationIds[0], fromBlock, toBlock);
|
||||
|
||||
this.logger.info(
|
||||
`[${chain}][exec] Got ${txs?.length} transactions to process [fromBlock: ${fromBlock} - toBlock: ${toBlock}]`
|
||||
);
|
||||
return txs;
|
||||
}
|
||||
}
|
||||
|
||||
type Range = {
|
||||
fromBlock: bigint;
|
||||
toBlock: bigint;
|
||||
};
|
|
@ -0,0 +1,49 @@
|
|||
import { TransactionFoundEvent } from "../../entities";
|
||||
import { AlgorandTransaction } from "../../entities/algorand";
|
||||
import { StatRepository } from "../../repositories";
|
||||
|
||||
export class HandleAlgorandTransactions {
|
||||
constructor(
|
||||
private readonly cfg: HandleAlgorandTransactionsOptions,
|
||||
private readonly mapper: (tx: AlgorandTransaction, filter: Filter[]) => TransactionFoundEvent,
|
||||
private readonly target: (parsed: TransactionFoundEvent[]) => Promise<void>,
|
||||
private readonly statsRepo: StatRepository
|
||||
) {}
|
||||
|
||||
public async handle(txs: AlgorandTransaction[]): Promise<TransactionFoundEvent[]> {
|
||||
const items: TransactionFoundEvent[] = [];
|
||||
|
||||
for (const tx of txs) {
|
||||
const txMapped = this.mapper(tx, this.cfg.filter);
|
||||
if (txMapped) {
|
||||
this.report(txMapped.attributes.protocol);
|
||||
items.push(txMapped);
|
||||
}
|
||||
}
|
||||
|
||||
await this.target(items);
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
private report(protocol: string) {
|
||||
const labels = {
|
||||
job: this.cfg.id,
|
||||
chain: "algorand",
|
||||
protocol: protocol ?? "unknown",
|
||||
commitment: "latest",
|
||||
};
|
||||
this.statsRepo.count(this.cfg.metricName, labels);
|
||||
}
|
||||
}
|
||||
|
||||
export interface HandleAlgorandTransactionsOptions {
|
||||
metricName: string;
|
||||
filter: Filter[];
|
||||
id: string;
|
||||
}
|
||||
|
||||
type Filter = {
|
||||
applicationIds: string;
|
||||
applicationAddress: string;
|
||||
};
|
|
@ -0,0 +1,221 @@
|
|||
import { AlgorandRepository, MetadataRepository, StatRepository } from "../../repositories";
|
||||
import { GetAlgorandTransactions } from "./GetAlgorandTransactions";
|
||||
import { AlgorandTransaction } from "../../entities/algorand";
|
||||
import { RunPollingJob } from "../RunPollingJob";
|
||||
import winston from "winston";
|
||||
|
||||
const ID = "watch-algorand-logs";
|
||||
|
||||
export class PollAlgorand extends RunPollingJob {
|
||||
protected readonly logger: winston.Logger;
|
||||
|
||||
private readonly blockRepo: AlgorandRepository;
|
||||
private readonly metadataRepo: MetadataRepository<PollAlgorandMetadata>;
|
||||
private readonly statsRepo: StatRepository;
|
||||
private readonly getAlgorand: GetAlgorandTransactions;
|
||||
|
||||
private cfg: PollAlgorandConfig;
|
||||
private latestBlockHeight?: bigint;
|
||||
private blockHeightCursor?: bigint;
|
||||
private lastRange?: { fromBlock: bigint; toBlock: bigint };
|
||||
|
||||
constructor(
|
||||
blockRepo: AlgorandRepository,
|
||||
metadataRepo: MetadataRepository<PollAlgorandMetadata>,
|
||||
statsRepo: StatRepository,
|
||||
cfg: PollAlgorandConfig
|
||||
) {
|
||||
super(cfg.id, statsRepo, cfg.interval);
|
||||
this.blockRepo = blockRepo;
|
||||
this.metadataRepo = metadataRepo;
|
||||
this.statsRepo = statsRepo;
|
||||
this.cfg = cfg;
|
||||
this.logger = winston.child({ module: "PollAlgorand", label: this.cfg.id });
|
||||
this.getAlgorand = new GetAlgorandTransactions(blockRepo);
|
||||
}
|
||||
|
||||
protected async preHook(): Promise<void> {
|
||||
const metadata = await this.metadataRepo.get(this.cfg.id);
|
||||
if (metadata) {
|
||||
this.blockHeightCursor = BigInt(metadata.lastBlock);
|
||||
}
|
||||
}
|
||||
|
||||
protected async hasNext(): Promise<boolean> {
|
||||
const hasFinished = this.cfg.hasFinished(this.blockHeightCursor);
|
||||
if (hasFinished) {
|
||||
this.logger.info(
|
||||
`[hasNext] PollAlgorand: (${this.cfg.id}) Finished processing all blocks from ${this.cfg.fromBlock} to ${this.cfg.toBlock}`
|
||||
);
|
||||
}
|
||||
|
||||
return !hasFinished;
|
||||
}
|
||||
|
||||
protected async get(): Promise<AlgorandTransaction[]> {
|
||||
this.latestBlockHeight = await this.blockRepo.getBlockHeight();
|
||||
|
||||
const range = this.getBlockRange(this.latestBlockHeight!);
|
||||
|
||||
const algorandTransactions = await this.getAlgorand.execute(range, {
|
||||
applicationIds: this.cfg.applicationIds,
|
||||
chainId: this.cfg.chainId,
|
||||
chain: this.cfg.chain,
|
||||
});
|
||||
|
||||
this.lastRange = range;
|
||||
|
||||
return algorandTransactions;
|
||||
}
|
||||
|
||||
protected async persist(): Promise<void> {
|
||||
this.blockHeightCursor = this.lastRange?.toBlock ?? this.blockHeightCursor;
|
||||
if (this.blockHeightCursor) {
|
||||
await this.metadataRepo.save(this.cfg.id, { lastBlock: this.blockHeightCursor });
|
||||
}
|
||||
}
|
||||
|
||||
private getBlockRange(latestBlockHeight: bigint): {
|
||||
fromBlock: bigint;
|
||||
toBlock: bigint;
|
||||
} {
|
||||
let fromBlock = this.blockHeightCursor
|
||||
? this.blockHeightCursor + 1n
|
||||
: this.cfg.fromBlock ?? latestBlockHeight;
|
||||
// fromBlock is configured and is greater than current block height, then we allow to skip blocks.
|
||||
if (
|
||||
this.blockHeightCursor &&
|
||||
this.cfg.fromBlock &&
|
||||
this.cfg.fromBlock > this.blockHeightCursor
|
||||
) {
|
||||
fromBlock = this.cfg.fromBlock;
|
||||
}
|
||||
|
||||
let toBlock = BigInt(fromBlock) + BigInt(this.cfg.getBlockBatchSize());
|
||||
// limit toBlock to obtained block height
|
||||
if (toBlock > fromBlock && toBlock > latestBlockHeight) {
|
||||
toBlock = latestBlockHeight;
|
||||
}
|
||||
// limit toBlock to configured toBlock
|
||||
if (this.cfg.toBlock && toBlock > this.cfg.toBlock) {
|
||||
toBlock = this.cfg.toBlock;
|
||||
}
|
||||
|
||||
return { fromBlock: BigInt(fromBlock), toBlock: BigInt(toBlock) };
|
||||
}
|
||||
|
||||
protected report(): void {
|
||||
const labels = {
|
||||
job: this.cfg.id,
|
||||
chain: this.cfg.chain ?? "",
|
||||
commitment: this.cfg.getCommitment(),
|
||||
};
|
||||
const latestBlockHeight = this.latestBlockHeight ?? 0n;
|
||||
const blockHeightCursor = this.blockHeightCursor ?? 0n;
|
||||
const diffCursor = BigInt(latestBlockHeight) - BigInt(blockHeightCursor);
|
||||
|
||||
this.statsRepo.count("job_execution", labels);
|
||||
|
||||
this.statsRepo.measure("polling_cursor", latestBlockHeight, {
|
||||
...labels,
|
||||
type: "max",
|
||||
});
|
||||
|
||||
this.statsRepo.measure("polling_cursor", blockHeightCursor, {
|
||||
...labels,
|
||||
type: "current",
|
||||
});
|
||||
|
||||
this.statsRepo.measure("polling_cursor", diffCursor, {
|
||||
...labels,
|
||||
type: "diff",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export type PollAlgorandMetadata = {
|
||||
lastBlock: bigint;
|
||||
};
|
||||
|
||||
export interface PollAlgorandConfigProps {
|
||||
blockBatchSize?: number;
|
||||
applicationIds: string[];
|
||||
commitment?: string;
|
||||
environment: string;
|
||||
fromBlock?: bigint;
|
||||
interval?: number;
|
||||
toBlock?: bigint;
|
||||
chainId: number;
|
||||
chain: string;
|
||||
id?: string;
|
||||
}
|
||||
|
||||
export class PollAlgorandConfig {
|
||||
private props: PollAlgorandConfigProps;
|
||||
|
||||
constructor(props: PollAlgorandConfigProps) {
|
||||
if (props.fromBlock && props.toBlock && props.fromBlock > props.toBlock) {
|
||||
throw new Error("fromBlock must be less than or equal to toBlock");
|
||||
}
|
||||
|
||||
this.props = props;
|
||||
}
|
||||
|
||||
public getBlockBatchSize() {
|
||||
return this.props.blockBatchSize ?? 100;
|
||||
}
|
||||
|
||||
public getCommitment() {
|
||||
return this.props.commitment ?? "latest";
|
||||
}
|
||||
|
||||
public hasFinished(currentFromBlock?: bigint): boolean {
|
||||
return (
|
||||
currentFromBlock != undefined &&
|
||||
this.props.toBlock != undefined &&
|
||||
currentFromBlock >= this.props.toBlock
|
||||
);
|
||||
}
|
||||
|
||||
public get fromBlock() {
|
||||
return this.props.fromBlock ? BigInt(this.props.fromBlock) : undefined;
|
||||
}
|
||||
|
||||
public setFromBlock(fromBlock: bigint | undefined) {
|
||||
this.props.fromBlock = fromBlock;
|
||||
}
|
||||
|
||||
public get toBlock() {
|
||||
return this.props.toBlock;
|
||||
}
|
||||
|
||||
public get interval() {
|
||||
return this.props.interval;
|
||||
}
|
||||
|
||||
public get id() {
|
||||
return this.props.id ?? ID;
|
||||
}
|
||||
|
||||
public get chain() {
|
||||
return this.props.chain;
|
||||
}
|
||||
|
||||
public get environment() {
|
||||
return this.props.environment;
|
||||
}
|
||||
|
||||
public get chainId() {
|
||||
return this.props.chainId;
|
||||
}
|
||||
|
||||
public get applicationIds(): string[] {
|
||||
return this.props.applicationIds;
|
||||
}
|
||||
}
|
||||
|
||||
export type GetAlgorandOpts = {
|
||||
applicationIds: string[];
|
||||
chainId: number;
|
||||
chain: string;
|
||||
};
|
|
@ -12,5 +12,8 @@ export * from "./aptos/GetAptosTransactions";
|
|||
export * from "./aptos/GetAptosTransactionsByEvents";
|
||||
export * from "./aptos/HandleAptosTransactions";
|
||||
export * from "./aptos/PollAptos";
|
||||
export * from "./algorand/PollAlgorand";
|
||||
export * from "./algorand/HandleAlgorandTransactions";
|
||||
export * from "./algorand/GetAlgorandTransactions";
|
||||
export * from "./RunPollingJob";
|
||||
export * from "./StartJobs";
|
||||
|
|
|
@ -28,6 +28,9 @@ export class GetWormchainLogs {
|
|||
);
|
||||
return [];
|
||||
}
|
||||
this.logger.info(
|
||||
`[wormchain][exec] Processing blocks [fromBlock: ${fromBlock} - toBlock: ${toBlock}]`
|
||||
);
|
||||
|
||||
for (let blockNumber = fromBlock; blockNumber <= toBlock; blockNumber++) {
|
||||
const wormchainLogs = await this.blockRepo.getBlockLogs(
|
||||
|
|
|
@ -29,6 +29,10 @@ export class GetWormchainRedeems {
|
|||
return [];
|
||||
}
|
||||
|
||||
this.logger.info(
|
||||
`[wormchain][exec] Processing blocks [fromBlock: ${fromBlock} - toBlock: ${toBlock}]`
|
||||
);
|
||||
|
||||
for (let blockNumber = fromBlock; blockNumber <= toBlock; blockNumber++) {
|
||||
const wormchainLogs = await this.blockRepo.getBlockLogs(
|
||||
opts.chainId,
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
export interface AlgorandTransaction {
|
||||
applicationId: string;
|
||||
blockNumber: number;
|
||||
timestamp: number;
|
||||
innerTxs?: {
|
||||
applicationId?: string;
|
||||
payload?: string;
|
||||
sender: string;
|
||||
method?: string;
|
||||
logs?: string[];
|
||||
}[];
|
||||
payload: string;
|
||||
method: string;
|
||||
sender: string;
|
||||
hash: string;
|
||||
}
|
|
@ -1,9 +1,12 @@
|
|||
import { IbcTransaction, WormchainBlockLogs, CosmosRedeem } from "./entities/wormchain";
|
||||
import { AptosEvent, AptosTransaction } from "./entities/aptos";
|
||||
import { SuiTransactionBlockReceipt } from "./entities/sui";
|
||||
import { Fallible, SolanaFailure } from "./errors";
|
||||
import { ConfirmedSignatureInfo } from "./entities/solana";
|
||||
import { AlgorandTransaction } from "./entities/algorand";
|
||||
import { TransactionFilter } from "./actions/aptos/PollAptos";
|
||||
import { RunPollingJob } from "./actions/RunPollingJob";
|
||||
import { SeiRedeem } from "./entities/sei";
|
||||
import {
|
||||
TransactionFilter as SuiTransactionFilter,
|
||||
SuiEventFilter,
|
||||
|
@ -20,8 +23,6 @@ import {
|
|||
EvmTag,
|
||||
Range,
|
||||
} from "./entities";
|
||||
import { IbcTransaction, WormchainBlockLogs, CosmosRedeem } from "./entities/wormchain";
|
||||
import { SeiRedeem } from "./entities/sei";
|
||||
|
||||
export interface EvmBlockRepository {
|
||||
getBlockHeight(chain: string, finality: string): Promise<bigint>;
|
||||
|
@ -97,6 +98,15 @@ export interface SeiRepository {
|
|||
getBlockTimestamp(blockNumber: bigint): Promise<number | undefined>;
|
||||
}
|
||||
|
||||
export interface AlgorandRepository {
|
||||
getTransactions(
|
||||
applicationId: string,
|
||||
fromBlock: bigint,
|
||||
toBlock: bigint
|
||||
): Promise<AlgorandTransaction[]>;
|
||||
getBlockHeight(): Promise<bigint | undefined>;
|
||||
}
|
||||
|
||||
export interface MetadataRepository<Metadata> {
|
||||
get(id: string): Promise<Metadata | undefined>;
|
||||
save(id: string, metadata: Metadata): Promise<void>;
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
import { LogFoundEvent, LogMessagePublished } from "../../../domain/entities";
|
||||
import { AlgorandTransaction } from "../../../domain/entities/algorand";
|
||||
import winston from "winston";
|
||||
import algosdk from "algosdk";
|
||||
|
||||
const CHAIN_ID_ALGORAND = 8;
|
||||
|
||||
let logger: winston.Logger = winston.child({ module: "algorandLogMessagePublishedMapper" });
|
||||
|
||||
export const algorandLogMessagePublishedMapper = (
|
||||
transaction: AlgorandTransaction,
|
||||
filters: {
|
||||
applicationIds: string;
|
||||
applicationAddress: string;
|
||||
}[]
|
||||
): LogFoundEvent<LogMessagePublished> | undefined => {
|
||||
const innetTx = transaction.innerTxs?.find((tx) => tx.applicationId == filters[0].applicationIds);
|
||||
|
||||
if (!innetTx?.method || !Array.isArray(innetTx.logs) || innetTx.logs.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const method = Buffer.from(innetTx.method, "base64").toString("utf8");
|
||||
|
||||
if (method !== "publishMessage") {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// We use the sender address from innerTxs to build the emitterChain because the sender address
|
||||
// from the transaction is the bridge address (token bridge)
|
||||
const emitterChain = Buffer.from(algosdk.decodeAddress(innetTx.sender).publicKey).toString("hex");
|
||||
|
||||
const sequence = Number(`0x${Buffer.from(innetTx.logs[0], "base64").toString("hex")}`);
|
||||
|
||||
logger.info(
|
||||
`[algorand] Source event info: [tx: ${transaction.hash}][${CHAIN_ID_ALGORAND}/${emitterChain}/${sequence}]`
|
||||
);
|
||||
|
||||
return {
|
||||
name: "log-message-published",
|
||||
address: transaction.sender,
|
||||
chainId: CHAIN_ID_ALGORAND,
|
||||
txHash: transaction.hash,
|
||||
blockHeight: BigInt(transaction.blockNumber),
|
||||
blockTime: transaction.timestamp,
|
||||
attributes: {
|
||||
sender: emitterChain,
|
||||
sequence: sequence,
|
||||
payload: transaction.payload,
|
||||
nonce: 0, // https://developer.algorand.org/docs/get-details/ethereum_to_algorand/#nonces-validity-windows-and-leases
|
||||
consistencyLevel: 0,
|
||||
},
|
||||
};
|
||||
};
|
|
@ -0,0 +1,86 @@
|
|||
import { TransactionFoundEvent } from "../../../domain/entities";
|
||||
import { AlgorandTransaction } from "../../../domain/entities/algorand";
|
||||
import { CHAIN_ID_ALGORAND } from "@certusone/wormhole-sdk";
|
||||
import { findProtocol } from "../contractsMapper";
|
||||
import { parseVaa } from "@certusone/wormhole-sdk";
|
||||
import winston from "winston";
|
||||
|
||||
let logger: winston.Logger = winston.child({ module: "algorandRedeemedTransactionFoundMapper" });
|
||||
|
||||
const ALGORAND_CHAIN = "algorand";
|
||||
|
||||
export const algorandRedeemedTransactionFoundMapper = (
|
||||
transaction: AlgorandTransaction,
|
||||
filters: {
|
||||
applicationIds: string;
|
||||
applicationAddress: string;
|
||||
}[]
|
||||
): TransactionFoundEvent | undefined => {
|
||||
const method = Buffer.from(transaction.method, "base64").toString("utf8");
|
||||
|
||||
const applicationId = String(transaction.applicationId);
|
||||
const protocol = findProtocol(ALGORAND_CHAIN, applicationId, method, transaction.hash);
|
||||
|
||||
if (!protocol || protocol.type === "unknown") {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const vaaInformation = mappedVaaInformation(transaction.payload);
|
||||
|
||||
if (!vaaInformation) {
|
||||
logger.warn(
|
||||
`[algorand] Cannot mapper vaa information: [hash: ${transaction.hash}][protocol: ${protocol.type}/${protocol.method}]`
|
||||
);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const filter = filters.find((filter) => filter.applicationIds === applicationId);
|
||||
|
||||
const { emitterChain, emitterAddress, sequence } = vaaInformation;
|
||||
|
||||
logger.info(
|
||||
`[${ALGORAND_CHAIN}] Redeemed transaction info: [hash: ${transaction.hash}][VAA: ${emitterChain}/${emitterAddress}/${sequence}]`
|
||||
);
|
||||
|
||||
return {
|
||||
name: "transfer-redeemed",
|
||||
address: filter?.applicationAddress ?? applicationId,
|
||||
blockHeight: BigInt(transaction.blockNumber),
|
||||
blockTime: transaction.timestamp,
|
||||
chainId: CHAIN_ID_ALGORAND,
|
||||
txHash: transaction.hash,
|
||||
attributes: {
|
||||
from: transaction.sender,
|
||||
emitterChain: emitterChain,
|
||||
emitterAddress: emitterAddress,
|
||||
sequence: Number(sequence),
|
||||
status: TxStatus.Completed,
|
||||
protocol: protocol.method,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const mappedVaaInformation = (payload: string): VaaInformation | undefined => {
|
||||
const payloadToHex = Buffer.from(payload, "base64").toString("hex");
|
||||
const buffer = Buffer.from(payloadToHex, "hex");
|
||||
const vaa = parseVaa(buffer);
|
||||
|
||||
return {
|
||||
emitterChain: vaa.emitterChain,
|
||||
emitterAddress: vaa.emitterAddress.toString("hex").toUpperCase(),
|
||||
sequence: Number(vaa.sequence),
|
||||
};
|
||||
};
|
||||
|
||||
type VaaInformation = {
|
||||
emitterChain: number;
|
||||
emitterAddress: string;
|
||||
sequence: number;
|
||||
formAddress?: string;
|
||||
toAddress?: string;
|
||||
};
|
||||
|
||||
enum TxStatus {
|
||||
Completed = "completed",
|
||||
Failed = "failed",
|
||||
}
|
|
@ -1778,6 +1778,21 @@
|
|||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"chain": "algorand",
|
||||
"protocols": [
|
||||
{
|
||||
"addresses": ["842126029", "86525641"],
|
||||
"type": "Token Bridge",
|
||||
"methods": [
|
||||
{
|
||||
"methodId": "completeTransfer",
|
||||
"method": "Token Bridge"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import { RateLimitedWormchainJsonRPCBlockRepository } from "./wormchain/RateLimitedWormchainJsonRPCBlockRepository";
|
||||
import { RateLimitedAlgorandJsonRPCBlockRepository } from "./algorand/RateLimitedAlgorandJsonRPCBlockRepository";
|
||||
import { RateLimitedAptosJsonRPCBlockRepository } from "./aptos/RateLimitedAptosJsonRPCBlockRepository";
|
||||
import { RateLimitedEvmJsonRPCBlockRepository } from "./evm/RateLimitedEvmJsonRPCBlockRepository";
|
||||
import { RateLimitedSeiJsonRPCBlockRepository } from "./sei/RateLimitedSeiJsonRPCBlockRepository";
|
||||
import { RateLimitedSuiJsonRPCBlockRepository } from "./sui/RateLimitedSuiJsonRPCBlockRepository";
|
||||
import { WormchainJsonRPCBlockRepository } from "./wormchain/WormchainJsonRPCBlockRepository";
|
||||
import { AlgorandJsonRPCBlockRepository } from "./algorand/AlgorandJsonRPCBlockRepository";
|
||||
import { AptosJsonRPCBlockRepository } from "./aptos/AptosJsonRPCBlockRepository";
|
||||
import { SNSClient, SNSClientConfig } from "@aws-sdk/client-sns";
|
||||
import { SeiJsonRPCBlockRepository } from "./sei/SeiJsonRPCBlockRepository";
|
||||
|
@ -11,6 +13,7 @@ import { InstrumentedHttpProvider } from "../rpc/http/InstrumentedHttpProvider";
|
|||
import { Config } from "../config";
|
||||
import {
|
||||
WormchainRepository,
|
||||
AlgorandRepository,
|
||||
AptosRepository,
|
||||
JobRepository,
|
||||
SuiRepository,
|
||||
|
@ -41,6 +44,7 @@ import {
|
|||
} from ".";
|
||||
|
||||
const WORMCHAIN_CHAIN = "wormchain";
|
||||
const ALGORAND_CHAIN = "algorand";
|
||||
const SOLANA_CHAIN = "solana";
|
||||
const APTOS_CHAIN = "aptos";
|
||||
const EVM_CHAIN = "evm";
|
||||
|
@ -96,6 +100,7 @@ export class RepositoriesBuilder {
|
|||
|
||||
this.cfg.enabledPlatforms.forEach((chain) => {
|
||||
this.buildWormchainRepository(chain);
|
||||
this.buildAlgorandRepository(chain);
|
||||
this.buildSolanaRepository(chain);
|
||||
this.buildAptosRepository(chain);
|
||||
this.buildEvmRepository(chain);
|
||||
|
@ -119,6 +124,7 @@ export class RepositoriesBuilder {
|
|||
aptosRepo: this.getAptosRepository(),
|
||||
wormchainRepo: this.getWormchainRepository(),
|
||||
seiRepo: this.getSeiRepository(),
|
||||
algorandRepo: this.getAlgorandRepository(),
|
||||
}
|
||||
)
|
||||
);
|
||||
|
@ -167,6 +173,10 @@ export class RepositoriesBuilder {
|
|||
return this.getRepo("sei-repo");
|
||||
}
|
||||
|
||||
public getAlgorandRepository(): AlgorandRepository {
|
||||
return this.getRepo("algorand-repo");
|
||||
}
|
||||
|
||||
public close(): void {
|
||||
this.snsClient?.destroy();
|
||||
}
|
||||
|
@ -287,6 +297,22 @@ export class RepositoriesBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
private buildAlgorandRepository(chain: string): void {
|
||||
if (chain == ALGORAND_CHAIN) {
|
||||
const algoIndexerRpcs = this.cfg.chains[chain].rpcs[1] as unknown as string[];
|
||||
const algoRpcs = this.cfg.chains[chain].rpcs[0] as unknown as string[];
|
||||
|
||||
const algoIndexerPools = this.createDefaultProviderPools(chain, algoIndexerRpcs);
|
||||
const algoV2Pools = this.createDefaultProviderPools(chain, algoRpcs);
|
||||
|
||||
const seiRepository = new RateLimitedAlgorandJsonRPCBlockRepository(
|
||||
new AlgorandJsonRPCBlockRepository(algoV2Pools, algoIndexerPools)
|
||||
);
|
||||
|
||||
this.repositories.set("algorand-repo", seiRepository);
|
||||
}
|
||||
}
|
||||
|
||||
private getRepo(name: string): any {
|
||||
const repo = this.repositories.get(name);
|
||||
if (!repo)
|
||||
|
@ -321,10 +347,14 @@ export class RepositoriesBuilder {
|
|||
return pools;
|
||||
}
|
||||
|
||||
private createDefaultProviderPools(chain: string) {
|
||||
private createDefaultProviderPools(chain: string, rpcs?: string[]) {
|
||||
if (!rpcs) {
|
||||
rpcs = this.cfg.chains[chain].rpcs;
|
||||
}
|
||||
|
||||
const cfg = this.cfg.chains[chain];
|
||||
const pools = providerPoolSupplier(
|
||||
cfg.rpcs.map((url) => ({ url })),
|
||||
rpcs.map((url) => ({ url })),
|
||||
(rpcCfg: RpcConfig) => this.createHttpClient(chain, rpcCfg.url),
|
||||
POOL_STRATEGY
|
||||
);
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
import { PollSei, PollSeiConfig, PollSeiConfigProps } from "../../domain/actions/sei/PollSei";
|
||||
import { FileMetadataRepository, SnsEventRepository } from "./index";
|
||||
import { wormchainRedeemedTransactionFoundMapper } from "../mappers/wormchain/wormchainRedeemedTransactionFoundMapper";
|
||||
import { algorandRedeemedTransactionFoundMapper } from "../mappers/algorand/algorandRedeemedTransactionFoundMapper";
|
||||
import { JobDefinition, Handler, LogFoundEvent } from "../../domain/entities";
|
||||
import { aptosRedeemedTransactionFoundMapper } from "../mappers/aptos/aptosRedeemedTransactionFoundMapper";
|
||||
import { wormchainLogMessagePublishedMapper } from "../mappers/wormchain/wormchainLogMessagePublishedMapper";
|
||||
import { seiRedeemedTransactionFoundMapper } from "../mappers/sei/seiRedeemedTransactionFoundMapper";
|
||||
import { algorandLogMessagePublishedMapper } from "../mappers/algorand/algorandLogMessagePublishedMapper";
|
||||
import { suiRedeemedTransactionFoundMapper } from "../mappers/sui/suiRedeemedTransactionFoundMapper";
|
||||
import { aptosLogMessagePublishedMapper } from "../mappers/aptos/aptosLogMessagePublishedMapper";
|
||||
import { suiLogMessagePublishedMapper } from "../mappers/sui/suiLogMessagePublishedMapper";
|
||||
import { HandleAlgorandTransactions } from "../../domain/actions/algorand/HandleAlgorandTransactions";
|
||||
import { HandleSolanaTransactions } from "../../domain/actions/solana/HandleSolanaTransactions";
|
||||
import { HandleAptosTransactions } from "../../domain/actions/aptos/HandleAptosTransactions";
|
||||
import { HandleWormchainRedeems } from "../../domain/actions/wormchain/HandleWormchainRedeems";
|
||||
|
@ -24,6 +27,7 @@ import {
|
|||
import {
|
||||
SolanaSlotRepository,
|
||||
WormchainRepository,
|
||||
AlgorandRepository,
|
||||
EvmBlockRepository,
|
||||
MetadataRepository,
|
||||
AptosRepository,
|
||||
|
@ -56,6 +60,11 @@ import {
|
|||
PollAptosTransactionsConfig,
|
||||
PollAptos,
|
||||
} from "../../domain/actions/aptos/PollAptos";
|
||||
import {
|
||||
PollAlgorandConfigProps,
|
||||
PollAlgorandConfig,
|
||||
PollAlgorand,
|
||||
} from "../../domain/actions/algorand/PollAlgorand";
|
||||
|
||||
export class StaticJobRepository implements JobRepository {
|
||||
private fileRepo: FileMetadataRepository;
|
||||
|
@ -75,6 +84,7 @@ export class StaticJobRepository implements JobRepository {
|
|||
private aptosRepo: AptosRepository;
|
||||
private wormchainRepo: WormchainRepository;
|
||||
private seiRepo: SeiRepository;
|
||||
private algorandRepo: AlgorandRepository;
|
||||
|
||||
constructor(
|
||||
environment: string,
|
||||
|
@ -90,6 +100,7 @@ export class StaticJobRepository implements JobRepository {
|
|||
aptosRepo: AptosRepository;
|
||||
wormchainRepo: WormchainRepository;
|
||||
seiRepo: SeiRepository;
|
||||
algorandRepo: AlgorandRepository;
|
||||
}
|
||||
) {
|
||||
this.fileRepo = new FileMetadataRepository(path);
|
||||
|
@ -102,6 +113,7 @@ export class StaticJobRepository implements JobRepository {
|
|||
this.aptosRepo = repos.aptosRepo;
|
||||
this.wormchainRepo = repos.wormchainRepo;
|
||||
this.seiRepo = repos.seiRepo;
|
||||
this.algorandRepo = repos.algorandRepo;
|
||||
this.environment = environment;
|
||||
this.dryRun = dryRun;
|
||||
this.fill();
|
||||
|
@ -207,7 +219,6 @@ export class StaticJobRepository implements JobRepository {
|
|||
}),
|
||||
jobDef.source.records
|
||||
);
|
||||
|
||||
const pollSei = (jobDef: JobDefinition) =>
|
||||
new PollSei(
|
||||
this.seiRepo,
|
||||
|
@ -218,6 +229,16 @@ export class StaticJobRepository implements JobRepository {
|
|||
id: jobDef.id,
|
||||
})
|
||||
);
|
||||
const pollAlgorand = (jobDef: JobDefinition) =>
|
||||
new PollAlgorand(
|
||||
this.algorandRepo,
|
||||
this.metadataRepo,
|
||||
this.statsRepo,
|
||||
new PollAlgorandConfig({
|
||||
...(jobDef.source.config as PollAlgorandConfigProps),
|
||||
id: jobDef.id,
|
||||
})
|
||||
);
|
||||
|
||||
this.sources.set("PollEvm", pollEvm);
|
||||
this.sources.set("PollSolanaTransactions", pollSolanaTransactions);
|
||||
|
@ -225,6 +246,7 @@ export class StaticJobRepository implements JobRepository {
|
|||
this.sources.set("PollAptos", pollAptos);
|
||||
this.sources.set("PollWormchain", pollWormchain);
|
||||
this.sources.set("PollSei", pollSei);
|
||||
this.sources.set("PollAlgorand", pollAlgorand);
|
||||
}
|
||||
|
||||
private loadMappers(): void {
|
||||
|
@ -238,6 +260,11 @@ export class StaticJobRepository implements JobRepository {
|
|||
this.mappers.set("aptosRedeemedTransactionFoundMapper", aptosRedeemedTransactionFoundMapper);
|
||||
this.mappers.set("wormchainLogMessagePublishedMapper", wormchainLogMessagePublishedMapper);
|
||||
this.mappers.set("seiRedeemedTransactionFoundMapper", seiRedeemedTransactionFoundMapper);
|
||||
this.mappers.set(
|
||||
"algorandRedeemedTransactionFoundMapper",
|
||||
algorandRedeemedTransactionFoundMapper
|
||||
);
|
||||
this.mappers.set("algorandLogMessagePublishedMapper", algorandLogMessagePublishedMapper);
|
||||
this.mappers.set(
|
||||
"wormchainRedeemedTransactionFoundMapper",
|
||||
wormchainRedeemedTransactionFoundMapper
|
||||
|
@ -331,6 +358,16 @@ export class StaticJobRepository implements JobRepository {
|
|||
return instance.handle.bind(instance);
|
||||
};
|
||||
|
||||
const handleAlgorandTransactions = async (config: any, target: string, mapper: any) => {
|
||||
const instance = new HandleAlgorandTransactions(
|
||||
config,
|
||||
mapper,
|
||||
await this.getTarget(target),
|
||||
this.statsRepo
|
||||
);
|
||||
return instance.handle.bind(instance);
|
||||
};
|
||||
|
||||
this.handlers.set("HandleEvmLogs", handleEvmLogs);
|
||||
this.handlers.set("HandleEvmTransactions", handleEvmTransactions);
|
||||
this.handlers.set("HandleSolanaTransactions", handleSolanaTx);
|
||||
|
@ -339,6 +376,7 @@ export class StaticJobRepository implements JobRepository {
|
|||
this.handlers.set("HandleWormchainLogs", handleWormchainLogs);
|
||||
this.handlers.set("HandleWormchainRedeems", handleWormchainRedeems);
|
||||
this.handlers.set("HandleSeiRedeems", handleSeiRedeems);
|
||||
this.handlers.set("HandleAlgorandTransactions", handleAlgorandTransactions);
|
||||
}
|
||||
|
||||
private async getTarget(target: string): Promise<(items: any[]) => Promise<void>> {
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
import { InstrumentedHttpProvider } from "../../rpc/http/InstrumentedHttpProvider";
|
||||
import { AlgorandTransaction } from "../../../domain/entities/algorand";
|
||||
import { AlgorandRepository } from "../../../domain/repositories";
|
||||
import { ProviderPool } from "@xlabs/rpc-pool";
|
||||
import winston from "winston";
|
||||
|
||||
type ProviderPoolMap = ProviderPool<InstrumentedHttpProvider>;
|
||||
|
||||
let TRANSACTIONS_ENDPOINT = "/v2/transactions";
|
||||
let STATUS_ENDPOINT = "/v2/status";
|
||||
|
||||
export class AlgorandJsonRPCBlockRepository implements AlgorandRepository {
|
||||
private readonly logger: winston.Logger;
|
||||
protected algoV2Pools: ProviderPoolMap;
|
||||
protected algoIndexerPools: ProviderPoolMap;
|
||||
|
||||
constructor(
|
||||
algoV2Pools: ProviderPool<InstrumentedHttpProvider>,
|
||||
algoIndexerPools: ProviderPool<InstrumentedHttpProvider>
|
||||
) {
|
||||
this.logger = winston.child({ module: "AlgorandJsonRPCBlockRepository" });
|
||||
this.algoV2Pools = algoV2Pools;
|
||||
this.algoIndexerPools = algoIndexerPools;
|
||||
}
|
||||
|
||||
async getBlockHeight(): Promise<bigint | undefined> {
|
||||
let result: ResultStatus;
|
||||
result = await this.algoV2Pools.get().get<typeof result>(STATUS_ENDPOINT);
|
||||
return BigInt(result["last-round"]);
|
||||
}
|
||||
|
||||
async getTransactions(
|
||||
applicationId: string,
|
||||
fromBlock: bigint,
|
||||
toBlock: bigint
|
||||
): Promise<AlgorandTransaction[]> {
|
||||
try {
|
||||
let result: ResultTransactions;
|
||||
result = await this.algoIndexerPools
|
||||
.get()
|
||||
.get<typeof result>(
|
||||
`${TRANSACTIONS_ENDPOINT}?application-id=${Number(
|
||||
applicationId
|
||||
)}&min-round=${fromBlock}&max-round=${toBlock}`
|
||||
);
|
||||
|
||||
if (!result.transactions || result.transactions.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return result.transactions.map((tx) => {
|
||||
return {
|
||||
payload: tx["application-transaction"]?.["application-args"][1],
|
||||
method: tx["application-transaction"]?.["application-args"][0],
|
||||
applicationId: tx["application-transaction"]["application-id"],
|
||||
blockNumber: tx["confirmed-round"],
|
||||
timestamp: tx["round-time"],
|
||||
innerTxs: tx["inner-txns"]?.map((innerTx) => {
|
||||
// build inner transactions
|
||||
return {
|
||||
applicationId: innerTx["application-transaction"]?.["application-id"],
|
||||
payload: innerTx["application-transaction"]?.["application-args"][1],
|
||||
method: innerTx["application-transaction"]?.["application-args"][0],
|
||||
sender: innerTx.sender,
|
||||
logs: innerTx.logs,
|
||||
};
|
||||
}),
|
||||
sender: tx.sender,
|
||||
hash: tx.id,
|
||||
};
|
||||
});
|
||||
} catch (e) {
|
||||
this.handleError(
|
||||
`Application id: ${applicationId} and range params: ${fromBlock} - ${toBlock}, error: ${e}`,
|
||||
"getTransactions"
|
||||
);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private handleError(e: any, method: string) {
|
||||
this.logger.error(`[algorand] Error calling ${method}: ${e.message ?? e}`);
|
||||
}
|
||||
}
|
||||
|
||||
type ResultStatus = {
|
||||
"last-round": number;
|
||||
};
|
||||
|
||||
type ResultTransactions = {
|
||||
"current-round": number;
|
||||
"next-token": string;
|
||||
transactions: {
|
||||
"tx-type": string;
|
||||
"application-transaction": {
|
||||
"application-id": string;
|
||||
"application-args": string[];
|
||||
};
|
||||
id: string;
|
||||
sender: string;
|
||||
"confirmed-round": number;
|
||||
"application-args": string[];
|
||||
"round-time": number;
|
||||
logs: string[];
|
||||
"inner-txns": {
|
||||
sender: string;
|
||||
logs: string[];
|
||||
"application-transaction": {
|
||||
"application-id": string;
|
||||
"application-args": string[];
|
||||
};
|
||||
}[];
|
||||
}[];
|
||||
};
|
|
@ -0,0 +1,29 @@
|
|||
import { RateLimitedRPCRepository } from "../RateLimitedRPCRepository";
|
||||
import { AlgorandTransaction } from "../../../domain/entities/algorand";
|
||||
import { AlgorandRepository } from "../../../domain/repositories";
|
||||
import { Options } from "../common/rateLimitedOptions";
|
||||
import winston from "winston";
|
||||
|
||||
export class RateLimitedAlgorandJsonRPCBlockRepository
|
||||
extends RateLimitedRPCRepository<AlgorandRepository>
|
||||
implements AlgorandRepository
|
||||
{
|
||||
constructor(delegate: AlgorandRepository, opts: Options = { period: 10_000, limit: 1000 }) {
|
||||
super(delegate, opts);
|
||||
this.logger = winston.child({ module: "RateLimitedAlgorandJsonRPCBlockRepository" });
|
||||
}
|
||||
|
||||
getTransactions(
|
||||
applicationId: string,
|
||||
fromBlock: bigint,
|
||||
toBlock: bigint
|
||||
): Promise<AlgorandTransaction[]> {
|
||||
return this.breaker
|
||||
.fn(() => this.delegate.getTransactions(applicationId, fromBlock, toBlock))
|
||||
.execute();
|
||||
}
|
||||
|
||||
getBlockHeight(): Promise<bigint | undefined> {
|
||||
return this.breaker.fn(() => this.delegate.getBlockHeight()).execute();
|
||||
}
|
||||
}
|
|
@ -20,3 +20,4 @@ export * from "./solana/Web3SolanaSlotRepository";
|
|||
export * from "./solana/RateLimitedSolanaSlotRepository";
|
||||
export * from "./sui/SuiJsonRPCBlockRepository";
|
||||
export * from "./wormchain/WormchainJsonRPCBlockRepository";
|
||||
export * from "./algorand/AlgorandJsonRPCBlockRepository";
|
||||
|
|
|
@ -17,7 +17,7 @@ let TRANSACTION_ENDPOINT = "/tx";
|
|||
let BLOCK_ENDPOINT = "/block";
|
||||
|
||||
const GROW_SLEEP_TIME = 350;
|
||||
const MAX_ATTEMPTS = 10;
|
||||
const MAX_ATTEMPTS = 20;
|
||||
|
||||
type ProviderPoolMap = ProviderPool<InstrumentedHttpProvider>;
|
||||
|
||||
|
@ -201,10 +201,12 @@ export class WormchainJsonRPCBlockRepository implements WormchainRepository {
|
|||
}
|
||||
}
|
||||
|
||||
if (!resultTransactionSearch || attempts > MAX_ATTEMPTS) {
|
||||
throw new Error(
|
||||
`[getRedeems] The transaction \n${query}\n with chainId: ${ibcTransaction.targetChain} never ended`
|
||||
);
|
||||
if (
|
||||
!resultTransactionSearch ||
|
||||
!resultTransactionSearch.result ||
|
||||
!resultTransactionSearch.result.txs
|
||||
) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return resultTransactionSearch.result.txs.map((tx) => {
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
import { afterEach, describe, it, expect, jest } from "@jest/globals";
|
||||
import { thenWaitForAssertion } from "../../../wait-assertion";
|
||||
import { AlgorandTransaction } from "../../../../src/domain/entities/algorand";
|
||||
import {
|
||||
PollAlgorandMetadata,
|
||||
PollAlgorandConfig,
|
||||
PollAlgorand,
|
||||
} from "../../../../src/domain/actions";
|
||||
import {
|
||||
AlgorandRepository,
|
||||
MetadataRepository,
|
||||
StatRepository,
|
||||
} from "../../../../src/domain/repositories";
|
||||
|
||||
let getBlockHeightSpy: jest.SpiedFunction<AlgorandRepository["getBlockHeight"]>;
|
||||
let getTransactionsSpy: jest.SpiedFunction<AlgorandRepository["getTransactions"]>;
|
||||
let metadataSaveSpy: jest.SpiedFunction<MetadataRepository<PollAlgorandMetadata>["save"]>;
|
||||
|
||||
let handlerSpy: jest.SpiedFunction<(txs: AlgorandTransaction[]) => Promise<void>>;
|
||||
|
||||
let metadataRepo: MetadataRepository<PollAlgorandMetadata>;
|
||||
let algorandRepo: AlgorandRepository;
|
||||
let statsRepo: StatRepository;
|
||||
|
||||
let handlers = {
|
||||
working: (txs: AlgorandTransaction[]) => Promise.resolve(),
|
||||
failing: (txs: AlgorandTransaction[]) => Promise.reject(),
|
||||
};
|
||||
|
||||
let pollAlgorand: PollAlgorand;
|
||||
|
||||
let cfg = new PollAlgorandConfig({
|
||||
chain: "algorand",
|
||||
applicationIds: ["842125965"],
|
||||
chainId: 8,
|
||||
environment: "testnet",
|
||||
});
|
||||
|
||||
describe("GetAlgorandTransactions", () => {
|
||||
afterEach(async () => {
|
||||
await pollAlgorand.stop();
|
||||
});
|
||||
|
||||
it("should be use from and batch size cfg, and process tx because is a wormhole redeem", async () => {
|
||||
// Given
|
||||
const txs = [
|
||||
{
|
||||
payload:
|
||||
"AQAAAAQNAMvOqeysTxjYZtgYEOHVj77EPk0eVW0t1/xm5erC78GEJKVo/TJWgLpH+rPrv39ujSllqUNBG+4LCj1vVEO6HUYAASWlSS/cAIzz6mm+ZqpputkD25jFwEKycUGqjj39nP8yQzvR4PEPC75tIIgDLoj0HSzybbH1N7hFykOVE31Tg1cBAliHbjlP/OhEnOspo9WryTrmRmlQNkVGZ+5TRHkwy5WtVVBj5W0BE0M/hnkVAlny37QN99DRTl6cIs0IM4Bpw+QBA3gXwF6aqPF8aVp3vieQNweFLukFFqGXSdxV6KYOmfojc8/P7wLrD+4P9I2Y1XNfi9IoyihI+anU9IFDPpUkmuQABMJXlTMcms2Yit3JBLVjbiImzbtzPIMCRMLXG082cJVtevhCFtxR94pkOF75THhZy3vZ7v2oCQJXp6fssKGP2jgABihy7P7j+ovzB0/i+emkEXZMAoJcrviPbx11A+hAS36ubl+pk1sO1K4d7FM4HjP/f0WosNoCBXqfyD3jCrlZO/kACBZCCH+WTb6bYLPJC+nzzjvHA73QDH4lDkA4KjiJq3Z8MXRqRn6vFR7vjszj9NJCRw9xD6xscpMW0sNXcMFDDqsACYzV/6FXG/XZ7Kd4FYfoQ2ZHLSeRxRF4Xrt5YvsyhxOTHdj0S2b7eb7B41CxDUKzhYvWGf3V4WhYfvRgMFou944BDC8szu1vORgXN/EWBAJwBRmjOZXpHNH7GRIFupubGSQPIDtuom2/DHo2F77vxKC7HXVMVHbV7C3oOgB8w2lXfSABD9wQtyp+4hZAHFNOclrkTNgO/BRFaxXbTwKzKMv1ax3+O21cf3eSlYfdlwTz2DJrts/M/72v18EZriEFGPnFtHUAEOa6k9hnDJZys1ZJ4cSsd77DIFSRTc6GxbaWBTYXfMOjTMSArzdUl5XQKFTRIrDMHRY+sM4dz5dAWc7IM1rYcIoAES8aaahKohCHoR/d//Z0ZT1P4+LcgrF4rABIxki6mDbSaz+86v+derrWYHErb8+s5OpH5qwGbrftiXsDlebiyN8AEg6Rp7M4ooj+qsdqgzt4DUmLgUU2eqBVF1nWuEt0lJgNWAQ2ZPgqp2R+NcsxXEys/lIpSaYgxT3pGLbFWRdpLWgAZnqa+gADj5AABgAAAAAAAAAAAAAAAA4ILwb/ZX2UMQy4zosNmgRUHYBSAAAAAAADXAIBAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACw7dfAAAAAAAAAAAAAAAAuX7574c0xxkE2AAvi2vGbdnEim4ABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABK8EJrAAgAAAAAAAAAAAAAAABTZwZsNNSHRYgR77UGp6Kv2tuOi3dvcm1ob2xlRGVwb3NpdAAAAAAAAAAAAAAAAAurcc/Wfp8DdXO0rccmziZuKxssAAAAAAAAAAAAAAAASvBCawAXAAAAAAAAAAAAAAAAC6txz9Z+nwN1c7StxybOJm4rGyw=",
|
||||
applicationId: "842126029",
|
||||
blockNumber: 40085294,
|
||||
timestamp: 1719311110,
|
||||
innerTxs: [
|
||||
{
|
||||
sender: "PG56DVKH6F3RXJASLIR4AXIXDYTFK2DQWIEOLDY22HEX5IPRE47HNFINKY",
|
||||
},
|
||||
],
|
||||
sender: "C3EXCPEEMYTIJ2EYUMEMLBDHIJ7J2KAHGKFHWD4GQX5MP7PYZO7O2C6YZE",
|
||||
hash: "SERG7537SOJADJO5LC2J5SC6DD2VONL76B64YB5PDID2T3FONK5Q",
|
||||
},
|
||||
];
|
||||
|
||||
givenAlgorandRepository(402222n, txs);
|
||||
givenMetadataRepository();
|
||||
givenStatsRepository();
|
||||
givenPollAlgorandTxs();
|
||||
|
||||
// Whem
|
||||
await whenPollAlgorandStarts();
|
||||
|
||||
// Then
|
||||
await thenWaitForAssertion(
|
||||
() => expect(getBlockHeightSpy).toHaveReturnedTimes(1),
|
||||
() => expect(getTransactionsSpy).toBeCalledWith("842125965", 402222n, 402222n)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
const givenAlgorandRepository = (height?: bigint, txs: any = []) => {
|
||||
algorandRepo = {
|
||||
getBlockHeight: () => Promise.resolve(height),
|
||||
getTransactions: () => Promise.resolve(txs),
|
||||
};
|
||||
|
||||
getBlockHeightSpy = jest.spyOn(algorandRepo, "getBlockHeight");
|
||||
getTransactionsSpy = jest.spyOn(algorandRepo, "getTransactions");
|
||||
handlerSpy = jest.spyOn(handlers, "working");
|
||||
};
|
||||
|
||||
const givenMetadataRepository = (data?: PollAlgorandMetadata) => {
|
||||
metadataRepo = {
|
||||
get: () => Promise.resolve(data),
|
||||
save: () => Promise.resolve(),
|
||||
};
|
||||
metadataSaveSpy = jest.spyOn(metadataRepo, "save");
|
||||
};
|
||||
|
||||
const givenStatsRepository = () => {
|
||||
statsRepo = {
|
||||
count: () => {},
|
||||
measure: () => {},
|
||||
report: () => Promise.resolve(""),
|
||||
};
|
||||
};
|
||||
|
||||
const givenPollAlgorandTxs = (from?: bigint) => {
|
||||
cfg.setFromBlock(from);
|
||||
pollAlgorand = new PollAlgorand(algorandRepo, metadataRepo, statsRepo, cfg);
|
||||
};
|
||||
|
||||
const whenPollAlgorandStarts = async () => {
|
||||
pollAlgorand.run([handlers.working]);
|
||||
};
|
|
@ -0,0 +1,113 @@
|
|||
import { afterEach, describe, it, expect, jest } from "@jest/globals";
|
||||
import { AlgorandTransaction } from "../../../../src/domain/entities/algorand";
|
||||
import { StatRepository } from "../../../../src/domain/repositories";
|
||||
import { LogFoundEvent } from "../../../../src/domain/entities";
|
||||
import {
|
||||
HandleAlgorandTransactionsOptions,
|
||||
HandleAlgorandTransactions,
|
||||
} from "../../../../src/domain/actions/algorand/HandleAlgorandTransactions";
|
||||
|
||||
let targetRepoSpy: jest.SpiedFunction<(typeof targetRepo)["save"]>;
|
||||
let statsRepo: StatRepository;
|
||||
|
||||
let handleAlgorandTransactions: HandleAlgorandTransactions;
|
||||
let txs: AlgorandTransaction[];
|
||||
let cfg: HandleAlgorandTransactionsOptions;
|
||||
|
||||
describe("HandleAlgorandTransactions", () => {
|
||||
afterEach(async () => {});
|
||||
|
||||
it("should be able to map source events tx", async () => {
|
||||
// Given
|
||||
givenConfig();
|
||||
givenStatsRepository();
|
||||
givenHandleEvmLogs();
|
||||
|
||||
// When
|
||||
const result = await handleAlgorandTransactions.handle(txs);
|
||||
|
||||
// Then
|
||||
expect(result).toHaveLength(1);
|
||||
expect(result[0].name).toBe("log-message-published");
|
||||
expect(result[0].chainId).toBe(8);
|
||||
expect(result[0].txHash).toBe("SQA7S37MCLGHQRMFZHRNUNUFJ6PJKRZN5RO52NMEWJU5B365SINQ");
|
||||
expect(result[0].address).toBe("MG3DIJNS3JTVKUAQGFV5BQTDAK26OUM3SRXSLIFWVUS67V54VPKDUJQTOQ");
|
||||
});
|
||||
});
|
||||
|
||||
const mapper = (tx: AlgorandTransaction) => {
|
||||
return {
|
||||
name: "log-message-published",
|
||||
address: "MG3DIJNS3JTVKUAQGFV5BQTDAK26OUM3SRXSLIFWVUS67V54VPKDUJQTOQ",
|
||||
chainId: 8,
|
||||
txHash: "SQA7S37MCLGHQRMFZHRNUNUFJ6PJKRZN5RO52NMEWJU5B365SINQ",
|
||||
blockHeight: 40085318n,
|
||||
blockTime: 1719311180,
|
||||
attributes: {
|
||||
sender: "67e93fa6c8ac5c819990aa7340c0c16b508abb1178be9b30d024b8ac25193d45",
|
||||
sequence: 10576,
|
||||
payload: "AAAAADcXNho=",
|
||||
nonce: 0,
|
||||
consistencyLevel: 0,
|
||||
protocol: "Token Bridge",
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const targetRepo = {
|
||||
save: async (events: LogFoundEvent<Record<string, string>>[]) => {
|
||||
Promise.resolve();
|
||||
},
|
||||
failingSave: async (events: LogFoundEvent<Record<string, string>>[]) => {
|
||||
Promise.reject();
|
||||
},
|
||||
};
|
||||
|
||||
const givenHandleEvmLogs = (targetFn: "save" | "failingSave" = "save") => {
|
||||
targetRepoSpy = jest.spyOn(targetRepo, targetFn);
|
||||
handleAlgorandTransactions = new HandleAlgorandTransactions(
|
||||
cfg,
|
||||
mapper,
|
||||
() => Promise.resolve(),
|
||||
statsRepo
|
||||
);
|
||||
};
|
||||
|
||||
const givenConfig = () => {
|
||||
cfg = {
|
||||
id: "poll-log-message-published-algorand",
|
||||
metricName: "process_source_event",
|
||||
filter: [
|
||||
{
|
||||
applicationIds: "842125965",
|
||||
applicationAddress: "J476J725L4JTOI2YU6DAI4E23LYUECLZR7RCYZ3LK6QFHX4M54ZI53SGXQ",
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
const givenStatsRepository = () => {
|
||||
statsRepo = {
|
||||
count: () => {},
|
||||
measure: () => {},
|
||||
report: () => Promise.resolve(""),
|
||||
};
|
||||
};
|
||||
|
||||
txs = [
|
||||
{
|
||||
payload: "AAAAADcXNho=",
|
||||
applicationId: "842126029",
|
||||
blockNumber: 40085318,
|
||||
timestamp: 1719311180,
|
||||
method: "Y29tcGxldGVUcmFuc2Zlcg==",
|
||||
innerTxs: [
|
||||
{
|
||||
logs: ["AAAAAAAAKVA="],
|
||||
sender: "M7UT7JWIVROIDGMQVJZUBQGBNNIIVOYRPC7JWMGQES4KYJIZHVCRZEGFRQ",
|
||||
},
|
||||
],
|
||||
sender: "MG3DIJNS3JTVKUAQGFV5BQTDAK26OUM3SRXSLIFWVUS67V54VPKDUJQTOQ",
|
||||
hash: "SQA7S37MCLGHQRMFZHRNUNUFJ6PJKRZN5RO52NMEWJU5B365SINQ",
|
||||
},
|
||||
];
|
|
@ -0,0 +1,110 @@
|
|||
import { afterEach, describe, it, expect, jest } from "@jest/globals";
|
||||
import { thenWaitForAssertion } from "../../../wait-assertion";
|
||||
import { AlgorandTransaction } from "../../../../src/domain/entities/algorand";
|
||||
import {
|
||||
PollAlgorandMetadata,
|
||||
PollAlgorandConfig,
|
||||
PollAlgorand,
|
||||
} from "../../../../src/domain/actions";
|
||||
import {
|
||||
AlgorandRepository,
|
||||
MetadataRepository,
|
||||
StatRepository,
|
||||
} from "../../../../src/domain/repositories";
|
||||
|
||||
let cfg = new PollAlgorandConfig({
|
||||
chain: "algorand",
|
||||
applicationIds: ["842125965"],
|
||||
chainId: 8,
|
||||
environment: "testnet",
|
||||
});
|
||||
|
||||
let getBlockHeightSpy: jest.SpiedFunction<AlgorandRepository["getBlockHeight"]>;
|
||||
let getTransactionsSpy: jest.SpiedFunction<AlgorandRepository["getTransactions"]>;
|
||||
let handlerSpy: jest.SpiedFunction<(txs: AlgorandTransaction[]) => Promise<void>>;
|
||||
let metadataSaveSpy: jest.SpiedFunction<MetadataRepository<PollAlgorandMetadata>["save"]>;
|
||||
|
||||
let metadataRepo: MetadataRepository<PollAlgorandMetadata>;
|
||||
let algorandRepo: AlgorandRepository;
|
||||
let statsRepo: StatRepository;
|
||||
|
||||
let handlers = {
|
||||
working: (txs: AlgorandTransaction[]) => Promise.resolve(),
|
||||
failing: (txs: AlgorandTransaction[]) => Promise.reject(),
|
||||
};
|
||||
|
||||
let pollAlgorand: PollAlgorand;
|
||||
|
||||
describe("PollAlgorand", () => {
|
||||
afterEach(async () => {
|
||||
await pollAlgorand.stop();
|
||||
});
|
||||
|
||||
it("should be able to read logs from latest block when no fromBlock is configured", async () => {
|
||||
const currentHeight = 10n;
|
||||
|
||||
const txs = [
|
||||
{
|
||||
payload:
|
||||
"AQAAAAQNAMvOqeysTxjYZtgYEOHVj77EPk0eVW0t1/xm5erC78GEJKVo/TJWgLpH+rPrv39ujSllqUNBG+4LCj1vVEO6HUYAASWlSS/cAIzz6mm+ZqpputkD25jFwEKycUGqjj39nP8yQzvR4PEPC75tIIgDLoj0HSzybbH1N7hFykOVE31Tg1cBAliHbjlP/OhEnOspo9WryTrmRmlQNkVGZ+5TRHkwy5WtVVBj5W0BE0M/hnkVAlny37QN99DRTl6cIs0IM4Bpw+QBA3gXwF6aqPF8aVp3vieQNweFLukFFqGXSdxV6KYOmfojc8/P7wLrD+4P9I2Y1XNfi9IoyihI+anU9IFDPpUkmuQABMJXlTMcms2Yit3JBLVjbiImzbtzPIMCRMLXG082cJVtevhCFtxR94pkOF75THhZy3vZ7v2oCQJXp6fssKGP2jgABihy7P7j+ovzB0/i+emkEXZMAoJcrviPbx11A+hAS36ubl+pk1sO1K4d7FM4HjP/f0WosNoCBXqfyD3jCrlZO/kACBZCCH+WTb6bYLPJC+nzzjvHA73QDH4lDkA4KjiJq3Z8MXRqRn6vFR7vjszj9NJCRw9xD6xscpMW0sNXcMFDDqsACYzV/6FXG/XZ7Kd4FYfoQ2ZHLSeRxRF4Xrt5YvsyhxOTHdj0S2b7eb7B41CxDUKzhYvWGf3V4WhYfvRgMFou944BDC8szu1vORgXN/EWBAJwBRmjOZXpHNH7GRIFupubGSQPIDtuom2/DHo2F77vxKC7HXVMVHbV7C3oOgB8w2lXfSABD9wQtyp+4hZAHFNOclrkTNgO/BRFaxXbTwKzKMv1ax3+O21cf3eSlYfdlwTz2DJrts/M/72v18EZriEFGPnFtHUAEOa6k9hnDJZys1ZJ4cSsd77DIFSRTc6GxbaWBTYXfMOjTMSArzdUl5XQKFTRIrDMHRY+sM4dz5dAWc7IM1rYcIoAES8aaahKohCHoR/d//Z0ZT1P4+LcgrF4rABIxki6mDbSaz+86v+derrWYHErb8+s5OpH5qwGbrftiXsDlebiyN8AEg6Rp7M4ooj+qsdqgzt4DUmLgUU2eqBVF1nWuEt0lJgNWAQ2ZPgqp2R+NcsxXEys/lIpSaYgxT3pGLbFWRdpLWgAZnqa+gADj5AABgAAAAAAAAAAAAAAAA4ILwb/ZX2UMQy4zosNmgRUHYBSAAAAAAADXAIBAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACw7dfAAAAAAAAAAAAAAAAuX7574c0xxkE2AAvi2vGbdnEim4ABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABK8EJrAAgAAAAAAAAAAAAAAABTZwZsNNSHRYgR77UGp6Kv2tuOi3dvcm1ob2xlRGVwb3NpdAAAAAAAAAAAAAAAAAurcc/Wfp8DdXO0rccmziZuKxssAAAAAAAAAAAAAAAASvBCawAXAAAAAAAAAAAAAAAAC6txz9Z+nwN1c7StxybOJm4rGyw=",
|
||||
applicationId: "842126029",
|
||||
blockNumber: 40085294,
|
||||
timestamp: 1719311110,
|
||||
innerTxs: [
|
||||
{
|
||||
sender: "PG56DVKH6F3RXJASLIR4AXIXDYTFK2DQWIEOLDY22HEX5IPRE47HNFINKY",
|
||||
},
|
||||
],
|
||||
sender: "C3EXCPEEMYTIJ2EYUMEMLBDHIJ7J2KAHGKFHWD4GQX5MP7PYZO7O2C6YZE",
|
||||
hash: "SERG7537SOJADJO5LC2J5SC6DD2VONL76B64YB5PDID2T3FONK5Q",
|
||||
},
|
||||
];
|
||||
givenAlgorandRepository(currentHeight, txs);
|
||||
givenMetadataRepository();
|
||||
givenStatsRepository();
|
||||
givenPollAlgorandTxs();
|
||||
|
||||
await whenpollAlgorandLogsStarts();
|
||||
|
||||
await thenWaitForAssertion(
|
||||
() => expect(getBlockHeightSpy).toHaveReturnedTimes(1),
|
||||
() => expect(getTransactionsSpy).toHaveBeenCalledWith("842125965", 10n, 10n)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
const givenAlgorandRepository = (height?: bigint, txs: any = []) => {
|
||||
algorandRepo = {
|
||||
getBlockHeight: () => Promise.resolve(height),
|
||||
getTransactions: () => Promise.resolve(txs),
|
||||
};
|
||||
|
||||
getBlockHeightSpy = jest.spyOn(algorandRepo, "getBlockHeight");
|
||||
getTransactionsSpy = jest.spyOn(algorandRepo, "getTransactions");
|
||||
handlerSpy = jest.spyOn(handlers, "working");
|
||||
};
|
||||
|
||||
const givenMetadataRepository = (data?: PollAlgorandMetadata) => {
|
||||
metadataRepo = {
|
||||
get: () => Promise.resolve(data),
|
||||
save: () => Promise.resolve(),
|
||||
};
|
||||
metadataSaveSpy = jest.spyOn(metadataRepo, "save");
|
||||
};
|
||||
|
||||
const givenStatsRepository = () => {
|
||||
statsRepo = {
|
||||
count: () => {},
|
||||
measure: () => {},
|
||||
report: () => Promise.resolve(""),
|
||||
};
|
||||
};
|
||||
|
||||
const givenPollAlgorandTxs = (from?: bigint) => {
|
||||
cfg.setFromBlock(from);
|
||||
pollAlgorand = new PollAlgorand(algorandRepo, metadataRepo, statsRepo, cfg);
|
||||
};
|
||||
|
||||
const whenpollAlgorandLogsStarts = async () => {
|
||||
pollAlgorand.run([handlers.working]);
|
||||
};
|
|
@ -0,0 +1,51 @@
|
|||
import { algorandLogMessagePublishedMapper } from "../../../../src/infrastructure/mappers/algorand/algorandLogMessagePublishedMapper";
|
||||
import { describe, it, expect } from "@jest/globals";
|
||||
|
||||
describe("algorandLogMessagePublishedMapper", () => {
|
||||
// 8/67e93fa6c8ac5c819990aa7340c0c16b508abb1178be9b30d024b8ac25193d45/10578
|
||||
it("should be able to map log to algorandLogMessagePublishedMapper", async () => {
|
||||
// When
|
||||
const result = algorandLogMessagePublishedMapper(tx, filters);
|
||||
|
||||
if (result) {
|
||||
// Then
|
||||
expect(result.name).toBe("log-message-published");
|
||||
expect(result.chainId).toBe(8);
|
||||
expect(result.txHash).toBe("WNXBBFRO2ZAWHPAC5RQOU2U3K7ZV5LWIY6LIAVYSRO2QGUDJOE6A");
|
||||
expect(result.address).toBe("BM26KC3NHYQ7BCDWVMP2OM6AWEZZ6ZGYQWKAQFC7XECOUBLP44VOYNBQTA");
|
||||
expect(result.attributes.consistencyLevel).toBe(0);
|
||||
expect(result.attributes.nonce).toBe(0);
|
||||
expect(result.attributes.payload).toBe("AAAAADwK+tc=");
|
||||
expect(result.attributes.sender).toBe(
|
||||
"67e93fa6c8ac5c819990aa7340c0c16b508abb1178be9b30d024b8ac25193d45"
|
||||
);
|
||||
expect(result.attributes.sequence).toBe(10578);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const tx = {
|
||||
payload: "AAAAADwK+tc=",
|
||||
method: "c2VuZFRyYW5zZmVy",
|
||||
applicationId: "842126029",
|
||||
blockNumber: 40095152,
|
||||
timestamp: 1719339547,
|
||||
innerTxs: [
|
||||
{
|
||||
applicationId: "842125965",
|
||||
payload:
|
||||
"AwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF5FHqAAAAAAAAAAAAAAAAuX7574c0xxkE2AAvi2vGbdnEim4ABgAAAAAAAAAAAAAAAFNnBmw01IdFiBHvtQanoq/a246LAAYLNeULbT4h8Ih2qx+nM8CxM59k2IWUCBRfuQTqBW/nKmNjdHBXaXRoZHJhdwAAAAAAAAAAAAAAAB2P8AKWlWUx/XZJgCHETuO3hZY1ABcAAAAASvBCaw==",
|
||||
method: "cHVibGlzaE1lc3NhZ2U=",
|
||||
sender: "M7UT7JWIVROIDGMQVJZUBQGBNNIIVOYRPC7JWMGQES4KYJIZHVCRZEGFRQ",
|
||||
logs: ["AAAAAAAAKVI="],
|
||||
},
|
||||
],
|
||||
sender: "BM26KC3NHYQ7BCDWVMP2OM6AWEZZ6ZGYQWKAQFC7XECOUBLP44VOYNBQTA",
|
||||
hash: "WNXBBFRO2ZAWHPAC5RQOU2U3K7ZV5LWIY6LIAVYSRO2QGUDJOE6A",
|
||||
};
|
||||
const filters = [
|
||||
{
|
||||
applicationIds: "842125965",
|
||||
applicationAddress: "J476J725L4JTOI2YU6DAI4E23LYUECLZR7RCYZ3LK6QFHX4M54ZI53SGXQ",
|
||||
},
|
||||
];
|
|
@ -0,0 +1,50 @@
|
|||
import { algorandRedeemedTransactionFoundMapper } from "../../../../src/infrastructure/mappers/algorand/algorandRedeemedTransactionFoundMapper";
|
||||
import { describe, it, expect } from "@jest/globals";
|
||||
|
||||
describe("algorandRedeemedTransactionFoundMapper", () => {
|
||||
// 6/0000000000000000000000000e082f06ff657d94310cb8ce8b0d9a04541d8052/220162
|
||||
it("should be able to map log to algorandRedeemedTransactionFoundMapper", async () => {
|
||||
// When
|
||||
const result = algorandRedeemedTransactionFoundMapper(tx, filters);
|
||||
|
||||
if (result) {
|
||||
// Then
|
||||
expect(result.name).toBe("transfer-redeemed");
|
||||
expect(result.chainId).toBe(8);
|
||||
expect(result.txHash).toBe("SERG7537SOJADJO5LC2J5SC6DD2VONL76B64YB5PDID2T3FONK5Q");
|
||||
expect(result.address).toBe("M7UT7JWIVROIDGMQVJZUBQGBNNIIVOYRPC7JWMGQES4KYJIZHVCRZEGFRQ");
|
||||
expect(result.attributes.from).toBe(
|
||||
"C3EXCPEEMYTIJ2EYUMEMLBDHIJ7J2KAHGKFHWD4GQX5MP7PYZO7O2C6YZE"
|
||||
);
|
||||
expect(result.attributes.emitterChain).toBe(6);
|
||||
expect(result.attributes.emitterAddress).toBe(
|
||||
"0000000000000000000000000E082F06FF657D94310CB8CE8B0D9A04541D8052"
|
||||
);
|
||||
expect(result.attributes.sequence).toBe(220162);
|
||||
expect(result.attributes.status).toBe("completed");
|
||||
expect(result.attributes.protocol).toBe("Token Bridge");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const tx = {
|
||||
payload:
|
||||
"AQAAAAQNAMvOqeysTxjYZtgYEOHVj77EPk0eVW0t1/xm5erC78GEJKVo/TJWgLpH+rPrv39ujSllqUNBG+4LCj1vVEO6HUYAASWlSS/cAIzz6mm+ZqpputkD25jFwEKycUGqjj39nP8yQzvR4PEPC75tIIgDLoj0HSzybbH1N7hFykOVE31Tg1cBAliHbjlP/OhEnOspo9WryTrmRmlQNkVGZ+5TRHkwy5WtVVBj5W0BE0M/hnkVAlny37QN99DRTl6cIs0IM4Bpw+QBA3gXwF6aqPF8aVp3vieQNweFLukFFqGXSdxV6KYOmfojc8/P7wLrD+4P9I2Y1XNfi9IoyihI+anU9IFDPpUkmuQABMJXlTMcms2Yit3JBLVjbiImzbtzPIMCRMLXG082cJVtevhCFtxR94pkOF75THhZy3vZ7v2oCQJXp6fssKGP2jgABihy7P7j+ovzB0/i+emkEXZMAoJcrviPbx11A+hAS36ubl+pk1sO1K4d7FM4HjP/f0WosNoCBXqfyD3jCrlZO/kACBZCCH+WTb6bYLPJC+nzzjvHA73QDH4lDkA4KjiJq3Z8MXRqRn6vFR7vjszj9NJCRw9xD6xscpMW0sNXcMFDDqsACYzV/6FXG/XZ7Kd4FYfoQ2ZHLSeRxRF4Xrt5YvsyhxOTHdj0S2b7eb7B41CxDUKzhYvWGf3V4WhYfvRgMFou944BDC8szu1vORgXN/EWBAJwBRmjOZXpHNH7GRIFupubGSQPIDtuom2/DHo2F77vxKC7HXVMVHbV7C3oOgB8w2lXfSABD9wQtyp+4hZAHFNOclrkTNgO/BRFaxXbTwKzKMv1ax3+O21cf3eSlYfdlwTz2DJrts/M/72v18EZriEFGPnFtHUAEOa6k9hnDJZys1ZJ4cSsd77DIFSRTc6GxbaWBTYXfMOjTMSArzdUl5XQKFTRIrDMHRY+sM4dz5dAWc7IM1rYcIoAES8aaahKohCHoR/d//Z0ZT1P4+LcgrF4rABIxki6mDbSaz+86v+derrWYHErb8+s5OpH5qwGbrftiXsDlebiyN8AEg6Rp7M4ooj+qsdqgzt4DUmLgUU2eqBVF1nWuEt0lJgNWAQ2ZPgqp2R+NcsxXEys/lIpSaYgxT3pGLbFWRdpLWgAZnqa+gADj5AABgAAAAAAAAAAAAAAAA4ILwb/ZX2UMQy4zosNmgRUHYBSAAAAAAADXAIBAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACw7dfAAAAAAAAAAAAAAAAuX7574c0xxkE2AAvi2vGbdnEim4ABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABK8EJrAAgAAAAAAAAAAAAAAABTZwZsNNSHRYgR77UGp6Kv2tuOi3dvcm1ob2xlRGVwb3NpdAAAAAAAAAAAAAAAAAurcc/Wfp8DdXO0rccmziZuKxssAAAAAAAAAAAAAAAASvBCawAXAAAAAAAAAAAAAAAAC6txz9Z+nwN1c7StxybOJm4rGyw=",
|
||||
applicationId: "842126029",
|
||||
blockNumber: 40085294,
|
||||
timestamp: 1719311110,
|
||||
method: "Y29tcGxldGVUcmFuc2Zlcg==",
|
||||
innerTxs: [
|
||||
{
|
||||
sender: "PG56DVKH6F3RXJASLIR4AXIXDYTFK2DQWIEOLDY22HEX5IPRE47HNFINKY",
|
||||
},
|
||||
],
|
||||
sender: "C3EXCPEEMYTIJ2EYUMEMLBDHIJ7J2KAHGKFHWD4GQX5MP7PYZO7O2C6YZE",
|
||||
hash: "SERG7537SOJADJO5LC2J5SC6DD2VONL76B64YB5PDID2T3FONK5Q",
|
||||
};
|
||||
const filters = [
|
||||
{
|
||||
applicationIds: "842126029",
|
||||
applicationAddress: "M7UT7JWIVROIDGMQVJZUBQGBNNIIVOYRPC7JWMGQES4KYJIZHVCRZEGFRQ",
|
||||
},
|
||||
];
|
|
@ -0,0 +1,235 @@
|
|||
import { mockRpcPool } from "../../mocks/mockRpcPool";
|
||||
mockRpcPool();
|
||||
|
||||
import { describe, it, expect, afterEach, afterAll } from "@jest/globals";
|
||||
import { AlgorandJsonRPCBlockRepository } from "../../../src/infrastructure/repositories";
|
||||
import { InstrumentedHttpProvider } from "../../../src/infrastructure/rpc/http/InstrumentedHttpProvider";
|
||||
import nock from "nock";
|
||||
|
||||
let repo: AlgorandJsonRPCBlockRepository;
|
||||
const rpc = "http://localhost";
|
||||
|
||||
describe("AlgorandJsonRPCBlockRepository", () => {
|
||||
afterAll(() => {
|
||||
nock.restore();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
nock.cleanAll();
|
||||
});
|
||||
|
||||
it("should be able to get block height", async () => {
|
||||
const expectedHeight = 40087333n;
|
||||
givenARepo();
|
||||
givenBlockHeightIs();
|
||||
|
||||
const result = await repo.getBlockHeight();
|
||||
|
||||
expect(result).toBe(expectedHeight);
|
||||
});
|
||||
|
||||
it("should be able to get the transactions", async () => {
|
||||
givenARepo();
|
||||
givenTransactions();
|
||||
const applicationId = "842125965";
|
||||
const result = await repo.getTransactions(applicationId, 40085294n, 40085299n);
|
||||
|
||||
expect(result).toBeTruthy();
|
||||
expect(result[0].applicationId).toBe(842125965);
|
||||
expect(result[0].blockNumber).toBe(40085294);
|
||||
expect(result[0].hash).toBe("Y2PTYVGAJYDALKNN4KVIJ4HBJNY5ZZO3BMYT7AR3KBBYQVXHP3JA");
|
||||
expect(result[0].payload).toBe(
|
||||
"AMvOqeysTxjYZtgYEOHVj77EPk0eVW0t1/xm5erC78GEJKVo/TJWgLpH+rPrv39ujSllqUNBG+4LCj1vVEO6HUYAASWlSS/cAIzz6mm+ZqpputkD25jFwEKycUGqjj39nP8yQzvR4PEPC75tIIgDLoj0HSzybbH1N7hFykOVE31Tg1cBAliHbjlP/OhEnOspo9WryTrmRmlQNkVGZ+5TRHkwy5WtVVBj5W0BE0M/hnkVAlny37QN99DRTl6cIs0IM4Bpw+QBA3gXwF6aqPF8aVp3vieQNweFLukFFqGXSdxV6KYOmfojc8/P7wLrD+4P9I2Y1XNfi9IoyihI+anU9IFDPpUkmuQABMJXlTMcms2Yit3JBLVjbiImzbtzPIMCRMLXG082cJVtevhCFtxR94pkOF75THhZy3vZ7v2oCQJXp6fssKGP2jgABihy7P7j+ovzB0/i+emkEXZMAoJcrviPbx11A+hAS36ubl+pk1sO1K4d7FM4HjP/f0WosNoCBXqfyD3jCrlZO/kA"
|
||||
);
|
||||
expect(result[0].sender).toBe("EZATROXX2HISIRZDRGXW4LRQ46Z6IUJYYIHU3PJGP7P5IQDPKVX42N767A");
|
||||
expect(result[0].timestamp).toBe(1719311110);
|
||||
});
|
||||
});
|
||||
|
||||
const givenARepo = () => {
|
||||
repo = new AlgorandJsonRPCBlockRepository(
|
||||
{ get: () => new InstrumentedHttpProvider({ url: rpc, chain: "algorand" }) } as any,
|
||||
{ get: () => new InstrumentedHttpProvider({ url: rpc, chain: "algorand" }) } as any
|
||||
);
|
||||
};
|
||||
|
||||
const givenBlockHeightIs = () => {
|
||||
nock(rpc).post("/v2/status").reply(200, { "last-round": 40087333 });
|
||||
};
|
||||
|
||||
const givenTransactions = () => {
|
||||
nock(rpc)
|
||||
.post("/v2/transactions?application-id=842125965&min-round=40085294&max-round=40085299")
|
||||
.reply(200, {
|
||||
"current-round": 40087557,
|
||||
"next-token": "LqdjAgAAAAA6AAAA",
|
||||
transactions: [
|
||||
{
|
||||
"application-transaction": {
|
||||
accounts: [
|
||||
"22DBCQI25XZ52JB5QPBQ72BCMHIYGCEJ3ODIRVOD5MRQTIV6IQUHAT7T5A",
|
||||
"XJC32PG73M4VIWAAZQZX6LRHPDAX2DMGVGQJJZSWJUZ5LECEVFMGJENX2M",
|
||||
],
|
||||
"application-args": [
|
||||
"dmVyaWZ5U2lncw==",
|
||||
"AMvOqeysTxjYZtgYEOHVj77EPk0eVW0t1/xm5erC78GEJKVo/TJWgLpH+rPrv39ujSllqUNBG+4LCj1vVEO6HUYAASWlSS/cAIzz6mm+ZqpputkD25jFwEKycUGqjj39nP8yQzvR4PEPC75tIIgDLoj0HSzybbH1N7hFykOVE31Tg1cBAliHbjlP/OhEnOspo9WryTrmRmlQNkVGZ+5TRHkwy5WtVVBj5W0BE0M/hnkVAlny37QN99DRTl6cIs0IM4Bpw+QBA3gXwF6aqPF8aVp3vieQNweFLukFFqGXSdxV6KYOmfojc8/P7wLrD+4P9I2Y1XNfi9IoyihI+anU9IFDPpUkmuQABMJXlTMcms2Yit3JBLVjbiImzbtzPIMCRMLXG082cJVtevhCFtxR94pkOF75THhZy3vZ7v2oCQJXp6fssKGP2jgABihy7P7j+ovzB0/i+emkEXZMAoJcrviPbx11A+hAS36ubl+pk1sO1K4d7FM4HjP/f0WosNoCBXqfyD3jCrlZO/kA",
|
||||
"WJO1p2w/c5ZFZIiFvczAbNcKPNP/bLlSWJvehiwl70OSEy+51KQhVxFN6EYBk73zovz4H4agl2X0di/REHoAhrMtegl3kmogUTHYcx05y+uMgrL9gvrtJxHVmvDySZ0W5yb2slTOW000j7dLlY6JZuLsPb1JWKfN",
|
||||
"ya4vUYDhuV1IzK9Kb9jilGSjYp5N0/v88OdQsvQmkWw=",
|
||||
],
|
||||
"application-id": 842125965,
|
||||
"foreign-apps": [],
|
||||
"foreign-assets": [],
|
||||
"global-state-schema": { "num-byte-slice": 0, "num-uint": 0 },
|
||||
"local-state-schema": { "num-byte-slice": 0, "num-uint": 0 },
|
||||
"on-completion": "noop",
|
||||
},
|
||||
"close-rewards": 0,
|
||||
"closing-amount": 0,
|
||||
"confirmed-round": 40085294,
|
||||
fee: 0,
|
||||
"first-valid": 40085291,
|
||||
"genesis-hash": "wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8=",
|
||||
"genesis-id": "mainnet-v1.0",
|
||||
group: "G2RHZhcMt6mtjYBl4tE6CP0Lfw+KNLf3Lj8ciEKby3U=",
|
||||
id: "Y2PTYVGAJYDALKNN4KVIJ4HBJNY5ZZO3BMYT7AR3KBBYQVXHP3JA",
|
||||
"intra-round-offset": 55,
|
||||
"last-valid": 40086291,
|
||||
"receiver-rewards": 0,
|
||||
"round-time": 1719311110,
|
||||
sender: "EZATROXX2HISIRZDRGXW4LRQ46Z6IUJYYIHU3PJGP7P5IQDPKVX42N767A",
|
||||
"sender-rewards": 0,
|
||||
signature: {
|
||||
logicsig: {
|
||||
args: [],
|
||||
logic:
|
||||
"BiAEAQAgFCYBADEgMgMSRDEBIxJEMRCBBhJENhoBNhoDNhoCiAADRCJDNQI1ATUAKDXwKDXxNAAVNQUjNQMjNQQ0AzQFDEEARDQBNAA0A4FBCCJYFzQANAMiCCRYNAA0A4EhCCRYBwA18TXwNAI0BCVYNPA08VACVwwUEkQ0A4FCCDUDNAQlCDUEQv+0Iok=",
|
||||
},
|
||||
},
|
||||
"tx-type": "appl",
|
||||
},
|
||||
{
|
||||
"application-transaction": {
|
||||
accounts: [
|
||||
"22DBCQI25XZ52JB5QPBQ72BCMHIYGCEJ3ODIRVOD5MRQTIV6IQUHAT7T5A",
|
||||
"XJC32PG73M4VIWAAZQZX6LRHPDAX2DMGVGQJJZSWJUZ5LECEVFMGJENX2M",
|
||||
],
|
||||
"application-args": [
|
||||
"dmVyaWZ5U2lncw==",
|
||||
"CBZCCH+WTb6bYLPJC+nzzjvHA73QDH4lDkA4KjiJq3Z8MXRqRn6vFR7vjszj9NJCRw9xD6xscpMW0sNXcMFDDqsACYzV/6FXG/XZ7Kd4FYfoQ2ZHLSeRxRF4Xrt5YvsyhxOTHdj0S2b7eb7B41CxDUKzhYvWGf3V4WhYfvRgMFou944BDC8szu1vORgXN/EWBAJwBRmjOZXpHNH7GRIFupubGSQPIDtuom2/DHo2F77vxKC7HXVMVHbV7C3oOgB8w2lXfSABD9wQtyp+4hZAHFNOclrkTNgO/BRFaxXbTwKzKMv1ax3+O21cf3eSlYfdlwTz2DJrts/M/72v18EZriEFGPnFtHUAEOa6k9hnDJZys1ZJ4cSsd77DIFSRTc6GxbaWBTYXfMOjTMSArzdUl5XQKFTRIrDMHRY+sM4dz5dAWc7IM1rYcIoAES8aaahKohCHoR/d//Z0ZT1P4+LcgrF4rABIxki6mDbSaz+86v+derrWYHErb8+s5OpH5qwGbrftiXsDlebiyN8A",
|
||||
"dKO/kTlT1pUmDYi8GqJaTu42PvAACsAHZyezX76i2sKP7lzLD+p2jtLMN6TcA2qNIytI9izdRzFBL0iQgZK25zh8zXaCd8F9qxt6UCfAs88XjiGtLneuBnEVSc+7H5x6nYCW6F4Uh/NVFdAqknU1BKjXVHG59J7b",
|
||||
"ya4vUYDhuV1IzK9Kb9jilGSjYp5N0/v88OdQsvQmkWw=",
|
||||
],
|
||||
"application-id": 842125965,
|
||||
"foreign-apps": [],
|
||||
"foreign-assets": [],
|
||||
"global-state-schema": { "num-byte-slice": 0, "num-uint": 0 },
|
||||
"local-state-schema": { "num-byte-slice": 0, "num-uint": 0 },
|
||||
"on-completion": "noop",
|
||||
},
|
||||
"close-rewards": 0,
|
||||
"closing-amount": 0,
|
||||
"confirmed-round": 40085294,
|
||||
fee: 0,
|
||||
"first-valid": 40085291,
|
||||
"genesis-hash": "wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8=",
|
||||
"genesis-id": "mainnet-v1.0",
|
||||
group: "G2RHZhcMt6mtjYBl4tE6CP0Lfw+KNLf3Lj8ciEKby3U=",
|
||||
id: "KXFFIB7C7Z7SYMAFN4BVJEYMUT6DGWFNGYJKK2V2PGMUDJ6I3XHA",
|
||||
"intra-round-offset": 56,
|
||||
"last-valid": 40086291,
|
||||
"receiver-rewards": 0,
|
||||
"round-time": 1719311110,
|
||||
sender: "EZATROXX2HISIRZDRGXW4LRQ46Z6IUJYYIHU3PJGP7P5IQDPKVX42N767A",
|
||||
"sender-rewards": 0,
|
||||
signature: {
|
||||
logicsig: {
|
||||
args: [],
|
||||
logic:
|
||||
"BiAEAQAgFCYBADEgMgMSRDEBIxJEMRCBBhJENhoBNhoDNhoCiAADRCJDNQI1ATUAKDXwKDXxNAAVNQUjNQMjNQQ0AzQFDEEARDQBNAA0A4FBCCJYFzQANAMiCCRYNAA0A4EhCCRYBwA18TXwNAI0BCVYNPA08VACVwwUEkQ0A4FCCDUDNAQlCDUEQv+0Iok=",
|
||||
},
|
||||
},
|
||||
"tx-type": "appl",
|
||||
},
|
||||
{
|
||||
"application-transaction": {
|
||||
accounts: [
|
||||
"22DBCQI25XZ52JB5QPBQ72BCMHIYGCEJ3ODIRVOD5MRQTIV6IQUHAT7T5A",
|
||||
"XJC32PG73M4VIWAAZQZX6LRHPDAX2DMGVGQJJZSWJUZ5LECEVFMGJENX2M",
|
||||
],
|
||||
"application-args": [
|
||||
"dmVyaWZ5U2lncw==",
|
||||
"Eg6Rp7M4ooj+qsdqgzt4DUmLgUU2eqBVF1nWuEt0lJgNWAQ2ZPgqp2R+NcsxXEys/lIpSaYgxT3pGLbFWRdpLWgA",
|
||||
"b768iY9APkdz6V/rFegMmpnINI0=",
|
||||
"ya4vUYDhuV1IzK9Kb9jilGSjYp5N0/v88OdQsvQmkWw=",
|
||||
],
|
||||
"application-id": 842125965,
|
||||
"foreign-apps": [],
|
||||
"foreign-assets": [],
|
||||
"global-state-schema": { "num-byte-slice": 0, "num-uint": 0 },
|
||||
"local-state-schema": { "num-byte-slice": 0, "num-uint": 0 },
|
||||
"on-completion": "noop",
|
||||
},
|
||||
"close-rewards": 0,
|
||||
"closing-amount": 0,
|
||||
"confirmed-round": 40085294,
|
||||
fee: 0,
|
||||
"first-valid": 40085291,
|
||||
"genesis-hash": "wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8=",
|
||||
"genesis-id": "mainnet-v1.0",
|
||||
group: "G2RHZhcMt6mtjYBl4tE6CP0Lfw+KNLf3Lj8ciEKby3U=",
|
||||
id: "WKV3GMESISL2DV3CQJZQ365CIZDPRQ4YJ2Q2J775YTYVTFPMWQQQ",
|
||||
"intra-round-offset": 57,
|
||||
"last-valid": 40086291,
|
||||
"receiver-rewards": 0,
|
||||
"round-time": 1719311110,
|
||||
sender: "EZATROXX2HISIRZDRGXW4LRQ46Z6IUJYYIHU3PJGP7P5IQDPKVX42N767A",
|
||||
"sender-rewards": 0,
|
||||
signature: {
|
||||
logicsig: {
|
||||
args: [],
|
||||
logic:
|
||||
"BiAEAQAgFCYBADEgMgMSRDEBIxJEMRCBBhJENhoBNhoDNhoCiAADRCJDNQI1ATUAKDXwKDXxNAAVNQUjNQMjNQQ0AzQFDEEARDQBNAA0A4FBCCJYFzQANAMiCCRYNAA0A4EhCCRYBwA18TXwNAI0BCVYNPA08VACVwwUEkQ0A4FCCDUDNAQlCDUEQv+0Iok=",
|
||||
},
|
||||
},
|
||||
"tx-type": "appl",
|
||||
},
|
||||
{
|
||||
"application-transaction": {
|
||||
accounts: [
|
||||
"22DBCQI25XZ52JB5QPBQ72BCMHIYGCEJ3ODIRVOD5MRQTIV6IQUHAT7T5A",
|
||||
"XJC32PG73M4VIWAAZQZX6LRHPDAX2DMGVGQJJZSWJUZ5LECEVFMGJENX2M",
|
||||
],
|
||||
"application-args": [
|
||||
"dmVyaWZ5VkFB",
|
||||
"AQAAAAQNAMvOqeysTxjYZtgYEOHVj77EPk0eVW0t1/xm5erC78GEJKVo/TJWgLpH+rPrv39ujSllqUNBG+4LCj1vVEO6HUYAASWlSS/cAIzz6mm+ZqpputkD25jFwEKycUGqjj39nP8yQzvR4PEPC75tIIgDLoj0HSzybbH1N7hFykOVE31Tg1cBAliHbjlP/OhEnOspo9WryTrmRmlQNkVGZ+5TRHkwy5WtVVBj5W0BE0M/hnkVAlny37QN99DRTl6cIs0IM4Bpw+QBA3gXwF6aqPF8aVp3vieQNweFLukFFqGXSdxV6KYOmfojc8/P7wLrD+4P9I2Y1XNfi9IoyihI+anU9IFDPpUkmuQABMJXlTMcms2Yit3JBLVjbiImzbtzPIMCRMLXG082cJVtevhCFtxR94pkOF75THhZy3vZ7v2oCQJXp6fssKGP2jgABihy7P7j+ovzB0/i+emkEXZMAoJcrviPbx11A+hAS36ubl+pk1sO1K4d7FM4HjP/f0WosNoCBXqfyD3jCrlZO/kACBZCCH+WTb6bYLPJC+nzzjvHA73QDH4lDkA4KjiJq3Z8MXRqRn6vFR7vjszj9NJCRw9xD6xscpMW0sNXcMFDDqsACYzV/6FXG/XZ7Kd4FYfoQ2ZHLSeRxRF4Xrt5YvsyhxOTHdj0S2b7eb7B41CxDUKzhYvWGf3V4WhYfvRgMFou944BDC8szu1vORgXN/EWBAJwBRmjOZXpHNH7GRIFupubGSQPIDtuom2/DHo2F77vxKC7HXVMVHbV7C3oOgB8w2lXfSABD9wQtyp+4hZAHFNOclrkTNgO/BRFaxXbTwKzKMv1ax3+O21cf3eSlYfdlwTz2DJrts/M/72v18EZriEFGPnFtHUAEOa6k9hnDJZys1ZJ4cSsd77DIFSRTc6GxbaWBTYXfMOjTMSArzdUl5XQKFTRIrDMHRY+sM4dz5dAWc7IM1rYcIoAES8aaahKohCHoR/d//Z0ZT1P4+LcgrF4rABIxki6mDbSaz+86v+derrWYHErb8+s5OpH5qwGbrftiXsDlebiyN8AEg6Rp7M4ooj+qsdqgzt4DUmLgUU2eqBVF1nWuEt0lJgNWAQ2ZPgqp2R+NcsxXEys/lIpSaYgxT3pGLbFWRdpLWgAZnqa+gADj5AABgAAAAAAAAAAAAAAAA4ILwb/ZX2UMQy4zosNmgRUHYBSAAAAAAADXAIBAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACw7dfAAAAAAAAAAAAAAAAuX7574c0xxkE2AAvi2vGbdnEim4ABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABK8EJrAAgAAAAAAAAAAAAAAABTZwZsNNSHRYgR77UGp6Kv2tuOi3dvcm1ob2xlRGVwb3NpdAAAAAAAAAAAAAAAAAurcc/Wfp8DdXO0rccmziZuKxssAAAAAAAAAAAAAAAASvBCawAXAAAAAAAAAAAAAAAAC6txz9Z+nwN1c7StxybOJm4rGyw=",
|
||||
],
|
||||
"application-id": 842125965,
|
||||
"foreign-apps": [],
|
||||
"foreign-assets": [],
|
||||
"global-state-schema": { "num-byte-slice": 0, "num-uint": 0 },
|
||||
"local-state-schema": { "num-byte-slice": 0, "num-uint": 0 },
|
||||
"on-completion": "noop",
|
||||
},
|
||||
"close-rewards": 0,
|
||||
"closing-amount": 0,
|
||||
"confirmed-round": 40085294,
|
||||
fee: 4000,
|
||||
"first-valid": 40085291,
|
||||
"genesis-hash": "wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8=",
|
||||
"genesis-id": "mainnet-v1.0",
|
||||
group: "G2RHZhcMt6mtjYBl4tE6CP0Lfw+KNLf3Lj8ciEKby3U=",
|
||||
id: "3E7KILAIPB4HE4XF5TWUBDHIRXGECZZSLKV3ERPZF64OJXEPRBSA",
|
||||
"intra-round-offset": 58,
|
||||
"last-valid": 40086291,
|
||||
"receiver-rewards": 0,
|
||||
"round-time": 1719311110,
|
||||
sender: "C3EXCPEEMYTIJ2EYUMEMLBDHIJ7J2KAHGKFHWD4GQX5MP7PYZO7O2C6YZE",
|
||||
"sender-rewards": 0,
|
||||
signature: {
|
||||
sig: "hSaeNt/qY/+QVVDWc44yYcYlt0SejQMLPs/HJp73Io1KzW/0OvKLvWchVu+9YGZdaEc+6xwq8kHMLBrlohpIAA==",
|
||||
},
|
||||
"tx-type": "appl",
|
||||
},
|
||||
],
|
||||
});
|
||||
};
|
|
@ -2,9 +2,11 @@ import { mockRpcPool } from "../../mocks/mockRpcPool";
|
|||
mockRpcPool();
|
||||
|
||||
import { RateLimitedWormchainJsonRPCBlockRepository } from "../../../src/infrastructure/repositories/wormchain/RateLimitedWormchainJsonRPCBlockRepository";
|
||||
import { RateLimitedAlgorandJsonRPCBlockRepository } from "../../../src/infrastructure/repositories/algorand/RateLimitedAlgorandJsonRPCBlockRepository";
|
||||
import { RateLimitedAptosJsonRPCBlockRepository } from "../../../src/infrastructure/repositories/aptos/RateLimitedAptosJsonRPCBlockRepository";
|
||||
import { RateLimitedEvmJsonRPCBlockRepository } from "../../../src/infrastructure/repositories/evm/RateLimitedEvmJsonRPCBlockRepository";
|
||||
import { RateLimitedSuiJsonRPCBlockRepository } from "../../../src/infrastructure/repositories/sui/RateLimitedSuiJsonRPCBlockRepository";
|
||||
import { RateLimitedSeiJsonRPCBlockRepository } from "../../../src/infrastructure/repositories/sei/RateLimitedSeiJsonRPCBlockRepository";
|
||||
import { describe, expect, it } from "@jest/globals";
|
||||
import { RepositoriesBuilder } from "../../../src/infrastructure/repositories/RepositoriesBuilder";
|
||||
import { configMock } from "../../mocks/configMock";
|
||||
|
@ -14,7 +16,6 @@ import {
|
|||
PromStatRepository,
|
||||
SnsEventRepository,
|
||||
} from "../../../src/infrastructure/repositories";
|
||||
import { RateLimitedSeiJsonRPCBlockRepository } from "../../../src/infrastructure/repositories/sei/RateLimitedSeiJsonRPCBlockRepository";
|
||||
|
||||
describe("RepositoriesBuilder", () => {
|
||||
it("should be throw error because dose not have any chain", async () => {
|
||||
|
@ -114,6 +115,7 @@ describe("RepositoriesBuilder", () => {
|
|||
expect(repos.getEvmBlockRepository("xlayer")).toBeInstanceOf(
|
||||
RateLimitedEvmJsonRPCBlockRepository
|
||||
);
|
||||
expect(repos.getAlgorandRepository()).toBeInstanceOf(RateLimitedAlgorandJsonRPCBlockRepository);
|
||||
expect(repos.getAptosRepository()).toBeInstanceOf(RateLimitedAptosJsonRPCBlockRepository);
|
||||
expect(repos.getMetadataRepository()).toBeInstanceOf(FileMetadataRepository);
|
||||
expect(repos.getSnsEventRepository()).toBeInstanceOf(SnsEventRepository);
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
WormchainRepository,
|
||||
EvmBlockRepository,
|
||||
MetadataRepository,
|
||||
AlgorandRepository,
|
||||
AptosRepository,
|
||||
StatRepository,
|
||||
SeiRepository,
|
||||
|
@ -25,6 +26,7 @@ const suiRepo = {} as any as SuiRepository;
|
|||
const aptosRepo = {} as any as AptosRepository;
|
||||
const wormchainRepo = {} as any as WormchainRepository;
|
||||
const seiRepo = {} as any as SeiRepository;
|
||||
const algorandRepo = {} as any as AlgorandRepository;
|
||||
|
||||
let repo: StaticJobRepository;
|
||||
|
||||
|
@ -42,6 +44,7 @@ describe("StaticJobRepository", () => {
|
|||
aptosRepo,
|
||||
wormchainRepo,
|
||||
seiRepo,
|
||||
algorandRepo,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -38,6 +38,13 @@ export const configMock = (): Config => {
|
|||
rpcs: ["http://localhost"],
|
||||
timeout: 10000,
|
||||
},
|
||||
algorand: {
|
||||
name: "algorand",
|
||||
network: "testnet",
|
||||
chainId: 8,
|
||||
rpcs: [["http://localhost"], ["http://localhost"]] as any,
|
||||
timeout: 10000,
|
||||
},
|
||||
fantom: {
|
||||
name: "fantom",
|
||||
network: "testnet",
|
||||
|
@ -254,7 +261,7 @@ export const configMock = (): Config => {
|
|||
dir: "./metadata-repo/jobs",
|
||||
},
|
||||
chains: chainsRecord,
|
||||
enabledPlatforms: ["solana", "evm", "sui", "aptos", "wormchain", "sei"],
|
||||
enabledPlatforms: ["solana", "evm", "sui", "aptos", "wormchain", "sei", "algorand"],
|
||||
};
|
||||
|
||||
return cfg;
|
||||
|
|
|
@ -105,6 +105,37 @@ data:
|
|||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "poll-log-message-published-algorand",
|
||||
"chain": "algorand",
|
||||
"source": {
|
||||
"action": "PollAlgorand",
|
||||
"config": {
|
||||
"blockBatchSize": 100,
|
||||
"commitment": "latest",
|
||||
"interval": 25000,
|
||||
"applicationIds": ["86525623"],
|
||||
"chain": "algorand",
|
||||
"chainId": 8
|
||||
}
|
||||
},
|
||||
"handlers": [
|
||||
{
|
||||
"action": "HandleAlgorandTransactions",
|
||||
"target": "sns",
|
||||
"mapper": "algorandLogMessagePublishedMapper",
|
||||
"config": {
|
||||
"metricName": "process_source_event",
|
||||
"filter": [
|
||||
{
|
||||
"applicationIds": "86525623",
|
||||
"applicationAddress": "C2SZBD4ZFFDXANBCUTG5GBUEWMQ34JS5LFGDRTEVJBAXDRF6ZWB7Q4KHHM"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
mainnet-jobs.json: |-
|
||||
|
@ -183,6 +214,37 @@ data:
|
|||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "poll-log-message-published-algorand",
|
||||
"chain": "algorand",
|
||||
"source": {
|
||||
"action": "PollAlgorand",
|
||||
"config": {
|
||||
"blockBatchSize": 100,
|
||||
"commitment": "latest",
|
||||
"interval": 15000,
|
||||
"applicationIds": ["842125965"],
|
||||
"chain": "algorand",
|
||||
"chainId": 8
|
||||
}
|
||||
},
|
||||
"handlers": [
|
||||
{
|
||||
"action": "HandleAlgorandTransactions",
|
||||
"target": "sns",
|
||||
"mapper": "algorandLogMessagePublishedMapper",
|
||||
"config": {
|
||||
"metricName": "process_source_event",
|
||||
"filter": [
|
||||
{
|
||||
"applicationIds": "842125965",
|
||||
"applicationAddress": "J476J725L4JTOI2YU6DAI4E23LYUECLZR7RCYZ3LK6QFHX4M54ZI53SGXQ"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
---
|
||||
|
@ -231,6 +293,10 @@ spec:
|
|||
- name: APTOS_RPCS
|
||||
value: '{{ .APTOS_RPCS }}'
|
||||
{{ end }}
|
||||
{{ if .ALGORAND_RPCS }}
|
||||
- name: ALGORAND_RPCS
|
||||
value: '{{ .ALGORAND_RPCS }}'
|
||||
{{ end }}
|
||||
image: {{ .IMAGE_NAME }}
|
||||
resources:
|
||||
limits:
|
||||
|
|
|
@ -166,6 +166,37 @@ data:
|
|||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "poll-redeemed-transactions-algorand",
|
||||
"chain": "algorand",
|
||||
"source": {
|
||||
"action": "PollAlgorand",
|
||||
"config": {
|
||||
"blockBatchSize": 100,
|
||||
"commitment": "latest",
|
||||
"interval": 25000,
|
||||
"applicationIds": ["86525641"],
|
||||
"chain": "algorand",
|
||||
"chainId": 8
|
||||
}
|
||||
},
|
||||
"handlers": [
|
||||
{
|
||||
"action": "HandleAlgorandTransactions",
|
||||
"target": "sns",
|
||||
"mapper": "algorandRedeemedTransactionFoundMapper",
|
||||
"config": {
|
||||
"filter": [
|
||||
{
|
||||
"applicationIds": "86525641",
|
||||
"applicationAddress": "MJA77XADFNUTX64FISCY6BAD33EG6LQXECXZ6NHY2ZP6K5FWEOGH6D62HA"
|
||||
}
|
||||
],
|
||||
"metricName": "process_vaa_event"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
mainnet-jobs.json: |-
|
||||
|
@ -333,6 +364,37 @@ data:
|
|||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "poll-redeemed-transactions-algorand",
|
||||
"chain": "algorand",
|
||||
"source": {
|
||||
"action": "PollAlgorand",
|
||||
"config": {
|
||||
"blockBatchSize": 100,
|
||||
"commitment": "latest",
|
||||
"interval": 15000,
|
||||
"applicationIds": ["842126029"],
|
||||
"chain": "algorand",
|
||||
"chainId": 8
|
||||
}
|
||||
},
|
||||
"handlers": [
|
||||
{
|
||||
"action": "HandleAlgorandTransactions",
|
||||
"target": "sns",
|
||||
"mapper": "algorandRedeemedTransactionFoundMapper",
|
||||
"config": {
|
||||
"filter": [
|
||||
{
|
||||
"applicationIds": "842126029",
|
||||
"applicationAddress": "M7UT7JWIVROIDGMQVJZUBQGBNNIIVOYRPC7JWMGQES4KYJIZHVCRZEGFRQ"
|
||||
}
|
||||
],
|
||||
"metricName": "process_vaa_event"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
---
|
||||
|
@ -381,6 +443,18 @@ spec:
|
|||
- name: SUI_RPCS
|
||||
value: '{{ .SUI_RPCS }}'
|
||||
{{ end }}
|
||||
{{ if .INJECTIVE_RPCS }}
|
||||
- name: INJECTIVE_RPCS
|
||||
value: '{{ .INJECTIVE_RPCS }}'
|
||||
{{ end }}
|
||||
{{ if .OSMOSIS_RPCS }}
|
||||
- name: OSMOSIS_RPCS
|
||||
value: '{{ .OSMOSIS_RPCS }}'
|
||||
{{ end }}
|
||||
{{ if .ALGORAND_RPCS }}
|
||||
- name: ALGORAND_RPCS
|
||||
value: '{{ .ALGORAND_RPCS }}'
|
||||
{{ end }}
|
||||
image: {{ .IMAGE_NAME }}
|
||||
resources:
|
||||
limits:
|
||||
|
|
Loading…
Reference in New Issue