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.