import { useCallback, useRef } from 'react';
import type { SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate';
import { parseRawLog } from '@cosmjs/stargate/build/logs';
export type PendingTx = {
txHash: string;
proofHash: string;
proofNonce: number;
submittedAt: number;
};
const TX_TIMEOUT_MS = 60_000; // 60s โ ~10 blocks
/**
* Tracks pending proof TXs and verifies them asynchronously on each new block.
* When a TX confirms, extracts miner_reward from wasm events and calls onConfirmed.
* When a TX fails or times out, calls onFailed.
*/
export default function usePendingTxVerifier(
signingClient: SigningCosmWasmClient | undefined,
onConfirmed: (txHash: string, proofHash: string, reward: number) => void,
onFailed: (txHash: string, proofHash: string, error: string) => void,
) {
const pendingRef = useRef<Map<string, PendingTx>>(new Map());
const addPending = useCallback((tx: PendingTx) => {
pendingRef.current.set(tx.txHash, tx);
console.log('[Verifier] Added pending TX:', tx.txHash.slice(0, 16), 'Pending:', pendingRef.current.size);
}, []);
const checkAll = useCallback(async () => {
if (!signingClient || pendingRef.current.size === 0) return;
const now = Date.now();
const entries = Array.from(pendingRef.current.entries());
console.log('[Verifier] Checking', entries.length, 'pending TX(s)');
// Check all pending TXs concurrently
const results = await Promise.allSettled(
entries.map(async ([txHash, pending]) => {
try {
const tx = await signingClient.getTx(txHash);
if (tx) {
// TX found on chain
pendingRef.current.delete(txHash);
if (tx.code !== 0) {
onFailed(txHash, pending.proofHash, tx.rawLog || `code ${tx.code}`);
return;
}
// Extract miner_reward from rawLog (CyberClient returns events: [])
let reward = 0;
try {
const logs = parseRawLog(tx.rawLog);
for (const log of logs) {
const wasmEvent = log.events.find((e) => e.type === 'wasm');
if (wasmEvent) {
const rewardAttr = wasmEvent.attributes.find(
(a) => a.key === 'miner_reward'
);
if (rewardAttr?.value) {
reward = Number(rewardAttr.value) / 1_000_000;
break;
}
}
}
} catch {
// rawLog may not be valid JSON โ reward stays 0
}
console.log('[Verifier] Confirmed TX:', txHash.slice(0, 16), 'reward:', reward.toFixed(6));
onConfirmed(txHash, pending.proofHash, reward);
} else if (now - pending.submittedAt > TX_TIMEOUT_MS) {
// Not found and timed out
pendingRef.current.delete(txHash);
console.log('[Verifier] TX timed out:', txHash.slice(0, 16));
onFailed(txHash, pending.proofHash, `TX not confirmed within ${TX_TIMEOUT_MS / 1000}s โ may have been dropped`);
}
// else: not found yet but within timeout โ keep waiting
} catch (err) {
// getTx network error โ keep in map, will retry next block
console.warn('[Verifier] getTx error for', txHash.slice(0, 16), err);
}
})
);
// Suppress unused var lint โ results checked via Promise.allSettled pattern
void results;
if (pendingRef.current.size > 0) {
console.log('[Verifier] Still pending:', pendingRef.current.size);
}
}, [signingClient, onConfirmed, onFailed]);
const pendingCount = useCallback(() => pendingRef.current.size, []);
return { addPending, checkAll, pendingCount };
}