diff --git a/apps/scan/lib/search-validate.ts b/apps/scan/lib/search-validate.ts index ab8c621..f3c5dc1 100644 --- a/apps/scan/lib/search-validate.ts +++ b/apps/scan/lib/search-validate.ts @@ -54,6 +54,23 @@ async function probeTx(network: NetworkId, hash: `0x${string}`): Promise (the only block route is +// height-keyed). +async function probeBlockByHash( + network: NetworkId, + hash: `0x${string}`, +): Promise { + try { + const client = createClient(network); + const block = await client.getBlock({ blockHash: hash }); + return block?.number ?? null; + } catch { + return null; + } +} + export async function validateAndResolveSearch( network: NetworkId, query: string, @@ -77,25 +94,37 @@ export async function validateAndResolveSearch( return { kind: "not_found", reason: `Block #${q} not found on either network` }; } - // Transaction hash — accept both 0x-prefixed (wallet shape) and bare - // 64-hex (Sentrix internal shape). viem getTransaction needs the 0x - // form, so we prepend if missing before probing. The href routes - // through with whatever the user typed — fetchTransaction strips 0x - // again for the REST call internally. + // 64-hex — ambiguous between tx hash and block hash. Accept both + // 0x-prefixed (wallet shape) and bare (Sentrix internal shape). + // viem needs the 0x form for both getTransaction + getBlock, so we + // prepend if missing before probing. Run all four probes (tx + block + // on current + other network) in parallel — at chain-RPC latencies + // (~50ms each) the cost is dominated by the slowest, not the sum. if (/^(0x)?[a-fA-F0-9]{64}$/.test(q)) { const probeHash = (q.startsWith("0x") ? q : `0x${q}`) as `0x${string}`; - const [hereOk, otherOk] = await Promise.all([ + const other = OTHER[network]; + const [txHere, txOther, blockHere, blockOther] = await Promise.all([ probeTx(network, probeHash), - probeTx(OTHER[network], probeHash), + probeTx(other, probeHash), + probeBlockByHash(network, probeHash), + probeBlockByHash(other, probeHash), ]); - if (hereOk) return { kind: "tx", href: `/tx/${q}`, onNetwork: network }; - if (otherOk) + if (txHere) return { kind: "tx", href: `/tx/${q}`, onNetwork: network }; + if (blockHere !== null) + return { kind: "block", href: `/blocks/${blockHere}`, onNetwork: network }; + if (txOther) return { kind: "tx", - href: withNetwork(`/tx/${q}`, network, OTHER[network]), - onNetwork: OTHER[network], + href: withNetwork(`/tx/${q}`, network, other), + onNetwork: other, + }; + if (blockOther !== null) + return { + kind: "block", + href: withNetwork(`/blocks/${blockOther}`, network, other), + onNetwork: other, }; - return { kind: "not_found", reason: "Transaction not found on either network" }; + return { kind: "not_found", reason: "No transaction or block with that hash on either network" }; } // Address — viem's `isAddress` accepts 0x-prefixed only. Sentrix's diff --git a/scripts/audit-live.mjs b/scripts/audit-live.mjs index 3498258..ee381a5 100755 --- a/scripts/audit-live.mjs +++ b/scripts/audit-live.mjs @@ -24,11 +24,17 @@ const JSON_OUT = process.argv.includes("--json"); // fires without actually owning a real testnet tx). Those console 404s // are filtered out of the issue count. const SURFACE = [ - { app: "scan", url: "https://scan.sentrixchain.com" }, - { app: "scan-tx-mainnet", url: "https://scan.sentrixchain.com/tx/0xdeadbeef", expectNotFound: true }, - { app: "scan-tx-testnet", url: "https://scan.sentrixchain.com/tx/0xdeadbeef?network=testnet", expectNotFound: true }, - { app: "scan-block-mainnet", url: "https://scan.sentrixchain.com/blocks/1" }, - { app: "scan-leaderboard", url: "https://scan.sentrixchain.com/leaderboard" }, + { app: "scan-v1", url: "https://scan.sentrixchain.com" }, + { app: "scan-v1-tx-mainnet", url: "https://scan.sentrixchain.com/tx/0xdeadbeef", expectNotFound: true }, + { app: "scan-v1-tx-testnet", url: "https://scan.sentrixchain.com/tx/0xdeadbeef?network=testnet", expectNotFound: true }, + { app: "scan-v1-block", url: "https://scan.sentrixchain.com/blocks/1" }, + { app: "scan-v1-leaderboard", url: "https://scan.sentrixchain.com/leaderboard" }, + // V2 (Leptos) — separate sentriscloud.com hosts. Coexists with V1 per + // user direction; both monitored permanently. + { app: "scan-v2", url: "https://scan.sentriscloud.com" }, + { app: "scan-v2-block", url: "https://scan.sentriscloud.com/block/1" }, + { app: "scan-v2-testnet", url: "https://scan-testnet.sentriscloud.com" }, + { app: "scan-v2-testnet-blk", url: "https://scan-testnet.sentriscloud.com/block/1" }, { app: "solux", url: "https://solux.sentriscloud.com" }, { app: "sentriscloud", url: "https://sentriscloud.com" }, { app: "sentrixchain", url: "https://sentrixchain.com" },