Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 41 additions & 12 deletions apps/scan/lib/search-validate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,23 @@ async function probeTx(network: NetworkId, hash: `0x${string}`): Promise<boolean
}
}

// 64-hex strings are ambiguous between tx hashes and block hashes. We
// probe both in parallel; if a block matches we resolve the height so
// the caller can route to /blocks/<height> (the only block route is
// height-keyed).
async function probeBlockByHash(
network: NetworkId,
hash: `0x${string}`,
): Promise<bigint | null> {
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,
Expand All @@ -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
Expand Down
16 changes: 11 additions & 5 deletions scripts/audit-live.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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" },
Expand Down
Loading