Files
proxmox/multi-chain-execution/src/chain-adapters/base-adapter.ts
defiQUG fbda1b4beb
Some checks failed
Deploy to Phoenix / deploy (push) Has been cancelled
docs: Ledger Live integration, contract deploy learnings, NEXT_STEPS updates
- ADD_CHAIN138_TO_LEDGER_LIVE: Ledger form done; public code review repo bis-innovations/LedgerLive; init/push commands
- CONTRACT_DEPLOYMENT_RUNBOOK: Chain 138 gas price 1 gwei, 36-addr check, TransactionMirror workaround
- CONTRACT_*: AddressMapper, MirrorManager deployed 2026-02-12; 36-address on-chain check
- NEXT_STEPS_FOR_YOU: Ledger done; steps completable now (no LAN); run-completable-tasks-from-anywhere
- MASTER_INDEX, OPERATOR_OPTIONAL, SMART_CONTRACTS_INVENTORY_SIMPLE: updates
- LEDGER_BLOCKCHAIN_INTEGRATION_COMPLETE: bis-innovations/LedgerLive reference

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 15:46:57 -08:00

157 lines
4.6 KiB
TypeScript

/**
* Base chain adapter: RPC abstraction, receipt/log fetch, reorg detection, fallback RPC.
*/
import { JsonRpcProvider, TransactionReceipt, Log } from 'ethers';
import type { ChainAdapterConfig, NormalizedReceipt, NormalizedLog, IChainAdapter } from './types.js';
import { getChainConfig } from './config.js';
function toHex(n: bigint): string {
return '0x' + n.toString(16);
}
export abstract class BaseChainAdapter implements IChainAdapter {
protected provider: JsonRpcProvider;
protected config: ChainAdapterConfig;
private rpcIndex = 0;
constructor(chainId: number, rpcUrls?: string[]) {
const cfg = getChainConfig(chainId);
if (!cfg) throw new Error(`Unknown chainId: ${chainId}`);
this.config = rpcUrls?.length ? { ...cfg, rpcUrls } : cfg;
this.provider = new JsonRpcProvider(this.config.rpcUrls[0]);
}
getChainId(): number {
return this.config.chainId;
}
getConfig(): ChainAdapterConfig {
return this.config;
}
protected getRpcUrl(): string {
return this.config.rpcUrls[this.rpcIndex % this.config.rpcUrls.length];
}
protected async switchRpc(): Promise<void> {
if (this.config.rpcUrls.length <= 1) return;
this.rpcIndex++;
this.provider = new JsonRpcProvider(this.getRpcUrl());
}
async getBlockNumber(): Promise<number> {
const n = await this.provider.getBlockNumber();
return n;
}
async getBlock(blockNumber: number): Promise<{ number: number; hash: string; parentHash: string; timestamp: number } | null> {
try {
const block = await this.provider.getBlock(blockNumber);
if (!block) return null;
return {
number: block.number,
hash: block.hash ?? '',
parentHash: block.parentHash ?? '',
timestamp: block.timestamp,
};
} catch {
return null;
}
}
async sendTransaction(signedTxHex: string): Promise<{ hash: string; from: string; nonce: number }> {
const tx = await this.provider.broadcastTransaction(signedTxHex);
return {
hash: tx.hash,
from: tx.from ?? '',
nonce: tx.nonce,
};
}
async getTransactionReceipt(txHash: string): Promise<NormalizedReceipt | null> {
try {
const receipt = await this.provider.getTransactionReceipt(txHash);
if (!receipt) return null;
return this.normalizeReceipt(receipt);
} catch {
return null;
}
}
protected normalizeReceipt(receipt: TransactionReceipt): NormalizedReceipt {
return {
chainId: this.config.chainId,
transactionHash: receipt.hash,
blockNumber: BigInt(receipt.blockNumber),
blockHash: receipt.blockHash ?? '',
transactionIndex: receipt.index,
from: receipt.from,
to: receipt.to ?? null,
gasUsed: BigInt(receipt.gasUsed.toString()),
cumulativeGasUsed: BigInt(receipt.cumulativeGasUsed.toString()),
contractAddress: receipt.contractAddress ?? null,
logsBloom: receipt.logsBloom ?? '',
status: receipt.status === 1 ? 1 : 0,
root: receipt.root ?? null,
};
}
async getLogs(
fromBlock: number,
toBlock: number,
address?: string,
topics?: string[]
): Promise<NormalizedLog[]> {
const filter: { fromBlock: number; toBlock: number; address?: string; topics?: string[] } = {
fromBlock,
toBlock,
};
if (address) filter.address = address;
if (topics?.length) filter.topics = topics as `0x${string}`[];
const logs = await this.provider.getLogs(filter);
return logs.map((log) => this.normalizeLog(log));
}
protected normalizeLog(log: Log): NormalizedLog {
const topics = log.topics as string[];
return {
chainId: this.config.chainId,
transactionHash: log.transactionHash,
blockNumber: BigInt(log.blockNumber),
blockHash: log.blockHash ?? '',
logIndex: log.index,
address: log.address,
topic0: topics[0] ?? null,
topic1: topics[1] ?? null,
topic2: topics[2] ?? null,
topic3: topics[3] ?? null,
data: log.data,
};
}
async detectReorg(blockNumber: number, expectedBlockHash: string): Promise<boolean> {
const block = await this.getBlock(blockNumber);
if (!block) return true;
return block.hash.toLowerCase() !== expectedBlockHash.toLowerCase();
}
async healthCheck(): Promise<boolean> {
try {
await this.provider.getBlockNumber();
return true;
} catch {
if (this.config.rpcUrls.length > 1) {
await this.switchRpc();
try {
await this.provider.getBlockNumber();
return true;
} catch {
return false;
}
}
return false;
}
}
}