From 24755c441de2aac6e93912f7308f6d0a7f3dcd81 Mon Sep 17 00:00:00 2001 From: Himess Date: Wed, 28 Oct 2026 01:03:05 +0300 Subject: [PATCH] app, docs: every transaction, address and commit links to its explorer app: a ChainRef (components/ChainRef.tsx) shows a transaction, an account or contract, a unified address or a commit in monospace, linked where the build links explorers (lib/explorer.ts: Basescan, ZecBlock's address and transaction pages, GitHub; the judge testnet and a real recording, never the mainnet beta or the placeholder). It takes the place of the plain values: loan accounts and owners, the vault's address, the market maker and the swap's maker, the evidence's contracts, router, relay, oracle and mocks, the commit on the status and evidence pages, the privacy page's recent events (their transactions too), and the transactions of a close, a deleverage and the demo panel. Ids with no explorer (vaults, claims, commitments, image ids) stay as they were. README: "The live run on the explorers": every step of the recorded run on ZecBlock and Basescan (the vault's address included) and the sixteen contracts, all verified on Basescan, with Morpho Blue, RISC Zero's router and the EntryPoint; the judges' list and SUBMISSION point there. Co-Authored-By: Claude Opus 5.5 --- README.md | 49 ++++++++++++++++++- app/components/BorrowForm.tsx | 7 +-- app/components/ChainRef.tsx | 60 ++++++++++++++++++++++++ app/components/DemoPanel.tsx | 3 +- app/components/KeyBackup.tsx | 3 +- app/components/LoanCard.tsx | 3 +- app/components/LoansView.tsx | 3 +- app/components/WorldView.tsx | 11 +++-- app/components/borrow/BorrowWizard.tsx | 5 +- app/components/position/Deleverage.tsx | 5 +- app/components/position/Exits.tsx | 9 ++-- app/components/position/Ledger.tsx | 3 +- app/components/position/PositionView.tsx | 5 +- app/components/public/EvidenceView.tsx | 13 ++--- app/components/public/StatusView.tsx | 3 +- app/components/swap/BuyZec.tsx | 7 +-- app/lib/explorer.ts | 47 ++++++++++++++----- docs/SUBMISSION.md | 3 +- 18 files changed, 194 insertions(+), 45 deletions(-) create mode 100644 app/components/ChainRef.tsx diff --git a/README.md b/README.md index 9986e20..dcb9ec9 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,9 @@ the RISC Zero proofs and Morpho Blue are real, and they ran live together on Base Sepolia on 27 October 2026, every proof checked by RISC Zero's deployed router; the ZEC price, the loan token (MockUSDC) and the signers' independence are not ([the full list](#what-is-real-and-what-is-a-mock)). -5. **Read on:** [what ran, and how to reproduce it](docs/TESTING.md), [the judges' FAQ](docs/JURY_FAQ.md), +5. **On the explorers:** every transaction of the live run, on Zcash and on Base, and every contract, verified on + Basescan ([the list](#the-live-run-on-the-explorers)). +6. **Read on:** [what ran, and how to reproduce it](docs/TESTING.md), [the judges' FAQ](docs/JURY_FAQ.md), [what is still open](docs/MAINNET.md#7-known-limitations-and-their-designs). ## What it does @@ -133,7 +135,7 @@ last one, every proof of that run checked by RISC Zero's deployed router: | The app in a browser | Gasless: open, deposit, borrow with pre-liquidation terms, deleverage and close from collateral (Safe signatures), repay and release, a claim paid from the bonds | | Live on Base Sepolia (27 October) | The core flows on a public chain: a deposit minted on its Groth16 proof (made on a rented RTX 4090) through RISC Zero's router, a sponsored borrow from an account that never held ETH, a close from collateral paid in one 3-of-5 FROST Zcash transaction whose delivery was then proven on Base, and a swap settled on the proof of its Zcash payment ([docs/TESTING.md § 2c](docs/TESTING.md#2c-live-on-base-sepolia-2026-10-27)) | -On 27 October 2026 the system ran live on Base Sepolia, its contracts verified on Basescan (all but our paymaster), +On 27 October 2026 the system ran live on Base Sepolia, its contracts verified on Basescan ([the list](#the-live-run-on-the-explorers)), every proof made on a rented GPU and checked by RISC Zero's deployed router. The earlier end-to-end runs used local forks of Base Sepolia, against the real Morpho Blue deployment, and checked proofs with a MOCK verifier, since RISC Zero does not run on our Windows machine; real Groth16 seals of every guest (deposit, F1, inclusion, swap payment) also pass RISC @@ -142,6 +144,49 @@ test/fork/RealSealsV2.t.sol --isolate`. No deployment is kept running through ju the live run's servers and GPU stopped after it, and the whole system runs on a local fork (below). Evidence (block heights, txids, timings) and how to reproduce every run: [docs/TESTING.md](docs/TESTING.md). +### The live run on the explorers + +Every step of the recorded run of 27 October, on ZecBlock (Zcash testnet) and Basescan (Base Sepolia); its +timings and the rehearsal before it are in [docs/TESTING.md § 2c](docs/TESTING.md#2c-live-on-base-sepolia-2026-10-27). + +| Step | Chain | Transaction | +|---|---|---| +| The vault's address: shielded only (the explorer shows its Orchard receiver, never a balance or a history) | Zcash | [`utest1cj0ssr88…fzjc29hjgv`](https://testnet.zecblock.com/address/utest1cj0ssr88lec8yhxfvshutftg6283kye4c79mk9cugk8z0cpsmgwk4s5ueeeeskmu3z30pqhjjwmelpmz9nk3qtru70e686fzjc29hjgv) | +| The deposit, 0.05 TAZ, Ironwood → Ironwood (block 4,548,243) | Zcash | [`e5eaf50d…75825c`](https://testnet.zecblock.com/tx/e5eaf50dc641b30f25c2cdc7785099979c66fa3d6ced5c7baaf5ad580d75825c) | +| The deposit proven in zero knowledge, through RISC Zero's router | Base | [`0x92af7a0f…64384b`](https://sepolia.basescan.org/tx/0x92af7a0f3260c389799961c49a4971e76e5938b3eaac3e02c70bfa6b1464384b) | +| zZEC minted into Morpho | Base | [`0xbfe27bc5…f9e219`](https://sepolia.basescan.org/tx/0xbfe27bc562805b8d29cd853cac428e63769a83aa64088502580a9f7e63f9e219) | +| 10 MockUSDC borrowed by a brand-new account, gas sponsored | Base | [`0x0e17d8c2…469c9d`](https://sepolia.basescan.org/tx/0x0e17d8c2a2ee01206b9372998e43dc71bd6c59416d30ba6ff39479d7b9469c9d) | +| Closed from collateral | Base | [`0xf9c5f4bc…2059e3`](https://sepolia.basescan.org/tx/0xf9c5f4bcfda141209063d1e7a6e03c4f991d4995d845d88bee80c2ad102059e3) | +| Both payouts in one 3-of-5 FROST transaction (block 4,548,312) | Zcash | [`0e82a053…3e29fa`](https://testnet.zecblock.com/tx/0e82a05392014ada0859325e570666bb205553849c9ffc6e529b41cda93e29fa) | +| The payouts' delivery proven on Base: the claims final | Base | [`0xee567c75…3edce9`](https://sepolia.basescan.org/tx/0xee567c750be2e9f60096590b5f9fcbbb7af5e2792e17b64fed636fcbee3edce9) | +| Swap: the order opened from the buyer's own wallet, 5 MockUSDC locked | Base | [`0x27198f05…180e1a`](https://sepolia.basescan.org/tx/0x27198f0529e57f14b69d92ecaf0d828cf68c3b474c807ec936baf0ed6f180e1a) | +| Swap: the maker paid 0.00391343 TAZ (block 4,548,320) | Zcash | [`fa6ce860…245ee0`](https://testnet.zecblock.com/tx/fa6ce860bce2a39b3be793d36d6e767b1e9cd738b0d716bc9185ea7797245ee0) | +| Swap: the payment proven on Base | Base | [`0xac5ad94f…c318fa`](https://sepolia.basescan.org/tx/0xac5ad94fc62de4dbe76d9b17243f39713bffe6f8fc29a2e8167699c394c318fa) | +| Swap: settled, the tokens released to the maker | Base | [`0xd1c1989d…038d69`](https://sepolia.basescan.org/tx/0xd1c1989d699bb55d0b78d04a61f7ca77996043c4d139769d48b307bed7038d69) | + +The contracts, all verified on Basescan (`contracts/deployments/base-sepolia{,.swap,.relay,.paymaster}.json`): + +| Contract | Address | +|---|---| +| ZecuredRegistry | [`0xDC9C7E6abeF85A7bE0C65e4208bC87D098b86479`](https://sepolia.basescan.org/address/0xDC9C7E6abeF85A7bE0C65e4208bC87D098b86479#code) | +| ZecuredSpendLogic | [`0xaED9Fea923D8DD915f5A1367b9bc67DdC763e94a`](https://sepolia.basescan.org/address/0xaED9Fea923D8DD915f5A1367b9bc67DdC763e94a#code) | +| ZecuredIntake | [`0x90e5F23645529e5009eD3179ae0D6D9F1D0FbF78`](https://sepolia.basescan.org/address/0x90e5F23645529e5009eD3179ae0D6D9F1D0FbF78#code) | +| ZZEC (zZEC, the collateral receipt) | [`0xAa823B4A4339f371fd3BB3cd502e8D3feCEb2e83`](https://sepolia.basescan.org/address/0xAa823B4A4339f371fd3BB3cd502e8D3feCEb2e83#code) | +| SettlementAdapter | [`0xCFa5c24718Cc87C4731D1969Fd6a6EC88f3a74cb`](https://sepolia.basescan.org/address/0xCFa5c24718Cc87C4731D1969Fd6a6EC88f3a74cb#code) | +| SignerBondV2 (the signers' bonds) | [`0x58A1b34a9a641F03C62D5D3A7Fb3D3AD85269e1d`](https://sepolia.basescan.org/address/0x58A1b34a9a641F03C62D5D3A7Fb3D3AD85269e1d#code) | +| ZecuredSlasher | [`0x3983F1e1C7929A2208296895F01cE0d422917594`](https://sepolia.basescan.org/address/0x3983F1e1C7929A2208296895F01cE0d422917594#code) | +| ZecuredCompensator | [`0x8F5cBBa2264D62ca16228eDd10253e3284bEB174`](https://sepolia.basescan.org/address/0x8F5cBBa2264D62ca16228eDd10253e3284bEB174#code) | +| ZecuredVersions | [`0xae56343593A54C93043e0997c509607736EBEEA9`](https://sepolia.basescan.org/address/0xae56343593A54C93043e0997c509607736EBEEA9#code) | +| ZecuredSwap | [`0xb8Cc1E883FFF77c098802317cC98A966248948fC`](https://sepolia.basescan.org/address/0xb8Cc1E883FFF77c098802317cC98A966248948fC#code) | +| ZcashHeaderRelay (Equihash on Base) | [`0xEc34aA21cDe1366b397a99A4d90fc6B2f02cFaEa`](https://sepolia.basescan.org/address/0xEc34aA21cDe1366b397a99A4d90fc6B2f02cFaEa#code) | +| MockUSDC (MOCK: the loan token) | [`0x637BBA9C47190C7a1ab2412F72cF0A9c088dE58b`](https://sepolia.basescan.org/address/0x637BBA9C47190C7a1ab2412F72cF0A9c088dE58b#code) | +| MockZecOracle (MOCK: the ZEC price) | [`0x5CF271B75eC511FB12C3667a1dfc69AD566A12a9`](https://sepolia.basescan.org/address/0x5CF271B75eC511FB12C3667a1dfc69AD566A12a9#code) | +| DemoShockOracle (DEMO: the market's oracle) | [`0x53a3D268683Cd748b3B71B55a202dF48B7cE633A`](https://sepolia.basescan.org/address/0x53a3D268683Cd748b3B71B55a202dF48B7cE633A#code) | +| DemoCapPrice (DEMO: the coverage cap's price) | [`0xeC45AeDa5f0013fd37c9767eC79892Dd55834111`](https://sepolia.basescan.org/address/0xeC45AeDa5f0013fd37c9767eC79892Dd55834111#code) | +| SingletonPaymasterV7 (our paymaster, Pimlico's contract) | [`0x86c16062083a64aDb333404204346C9e4E03bb1e`](https://sepolia.basescan.org/address/0x86c16062083a64aDb333404204346C9e4E03bb1e#code) | + +Used, not ours: [Morpho Blue](https://sepolia.basescan.org/address/0xBBBBBbbBBb9cC5e90e3b3Af64bdAF62C37EEFFCb#code), [RISC Zero's verifier router](https://sepolia.basescan.org/address/0x0b144E07A0826182B6b59788c34b32Bfa86Fb711#code), [ERC-4337 EntryPoint v0.7](https://sepolia.basescan.org/address/0x0000000071727De22E5E9d8BAf0edAc6f37da032#code). + ## What is real and what is a mock **Real**, as run on Zcash testnet and Base Sepolia (live on 27 October 2026), and on forks of Base Sepolia and diff --git a/app/components/BorrowForm.tsx b/app/components/BorrowForm.tsx index 9a7ed18..ec51203 100644 --- a/app/components/BorrowForm.tsx +++ b/app/components/BorrowForm.tsx @@ -20,6 +20,7 @@ import { HealthRunway, healthOf, TokenAmount, usd, usdc, usdcFloor } from "./loa import { UsdcMark, ZzecMark } from "./marks"; import { Button, cx, Disclosure, Hint, Notice } from "./ui"; import { TxResult } from "./TxResult"; +import { ChainRef } from "./ChainRef"; /** The quick picks under the amount: shares of what can still be borrowed at the target LTV. */ const PICKS = [ @@ -148,12 +149,12 @@ export function BorrowForm({ > {result.to.toLowerCase() === loan.account.toLowerCase() ? ( <> - In your loan account {short(loan.account)}.{" "} + In your loan account {short(loan.account)}.{" "} {first ? "One transaction deployed it, authorized Zecured on Morpho and borrowed; it has never held ETH." : null} ) : ( <> - Sent to {short(result.to)}. + Sent to {short(result.to)}. )} @@ -286,7 +287,7 @@ export function BorrowForm({ ) : (

To this loan's account - {short(loan.account)} + {short(loan.account)} diff --git a/app/components/ChainRef.tsx b/app/components/ChainRef.tsx new file mode 100644 index 0000000..31bda55 --- /dev/null +++ b/app/components/ChainRef.tsx @@ -0,0 +1,60 @@ +/** + * A transaction, an address or a commit, in monospace like Mono, linked to its explorer where this build links one + * (lib/explorer.ts): Basescan for Base, ZecBlock for Zcash, GitHub for a commit, in a new tab with no referrer. Where + * the build links none (the mainnet beta, the replay's placeholder), it is plain text. Server-safe: no hooks. + */ +import type { ReactNode } from "react"; +import { IconExternal } from "./icons"; +import { cx } from "./ui"; +import { baseAddressUrl, baseTxUrl, commitUrl, zcashAddressUrl, zcashTxUrl } from "@/lib/explorer"; + +export type ChainRefKind = "base-tx" | "base-address" | "zcash-tx" | "zcash-address" | "commit"; + +const SITE: Record = { + "base-tx": "Basescan", + "base-address": "Basescan", + "zcash-tx": "ZecBlock", + "zcash-address": "ZecBlock", + commit: "GitHub", +}; + +export function chainRefUrl(kind: ChainRefKind, value: string): string | null { + switch (kind) { + case "base-tx": + return baseTxUrl(value); + case "base-address": + return baseAddressUrl(value); + case "zcash-tx": + return zcashTxUrl(value); + case "zcash-address": + return zcashAddressUrl(value); + case "commit": + return commitUrl(value); + } +} + +/** `children` is what shows: the whole value by default, or its shortened form. */ +export function ChainRef({ kind, value, children, className }: { kind: ChainRefKind; value: string; children?: ReactNode; className?: string }) { + const shown = children ?? value; + const url = chainRefUrl(kind, value); + const text = cx( + "font-mono text-[0.85em] text-[color:var(--strong-color,var(--color-text))]", + typeof shown === "string" && shown.length > 24 ? "break-all" : "whitespace-nowrap", + className, + ); + if (!url) return {shown}; + return ( + + {shown} + + (opens {SITE[kind]}, a third-party site, in a new tab) + + ); +} diff --git a/app/components/DemoPanel.tsx b/app/components/DemoPanel.tsx index 21a3e53..20cc266 100644 --- a/app/components/DemoPanel.tsx +++ b/app/components/DemoPanel.tsx @@ -19,6 +19,7 @@ import { TARGET_LTV_BPS, } from "@/lib/morpho"; import { formatTaz, ZATS_PER_ZEC } from "@/lib/zcash"; +import { ChainRef } from "./ChainRef"; const TOKEN_KEY = "zecured:demo-token"; const MODES: { mode: NodeMode; label: string; help: string }[] = [ @@ -423,7 +424,7 @@ function OracleCard({ token }: { token: string }) {

- Owner transaction {update.result.txHash} + Owner transaction

Positions show it at their next refresh.

diff --git a/app/components/KeyBackup.tsx b/app/components/KeyBackup.tsx index 7bb16f7..87697c0 100644 --- a/app/components/KeyBackup.tsx +++ b/app/components/KeyBackup.tsx @@ -7,6 +7,7 @@ import { REPLAY } from "@/lib/replay/flag"; import { CopyButton } from "./client"; import { IconArrowRight, IconDownload, IconKey, IconUpload } from "./icons"; import { Button, buttonClass, cx, Disclosure, Notice } from "./ui"; +import { ChainRef } from "./ChainRef"; /** Saves a loan backup as a local file (Blob URL: no network). */ export function downloadBackup(loan: StoredLoan): void { @@ -114,7 +115,7 @@ export function KeyBackup({ loan, onConfirmed }: { loan: StoredLoan; onConfirmed •••••••••••••• - account {short(loan.account)} + account {short(loan.account)} only in this browser
diff --git a/app/components/LoanCard.tsx b/app/components/LoanCard.tsx index 12885e7..5a02d99 100644 --- a/app/components/LoanCard.tsx +++ b/app/components/LoanCard.tsx @@ -8,6 +8,7 @@ import { IconArrowRight } from "./icons"; import { fallBeforeLiquidation, fallPercent, HealthRunway, healthOf, TokenAmount, usdc, zec } from "./loan"; import { statusOf } from "./status"; import { Badge, buttonClass, cx, Skeleton } from "./ui"; +import { ChainRef } from "./ChainRef"; /** * One loan of this browser, live, in a row: its collateral and the vault's status (the signers' word), its debt on @@ -51,7 +52,7 @@ export function LoanCard({ loan, compact = false }: { loan: StoredLoan; compact? {loan.demo ? Demo TAZ : null}

- shielded · account {short(loan.account)} + shielded · account {short(loan.account)} {loan.vaultId ? ( <> {" · "}vault {short(loan.vaultId, 8, 6)} diff --git a/app/components/LoansView.tsx b/app/components/LoansView.tsx index ab4e442..9a56a56 100644 --- a/app/components/LoansView.tsx +++ b/app/components/LoansView.tsx @@ -12,6 +12,7 @@ import { notifyLoansChanged, useLoans, useVault } from "@/lib/hooks"; import { importLoan, loanStore, short, type StoredLoan } from "@/lib/keys"; import { vaultHref } from "@/lib/routes"; import { formatZec } from "@/lib/zcash"; +import { ChainRef } from "./ChainRef"; export function LoansView() { const mounted = useMounted(); @@ -102,7 +103,7 @@ function LoanRow({ loan }: { loan: StoredLoan }) { )}

- Account {short(loan.account)} + Account {short(loan.account)} {loan.vaultId ? ( <> , vault {short(loan.vaultId, 10, 6)} diff --git a/app/components/WorldView.tsx b/app/components/WorldView.tsx index 42e5145..463021c 100644 --- a/app/components/WorldView.tsx +++ b/app/components/WorldView.tsx @@ -20,6 +20,7 @@ import { loanTokenName, priceSourceName } from "@/lib/labels"; import { REPLAY } from "@/lib/replay/flag"; import { SWAP_PRIVACY_SENTENCE } from "@/lib/v4"; import { formatTaz } from "@/lib/zcash"; +import { ChainRef } from "./ChainRef"; interface PublicData { market: MarketState | null; @@ -129,6 +130,7 @@ function LedgerHeading({ children }: { children: ReactNode }) { function fieldValue(event: RegistryEvent, key: string, value: string): ReactNode { if (/amount/i.test(key) && /^\d+$/.test(value)) return {formatTaz(BigInt(value))}; + if (/^0x[0-9a-fA-F]{40}$/.test(value)) return {short(value, 8, 6)}; if (/^0x[0-9a-fA-F]{40,}$/.test(value)) return {short(value, 8, 6)}; return {value}; } @@ -205,6 +207,9 @@ export function WorldView() { vault {short(e.vaultId, 8, 6)} + + tx {short(e.transactionHash, 8, 6)} + {Object.entries(e.fields).map(([k, v]) => ( {k} {fieldValue(e, k, v)} @@ -316,14 +321,14 @@ function YouView({ loan }: { loan: StoredLoan }) { return (

- Owner key: held here (secret), owner {short(loan.owner)} + Owner key: held here (secret), owner {short(loan.owner)}

- Account {short(loan.account)} + Account {short(loan.account)}

{loan.vaultAddress ? (

- Vault address {short(loan.vaultAddress, 12, 8)} + Vault address {short(loan.vaultAddress, 12, 8)}

) : null} {loan.destinationCommitment ? ( diff --git a/app/components/borrow/BorrowWizard.tsx b/app/components/borrow/BorrowWizard.tsx index c7ff88d..430914f 100644 --- a/app/components/borrow/BorrowWizard.tsx +++ b/app/components/borrow/BorrowWizard.tsx @@ -40,6 +40,7 @@ import { formatPeriod, openGate, openingFee } from "@/lib/v3"; import { awaitingDeposit, capWait, depositConfirmed, openingsNotice, openRefusal, refundProofRefusal } from "@/lib/v4"; import { blocksToDuration, formatTaz, formatZec } from "@/lib/zcash"; import { DemoPayment } from "./DemoPayment"; +import { ChainRef } from "@/components/ChainRef"; /** The flow in five chapters, as the site tells it; the π of "Prove" turns gold once the deposit is proven. */ const STEPS = ["Vault", "Key", "Deposit", "Prove", "Borrow"]; @@ -179,7 +180,7 @@ export function BorrowWizard() { - account {short(l.account)} + account {short(l.account)} {l.vaultId ? null : Vault not opened} @@ -915,7 +916,7 @@ function DepositStep({

- {loan.vaultAddress} +
diff --git a/app/components/position/Deleverage.tsx b/app/components/position/Deleverage.tsx index a78bf2d..d5cac83 100644 --- a/app/components/position/Deleverage.tsx +++ b/app/components/position/Deleverage.tsx @@ -15,6 +15,7 @@ import type { StoredLoan } from "@/lib/keys"; import { loanTokenName } from "@/lib/labels"; import { formatBps, formatUsdc, formatUsdPrice, formatWad, ltvBps, parseUsdc } from "@/lib/morpho"; import { formatTaz } from "@/lib/zcash"; +import { ChainRef } from "@/components/ChainRef"; /** * P-7, "Reduce debt with collateral" (S18): a market maker repays the amount asked for and takes @@ -148,7 +149,7 @@ export function Deleverage({ ], ["Health factor after", preview.healthAfter === null ? "∞ (no debt left)" : formatWad(preview.healthAfter)], ["Liquidation price after", preview.liquidationPriceAfter === null ? "—" : `${formatUsdPrice(preview.liquidationPriceAfter)} per ZEC`], - ["Executor", {quote.executor}], + ["Executor", ], ["Quote valid until", new Date(quote.expiresAt * 1000).toLocaleTimeString()], ]} /> @@ -184,7 +185,7 @@ export function Deleverage({

{submit.result.tx ? ( <> - The market maker's transaction: {submit.result.tx} + The market maker's transaction: ) : ( "Executed by the market maker." diff --git a/app/components/position/Exits.tsx b/app/components/position/Exits.tsx index 40a2ccf..fbe6267 100644 --- a/app/components/position/Exits.tsx +++ b/app/components/position/Exits.tsx @@ -20,6 +20,7 @@ import { loanStore, short, type StoredLoan } from "@/lib/keys"; import { loanTokenName } from "@/lib/labels"; import { formatUsdc, parseUsdc } from "@/lib/morpho"; import { formatTaz } from "@/lib/zcash"; +import { ChainRef } from "@/components/ChainRef"; /** The borrower accepts up to 1 % less ZEC than quoted (price moves between quote and execution). */ const CLOSE_SLIPPAGE_BPS = 100n; @@ -213,7 +214,7 @@ function RepayFlow({ loan, vault, snapshot, commitment, onChanged }: FlowProps) {!enough ? (

- Send at least {formatUsdc(needed - snapshot.usdcBalance)} more {loanTokenName()} to {loan.account}, then + Send at least {formatUsdc(needed - snapshot.usdcBalance)} more {loanTokenName()} to , then refresh. That transfer is public: send it from an address with no link to you, or close from collateral instead.

@@ -319,7 +320,7 @@ function CloseFlow({ loan, vault, snapshot, commitment, onChanged }: FlowProps) {quote.executor}], + ["Market maker", ], ["Quote valid until", formatUtc(quote.expiresAt)], ]} /> @@ -380,14 +381,14 @@ export function PendingClose({ loan, vault, onChanged }: { loan: StoredLoan; vau ? "Expired without execution" : "Waiting for the market maker", ], - ["Market maker", {pending.executor}], + ["Market maker", ], ["Valid until", formatUtc(Number(pending.deadline))], ["Nonce", {short(pending.nonce, 8, 6)}], ...(pending.txHash ? ([ [ "Market maker's transaction", - sampleTransactions() ? sample data : {short(pending.txHash, 10, 8)}, + sampleTransactions() ? sample data : {short(pending.txHash, 10, 8)}, ], ] as [string, ReactNode][]) : []), diff --git a/app/components/position/Ledger.tsx b/app/components/position/Ledger.tsx index 2ea5b87..37f7526 100644 --- a/app/components/position/Ledger.tsx +++ b/app/components/position/Ledger.tsx @@ -44,6 +44,7 @@ import { refundClaims, } from "@/lib/v3"; import { blocksToDuration, formatTaz } from "@/lib/zcash"; +import { ChainRef } from "@/components/ChainRef"; const now = () => Math.floor(Date.now() / 1000); @@ -767,7 +768,7 @@ function CompromisedCard({ ledger }: { ledger: VaultLedger }) {

{ledger.links.compensator ? (

- Compensator {ledger.links.compensator} + Compensator

) : null} diff --git a/app/components/position/PositionView.tsx b/app/components/position/PositionView.tsx index 4f42dae..588eb2c 100644 --- a/app/components/position/PositionView.tsx +++ b/app/components/position/PositionView.tsx @@ -32,6 +32,7 @@ import { Deleverage } from "./Deleverage"; import { ClaimsCard, PendingActivationCard, PendingRefundsCard, RefundsCard, TheftNotice } from "./Ledger"; import { PartialRelease } from "./PartialRelease"; import { ProtectionCard } from "./Protection"; +import { ChainRef } from "@/components/ChainRef"; const ZERO_ACCOUNT = "0x0000000000000000000000000000000000000000"; @@ -248,7 +249,7 @@ export function PositionView() { {closed ? (toMaker ? "Repaid by the market maker" : "Repaid") : rate !== null ? `${ratePct(rate)} a year, accruing every block` : "including accrued interest"} {account ? ( <> - {" · "}account {short(account)}, gas sponsored + {" · "}account {short(account)}, gas sponsored ) : null}

@@ -374,7 +375,7 @@ export function PositionView() { [ "Smart account", - {account} + , ], diff --git a/app/components/public/EvidenceView.tsx b/app/components/public/EvidenceView.tsx index 4686599..988d694 100644 --- a/app/components/public/EvidenceView.tsx +++ b/app/components/public/EvidenceView.tsx @@ -11,6 +11,7 @@ import { explorerLink, orderedSections, type Evidence, type EvidenceSection } fr import { useEvidence } from "@/lib/hooks"; import { short } from "@/lib/keys"; import { formatPeriod } from "@/lib/v3"; +import { ChainRef } from "@/components/ChainRef"; const DURATION_LABEL: Record = { "deposit-to-proof": "Deposit on Zcash to its proof on Base", @@ -45,7 +46,7 @@ function Section({ e, s, own }: { e: Evidence; s: EvidenceSection; own: boolean ) : ( - {m.commit} + {m.tag ? (tag {m.tag}) : null} ), @@ -55,13 +56,13 @@ function Section({ e, s, own }: { e: Evidence; s: EvidenceSection; own: boolean (c): [string, ReactNode] => [ c.name, - {c.address} + , ], ), - ...(m.router ? [["Proof router", {m.router}] as [string, ReactNode]] : []), - ...(m.relay ? [["Zcash header relay", {m.relay}] as [string, ReactNode]] : []), - ...(m.oracle ? [["Market oracle", {m.oracle}] as [string, ReactNode]] : []), + ...(m.router ? [["Proof router", ] as [string, ReactNode]] : []), + ...(m.relay ? [["Zcash header relay", ] as [string, ReactNode]] : []), + ...(m.oracle ? [["Market oracle", ] as [string, ReactNode]] : []), ...m.imageIds.map((g): [string, ReactNode] => [`Guest ${g.name}`, {g.imageId}]), ]} /> @@ -71,7 +72,7 @@ function Section({ e, s, own }: { e: Evidence; s: EvidenceSection; own: boolean
  • {x.label} {x.name} - {x.address ? {short(x.address, 10, 6)} : null} + {x.address ? {short(x.address, 10, 6)} : null} {x.why}
  • ))} diff --git a/app/components/public/StatusView.tsx b/app/components/public/StatusView.tsx index 9bb40dc..01be757 100644 --- a/app/components/public/StatusView.tsx +++ b/app/components/public/StatusView.tsx @@ -15,6 +15,7 @@ import { formatBps, formatUsdc } from "@/lib/morpho"; import { formatWadPercent } from "@/lib/preliq"; import { anonymitySets, bondNumbers, capNumbers, relayLag, SIGNERS } from "@/lib/public"; import { formatTaz } from "@/lib/zcash"; +import { ChainRef } from "@/components/ChainRef"; const MODE_TONE: Record = { normal: "ok", @@ -297,7 +298,7 @@ export function StatusView() { [ "Commit", - {r.commit} {r.treeClean ? null : uncommitted changes} + {r.treeClean ? null : uncommitted changes} , ], ["Started", r.startedAt ? formatUtc(r.startedAt) : "—"], diff --git a/app/components/swap/BuyZec.tsx b/app/components/swap/BuyZec.tsx index 4901de9..68d17da 100644 --- a/app/components/swap/BuyZec.tsx +++ b/app/components/swap/BuyZec.tsx @@ -30,6 +30,7 @@ import { SWAP_PRIVACY_SENTENCE } from "@/lib/v4"; import { connectWallet, ensureChain, injectedWallet, sendFromWallet, WalletError } from "@/lib/wallet"; import { formatTaz, formatZec, TICKER } from "@/lib/zcash"; import { QrScan } from "./QrScan"; +import { ChainRef } from "@/components/ChainRef"; const store = () => new SwapStore(browserStorage()); const nowSeconds = () => Math.floor(Date.now() / 1000); @@ -350,7 +351,7 @@ export function BuyZec() {
    {quote ? (

    - The maker {short(quote.quote.maker)} has {Math.round(quote.quote.payWindow / 3600)} h to pay and prove, or you take your{" "} + The maker {short(quote.quote.maker)} has {Math.round(quote.quote.payWindow / 3600)} h to pay and prove, or you take your{" "} {name} back.

    ) : null} @@ -408,7 +409,7 @@ export function BuyZec() { {wallet ? (

    - {short(wallet)} + {short(wallet)} · your wallet, visible on Base when you open the order

    ) : null} @@ -637,7 +638,7 @@ function OrderCard({ Order {short(order.orderId, 10, 8)} - · paid to {short(order.receiver, 12, 8)} + · paid to {short(order.receiver, 12, 8)} · destination {order.destinationSent ? "handed to the maker" : "not yet handed to the maker"}

    diff --git a/app/lib/explorer.ts b/app/lib/explorer.ts index 2095634..58608f1 100644 --- a/app/lib/explorer.ts +++ b/app/lib/explorer.ts @@ -1,27 +1,52 @@ /** - * Where the app links a Base or a Zcash transaction. A live build follows its deployment profile (lib/deployment.ts - * `baseExplorerTx`, `zcashExplorerTx`: the judge testnet links, the mainnet beta does not); a replay build (DEMO) - * links its recording's real transactions, and nothing while its recording is the placeholder, whose hashes are dummies. + * Where the app links a Base or a Zcash transaction or address. A live build follows its deployment profile + * (lib/deployment.ts `baseExplorerTx`, `zcashExplorerTx`: the judge testnet links, the mainnet beta does not); a replay + * build (DEMO) links its recording's real transactions, and nothing while its recording is the placeholder, whose + * hashes are dummies. An explorer's address page sits beside its `/tx/` page. */ import { DEPLOYMENT } from "./deployment"; import { REPLAY } from "./replay/flag"; import { recording } from "./replay/recording"; -export function baseTxUrl(hash: string): string | null { +/** The explorers' `/tx/` prefixes this build links to, or null where it links none. */ +function txPrefixes(): { base: string | null; zcash: string | null } { if (REPLAY) { const r = recording(); - return r.placeholder ? null : `${r.explorers.base}${hash}`; + return r.placeholder ? { base: null, zcash: null } : { base: r.explorers.base, zcash: r.explorers.zcash }; } - return DEPLOYMENT.baseExplorerTx ? `${DEPLOYMENT.baseExplorerTx}${hash}` : null; + return { base: DEPLOYMENT.baseExplorerTx, zcash: DEPLOYMENT.zcashExplorerTx }; +} + +function addressPrefix(txPrefix: string | null): string | null { + return txPrefix && /\/tx\/$/.test(txPrefix) ? txPrefix.replace(/\/tx\/$/, "/address/") : null; +} + +export function baseTxUrl(hash: string): string | null { + const p = txPrefixes().base; + return p ? `${p}${hash}` : null; +} + +/** An account's or a contract's page on the Base explorer. */ +export function baseAddressUrl(address: string): string | null { + const p = addressPrefix(txPrefixes().base); + return p ? `${p}${address}` : null; } /** A Zcash transaction's link, its id in display order (as `zecured-wallet balance` prints it). */ export function zcashTxUrl(txid: string): string | null { - if (REPLAY) { - const r = recording(); - return r.placeholder || !r.explorers.zcash ? null : `${r.explorers.zcash}${txid}`; - } - return DEPLOYMENT.zcashExplorerTx ? `${DEPLOYMENT.zcashExplorerTx}${txid}` : null; + const p = txPrefixes().zcash; + return p ? `${p}${txid}` : null; +} + +/** A unified address's page on the Zcash explorer: its receivers, never a shielded balance or history. */ +export function zcashAddressUrl(address: string): string | null { + const p = addressPrefix(txPrefixes().zcash); + return p ? `${p}${address}` : null; +} + +/** A commit of this repository on GitHub. */ +export function commitUrl(sha: string): string | null { + return /^[0-9a-f]{7,40}$/i.test(sha) ? `https://github.com/Himess/zecured/commit/${sha}` : null; } /** DEMO: the replay plays sample data, whose hashes are dummies: the app names no transaction then. */ diff --git a/docs/SUBMISSION.md b/docs/SUBMISSION.md index 63e4078..9c29126 100644 --- a/docs/SUBMISSION.md +++ b/docs/SUBMISSION.md @@ -5,7 +5,8 @@ and what is a mock. Every link is absolute, so it works from the form. **Links.** The site: https://zecured.vercel.app/ (the details: https://zecured.vercel.app/docs/). The app replaying our live run of 27 October in your browser (DEMO, no server needed): https://zecured.vercel.app/app/. -The video of the live run: https://youtu.be/iyXiao54KWk. The code: https://github.com/Himess/zecured. The same pages on GitHub Pages: https://himess.github.io/zecured/. +The video of the live run: https://youtu.be/iyXiao54KWk. Its transactions on ZecBlock and Basescan, and the contracts, +verified on Basescan: https://github.com/Himess/zecured#the-live-run-on-the-explorers. The code: https://github.com/Himess/zecured. The same pages on GitHub Pages: https://himess.github.io/zecured/. **Team.** Himess (https://github.com/Himess). Built from scratch for ZECATHON 2026, Cross-Chain track; first commit 10 October 2026.