Skip to content

B20 Playground demo (/demos/b20) + explorer announcement/memo decoding - #50

Closed
montycheese wants to merge 64 commits into
mainfrom
b20-docs-vercel-preview
Closed

B20 Playground demo (/demos/b20) + explorer announcement/memo decoding#50
montycheese wants to merge 64 commits into
mainfrom
b20-docs-vercel-preview

Conversation

@montycheese

@montycheese montycheese commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds the B20 Playground interactive demo at /demos/b20 — a hands-on tour of Base-native B20 tokens on Vibenet — plus the explorer decoding needed to surface B20 events, and small shared UI primitives.

Note: this branch started as a Vercel preview fork of feat/add-b20-docs (that branch's original PR #45 is closed). It has since become the canonical, merge-ready branch: the preview-only no-op was removed and the code has been through audit, review, and a structural cleanup. It targets main.

What's included

B20 Playground demo (app/demos/b20/) — four modules behind a tab bar:

  • Policy Viewer — inspect any B20's policy scopes, resolve the Policy Registry, and check whether an address is authorized per scope.
  • Memos — attach an indexed bytes32 memo to transfers/mints/burns, with an on-chain memo history for the selected token.
  • Announcements — publish Asset-token disclosures and schedule UI-multiplier updates through the on-chain announcement bracket (operator-gated), with inline success confirmation.
  • Native Deployment — create Asset/Stablecoin B20 tokens through the singleton Factory precompile, with a deterministic-address preview and a post-deploy success view.

Structured as a thin B20Demo orchestrator (state + wallet/inspect/send wiring) that dispatches to per-module components, with shared lib/ (types, constants, viem client, protocol ABIs/helpers, glossary, prompts, samples) and reusable components/ primitives.

Explorer support (app/vibenet/)

  • library/explorer.ts — pure decoders for the B20 announcement bracket (Announcement / UIMultiplierUpdated / EndAnnouncement), bytes32 memo calldata/events, executeBatch, ERC-20 transfers, and EIP-8130 scope/type formatting.
  • explorer/tx/[hash]/page.tsx — renders those decoded B20 events in the transaction viewer.

Shared UI

  • components/ui/InfoTooltip.tsx — accessible tooltip used for inline concept help across the demo.
  • components/ui/icons.tsx — added icons.
  • components/ui/Modal.tsx — accessibility improvements (labelled dialog, focus move-in/restore).

Wiring & docs — demo catalogue entry, sitemap, globals.css animations, and regenerated llms.txt / AGENTS.md.

Testing

  • app/demos/b20/lib/protocol.test.ts — variant/address parsing, memo bytes32 round-trip + boundaries, deployment-param ABI encoding, amount validation, featureId, formatAmount.
  • app/demos/b20/lib/prompts.test.ts — prompt catalogue integrity.
  • app/vibenet/library/explorer.test.ts — all memo-call shapes, the announcement-bracket events, executeBatch offset decoding, the EIP-8130 scope bitmask, and number/type formatters (fixtures built with viem encoders so they match on-chain layout).

npm run typecheck, npm run lint, npm run build, and npm test (73 tests) all pass. The four modules were verified in-browser.

Notes for reviewers

  • The demo runs against Vibenet (raw EIP-1193 wallet + viem for reads); it carries the Vibenet maintenance banner and is unusable while the devnet is down.
  • The TIPS page remains disabled on the public build — unrelated to this branch.

montycheese and others added 30 commits July 14, 2026 20:19
Migrated the app out of the internal Nx / @cbhq template into a standalone
Next.js (App Router) project that builds and deploys with the standard
toolchain on Vercel.

- App code (snapshots, upgrades, vibenet, shared shell/UI) carried over
  unchanged; it had no internal @cbhq imports.
- Replaced internal tooling with Vercel-native config: standard package.json
  scripts (dev/build/start/lint/typecheck/test), next.config.mjs,
  Next tsconfig, eslint-config-next, Tailwind + PostCSS, Vitest.
- Dropped Nx, Helm charts, terraform, pipelines, Dockerfile, and the
  @cbhq registry/config.
- Base Sans fonts via next/font/local; env documented in .env.example.

Verified: next build (13 routes), lint, typecheck, vitest (10 tests), and a
local dev run of /, /snapshots, /upgrades, /vibenet.
Brings the canonical /api/snapshots implementation from the v2-snapshots-ui
repo into omni:
- R2RequestError with status, and getLatestSnapshotManifest that walks
  newest-first and skips 404s, so an in-progress latest snapshot (manifest
  not yet uploaded) falls back to the previous completed one.
- Returns archiveUrl / archiveFile / metadataUrl (added as optional fields
  on the Snapshot type).

Kept omni's two prior review fixes rather than regress them: fail closed
(502) when any configured network errors instead of a partial 206, and no
SUPPORTED_COMPONENTS allowlist filter so new manifest components are counted.

Verified: next build (13 routes), lint, types, and vitest (10 tests).
Ports v2-snapshots-ui@ba0c382 into the snapshots UI so the two stop drifting:
- Group account_changesets + storage_changesets into a single "State History"
  row (combined size); toggling it toggles both.
- Gate dependent components: transaction_senders requires transactions;
  rocksdb_indices requires transactions + receipts + state history. Unmet
  dependents are disabled and auto-deselected on toggle.
- Update the Full preset to state, headers, transactions, receipts, and state
  history (+ description).
- Rewrite the download command to `base-reth-node download --chain <chain>`
  with --with-txs/--with-senders/--with-receipts/--with-state-history flags,
  --<preset> --resumable for presets, --archive --without-rocksdb, and map
  mainnet to `base`.
- Component count reflects the grouped State History row.
feat: upgrade runtime to Node.js 24
Co-authored-by: Montana Wong <montanawong@gmail.com>
Co-authored-by: vercel[bot] <35613825+vercel[bot]@users.noreply.github.com>
Co-authored-by: Vercel <vercel[bot]@users.noreply.github.com>
Co-authored-by: Montana Wong <montanawong@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Claude <noreply@anthropic.com>
The success screen rendered inline on the Native Deployment tab, which read as
"still deploying," and its state was lost the moment you followed a link and came
back. A modal is the right fit for an ephemeral confirmation: it overlays the
tab, is expected to be transient, and dismisses cleanly.

- CreatedView is now the body of a Modal opened when a token is created; the
  deploy form stays put behind it. Next-step actions navigate and close the
  modal; Done / Escape / backdrop dismiss it.
- Explorer and creation-transaction links open in a new tab, so following one no
  longer tears down the modal and the demo context.

Modal a11y, while here (addresses the standing gaps, benefits all consumers):
- aria-labelledby wires the title to the dialog, so it's announced by name.
- Focus moves into the panel on open and is restored to the trigger on close.
Focus-trap intentionally left out for now — the account demo nests Radix poppers
in modals and a naive trap can fight their focus management; wants its own change.
…tent

Walk back the modal. The Native Deployment tab is where the token is created, so
an inline result there is the natural home — the modal was over-correcting for
problems that are better solved directly:

- Lift `created` from DeployModule to the parent B20Demo, so the success view
  survives switching tabs and returning to Native Deployment (verified). It
  resets only via "Create another token".
- Keep the fix that actually mattered: Explorer / creation-transaction links open
  in a new tab, so following one no longer unmounts the demo and loses the result.
- Full-width success layout (banner + two-column identity / what-it-did + the
  three next-step cards) instead of the cramped modal body.

The Modal a11y improvements from the previous commit (aria-labelledby, initial
focus, focus restore) are intentionally kept — the account demo's modals use them.
@vercel

vercel Bot commented Aug 7, 2026

Copy link
Copy Markdown

Deployment failed for project omni-ui with the following error:

You don't have permission to create a Preview Deployment for this Vercel project: omni-ui.

View Documentation: https://vercel.com/docs/accounts/team-members-and-roles

Publishing an announcement only cleared the form and logged a row in the
Activity panel, so operators had no clear signal the action landed. Capture
the tx hash from onSend and render a persistent success banner (role=status)
at the top of the composer: the announcement ID, a disclosure/multiplier
summary, a new-tab transaction link, and a dismiss control — matching the
token-creation confirmation.

Co-Authored-By: Claude <noreply@anthropic.com>
…oser

The announcement confirmation appeared to flash and vanish. Root cause was
not the banner itself: send() re-inspects the token after every tx, yielding
a fresh token object with the same address, and the operator-role effect was
keyed on the token object — so it re-ran, resetting isOperator to false and
refetching on every action. That flipped tokenAccess operator→external→operator
for ~100ms, glitching the whole operator UI (and the banner's subtree) after
each publish.

- Key the operator-role effect on the token address, not the object identity,
  so a same-address re-inspect no longer re-runs it (no flicker). Genuine token
  switches still reset and refetch.
- Render the publish confirmation at the top of the module, outside the
  token/operator-gated Card, with a subtle animate-in entrance so it can never
  be hidden by transient inspect state.

Co-Authored-By: Claude <noreply@anthropic.com>
Audit fixes:
- Add rel="noreferrer" to the two docs links that open new tabs (reverse
  tabnabbing; matches every other _blank link in the repo).
- Remove the Vercel-preview no-op comment from demos/b20/page.tsx.
- Add type="button" to the recent-token, memo-kind, and variant selectors.
- Clear CopyPromptButton's reset timer on unmount.

Test coverage (pure decode/format logic that was untested):
- explorer.ts: executeBatch offset decoding, all four memo-call shapes,
  UIMultiplierUpdated/EndAnnouncement events, EIP-8130 scope bitmask, and the
  number/type formatters. Fixtures are built with viem encoders so they match
  on-chain layout by construction.
- protocol.ts: amount() validation, featureId per-variant, shortAddress, and
  the memo length boundary / empty-memo cases.

71 tests pass (was 53). typecheck, lint, build clean.

Co-Authored-By: Claude <noreply@anthropic.com>
Inline errors:
- Add an ErrorNote banner (role="alert", matching the inspectError style) and
  route the Memo, Announcement, and Deploy submit failures to it instead of the
  browser alert() — consistent, non-blocking feedback in a public demo.

Dedupe:
- Move formatAmount and a new bytes32ToMemo (the inverse of memoToBytes32) into
  protocol.ts, and consume them from B20Demo.tsx and MemoHistory.tsx. Removes
  three copies of amount formatting / memo decoding and makes both unit-testable.
- Cover formatAmount and bytes32ToMemo (incl. the memoToBytes32 round-trip) in
  protocol.test.ts.

73 tests pass. typecheck, lint, build clean; inline error verified in-browser.

Co-Authored-By: Claude <noreply@anthropic.com>
Comment thread app/demos/b20/B20Demo.tsx
Comment on lines +296 to +320
useEffect(() => {
if (typeof window === 'undefined') return;
window.requestAnimationFrame(() => window.scrollTo({ top: 0, left: 0, behavior: 'auto' }));
}, [created?.address, module]);

const selectModule = (next: Module) => { setModule(next); trackB20ModuleSelect(next); };
const tokenAccess: TokenAccess = token?.address.toLowerCase() === SAMPLE_TOKEN.toLowerCase() ? 'sample' : isOperator ? 'operator' : wallet ? 'external' : 'disconnected';
return (
<div className="animate-in -mb-20 flex min-h-[calc(100vh-116px)] flex-col gap-5 pb-6 text-black [&_.text-3xl]:hidden [&_.tracking-tight]:capitalize dark:text-white">
<header className="flex flex-wrap items-center justify-end gap-3 border-b border-bds-gray-10 pb-4 dark:border-white/10">
<div className={cn('flex flex-wrap items-center gap-2', textVariantClasses.label)}>
<span className="rounded-full border border-bds-gray-10 px-3 py-2 dark:border-white/10"><span className="mr-2 text-bds-green-50">●</span>Vibenet</span>
<Link href="/vibenet/faucet" className="rounded-full border border-bds-gray-10 px-3 py-2 hover:border-base-blue dark:border-white/10">Faucet</Link>
{wallet ? <div className="inline-flex items-center gap-2 rounded-full border border-bds-gray-10 px-3 py-1.5 dark:border-white/10"><CopyableValue value={wallet} display={shortAddress(wallet)} /><span aria-hidden="true">·</span><span>{walletBalance === null ? '…' : `${Number(formatEther(walletBalance)).toFixed(3)} ETH`}</span><button type="button" onClick={disconnect} className="rounded-full px-2 py-1 text-[11px] text-bds-gray-60 transition-colors hover:bg-bds-gray-5 hover:text-black dark:text-bds-gray-30 dark:hover:bg-white/10 dark:hover:text-white">Disconnect</button></div> : <Button size="sm" onClick={() => void connect()}>Connect wallet</Button>}
</div>
</header>
<div className="flex min-h-0 flex-1 flex-col gap-5">
<div className="overflow-x-auto">
<Tabs items={MODULES} value={module} onChange={(value) => selectModule(value as Module)} ariaLabel="B20 modules" />
</div>
<main className="min-w-0 flex-1">{module === 'policy' ? <PolicyModule token={token} tokenAccess={tokenAccess} address={tokenAddress} setAddress={setTokenAddress} recent={recent} onInspect={inspect} onDeploy={() => selectModule('deploy')} busy={busy} checkAddress={checkAddress} setCheckAddress={setCheckAddress} checks={checks} onCheck={checkPolicies} /> : null}{module === 'memos' ? <MemoModule token={token} tokenAccess={tokenAccess} onDeploy={() => selectModule('deploy')} onSend={send} busy={busy} /> : null}{module === 'announcements' ? token && tokenAccess === 'sample' ? <SampleAnnouncementViewer onDeploy={() => selectModule('deploy')} /> : <AnnouncementModule token={token} tokenAccess={tokenAccess} wallet={wallet} onDeploy={() => selectModule('deploy')} onSend={send} busy={busy} /> : null}{module === 'deploy' ? <DeployModule wallet={wallet} onSend={send} created={created} onCreated={async (next) => { if (wallet) setRecent(writeRecent(wallet, next)); setTokenAddress(next.address); setCreated(next); await inspect(next.address); }} onReset={() => setCreated(null)} onNavigate={selectModule} busy={busy} /> : null}</main>
</div>
{inspectError ? <div role="alert" className="rounded-xl border border-bds-red-20 bg-bds-red-0 px-4 py-3 text-[13px] text-bds-red-70 dark:border-bds-red-80 dark:bg-bds-red-100/40 dark:text-bds-red-20">{inspectError}</div> : null}
<Activity rows={activity} />
</div>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nestling all of this HTML into a use effect seems like an anti pattern, either change it or make it into a component for cleaner reading

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in ad5a00c — reformatted the dense single-line JSX returns into readable multi-line JSX (ran prettier over the file). Kept it as formatting rather than a full component split to keep the diff behavior-preserving; happy to extract sub-components if you want it broken down further.

Comment thread app/demos/b20/B20Demo.tsx Outdated
}

function PolicyModule({ token, tokenAccess, address, setAddress, recent, onInspect, onDeploy, busy, checkAddress, setCheckAddress, checks, onCheck }: { token: TokenInfo | null; tokenAccess: TokenAccess; address: string; setAddress: (v: string) => void; recent: RecentToken[]; onInspect: (v?: string) => void; onDeploy: () => void; busy: string | null; checkAddress: string; setCheckAddress: (v: string) => void; checks: Record<string, boolean> | null; onCheck: () => void }) {
return <div className="flex flex-col gap-5"><section><div className="flex items-center gap-3"><span className="text-3xl">♢</span><div><Text variant="title2">Policy Viewer</Text><Text variant="body" tone="muted">Inspect any B20 token’s policies and check address authorization.</Text></div></div></section>{!token && !address ? <div className="grid gap-4 md:grid-cols-2"><Card className="flex flex-col bg-white p-5 dark:bg-white/5"><span className="mb-3 w-fit rounded-full bg-bds-blue-0 px-2 py-1 text-[11px] text-base-blue dark:bg-bds-blue-100/40 dark:text-bds-blue-20">No wallet required</span><Text variant="headline">Explore sample token</Text><Text variant="footnote" tone="muted">Inspect a predeployed Asset B20 and learn how its policy scopes are configured.</Text><Button className="mt-5 self-start" variant="outline" onClick={() => onInspect(SAMPLE_TOKEN)} disabled={busy === 'inspect'}>{busy === 'inspect' ? 'Loading…' : 'Explore sample'}</Button></Card><Card className="flex flex-col bg-white p-5 dark:bg-white/5"><span className="mb-3 w-fit rounded-full bg-bds-green-0 px-2 py-1 text-[11px] text-bds-green-70 dark:bg-bds-green-100/40 dark:text-bds-green-20">Interactive</span><Text variant="headline">Create your own token</Text><Text variant="footnote" tone="muted">Deploy an Asset B20, receive its issuer roles, and sign announcements with your wallet.</Text><Button className="mt-5 self-start" onClick={onDeploy}>Create token</Button></Card></div> : null}<Card className="grid overflow-hidden bg-white md:grid-cols-[minmax(0,1fr)_250px] dark:bg-white/5"><div className="p-5"><Field label="Token"><div className="flex gap-2"><Input value={address} onChange={(e) => setAddress(e.target.value)} placeholder="Paste B20 token address" /><Button size="sm" variant="outline" onClick={() => onInspect()} disabled={busy === 'inspect'}>{busy === 'inspect' ? 'Checking…' : 'Check'}</Button></div></Field>{recent.length ? <><p className="mt-4 text-[12px] text-bds-gray-50">or select a recent deployment</p><div className="mt-3 flex flex-wrap gap-2">{recent.map((entry) => <button key={entry.address} type="button" onClick={() => onInspect(entry.address)} className="rounded-lg border border-bds-gray-10 px-3 py-2 text-left text-[12px] hover:border-base-blue dark:border-white/10"><strong className="block text-base-blue">{entry.symbol}</strong>{entry.variant}</button>)}</div></> : <p className="mt-4 text-[12px] text-bds-gray-50">Recent B20 deployments from this wallet appear here.</p>}</div><div className="border-t border-bds-gray-10 bg-bds-gray-5 p-5 md:border-l md:border-t-0 dark:border-white/10 dark:bg-white/[0.03]"><div className="flex items-center gap-1.5"><Text variant="label">Variant</Text><InfoTooltip label="About B20 variants">{B20_HELP.variant}</InfoTooltip></div>{token ? <><p className="mt-6 text-[18px] font-medium capitalize">{token.variant}</p><span className={cn('mt-3 inline-block rounded-full px-2 py-1 text-[11px]', tokenAccess === 'operator' ? 'bg-bds-green-0 text-bds-green-70 dark:bg-bds-green-100/40 dark:text-bds-green-20' : 'bg-bds-gray-10 text-bds-gray-60 dark:bg-white/10 dark:text-bds-gray-30')}>{tokenAccess === 'sample' ? 'Sample token · Read only' : tokenAccess === 'operator' ? 'Your token · OPERATOR_ROLE' : tokenAccess === 'external' ? 'External token · No operator access' : 'Connect wallet to check access'}</span><Link href="https://github.com/base/base-std/tree/main/docs/B20" target="_blank" rel="noreferrer" className="mt-5 block text-[12px] text-base-blue hover:underline">How variants work ↗</Link></> : <p className="mt-3 text-[12px] text-bds-gray-50">Load a token to inspect its variant.</p>}</div></Card>{token ? <><section className="rounded-2xl border border-bds-gray-10 bg-white p-5 dark:border-white/10 dark:bg-white/5"><div className="flex items-center justify-between gap-3"><div><div className="flex items-center gap-1.5"><Text variant="headline">Policy scopes</Text><InfoTooltip label="About policy scopes">{B20_HELP.policyScopes}</InfoTooltip></div><Text variant="footnote" tone="muted">Each scope maps to a Policy Registry entry. Burn is role-gated, not policy-gated.</Text></div><a href="https://github.com/base/base-std/tree/main/docs/PolicyRegistry" target="_blank" rel="noreferrer" className="text-[12px] text-base-blue hover:underline">Learn about scopes ↗</a></div><div className="mt-5 grid gap-3 sm:grid-cols-2 xl:grid-cols-3">{token.policies.map((policy) => <div key={policy.scope} className="rounded-xl border border-bds-gray-10 p-4 dark:border-white/10"><div className="flex items-center gap-1.5"><strong className="text-[13px]">{policy.label}</strong>{SCOPE_HELP[policy.scope] ? <InfoTooltip label={`About ${policy.label}`}>{SCOPE_HELP[policy.scope]}</InfoTooltip> : null}</div><div className="mt-4 flex items-center gap-1.5"><p className="text-[11px] text-bds-gray-50">Policy ID</p><InfoTooltip label="About policy ID">{B20_HELP.policyId}</InfoTooltip></div><p className="text-[16px]">{policy.id.toString()}</p><span className={cn('mt-2 inline-flex items-center gap-1 rounded px-2 py-1 text-[11px]', policy.id === 0n ? 'bg-bds-orange-0 text-bds-orange-70 dark:bg-bds-orange-100/40' : policy.exists ? 'bg-bds-green-0 text-bds-green-70 dark:bg-bds-green-100/40' : 'bg-bds-red-0 text-bds-red-70 dark:bg-bds-red-100/40')}>{policy.id === 0n ? 'Wide open' : policy.exists ? 'Configured' : 'Missing policy'}<InfoTooltip label="What this status means">{policy.id === 0n ? B20_HELP.statusWideOpen : policy.exists ? B20_HELP.statusConfigured : B20_HELP.statusMissing}</InfoTooltip></span><div className="mt-3 flex items-center gap-1.5"><p className="text-[11px] text-bds-gray-50">Admin</p><InfoTooltip label="About the policy admin">{B20_HELP.policyAdmin}</InfoTooltip></div><p className="font-mono text-[12px]">{shortAddress(policy.admin)}</p>{checks ? <p className={cn('mt-3 text-[12px]', checks[policy.scope] ? 'text-bds-green-60' : 'text-bds-red-60')}>{checks[policy.scope] ? '◉ Authorized' : '⊗ Blocked'}</p> : null}</div>)}</div></section><Card className="bg-white p-5 dark:bg-white/5"><div className="flex items-center gap-1.5"><Text variant="headline">Check an address</Text><InfoTooltip label="How the check works">{B20_HELP.checkAddress}</InfoTooltip></div><Text variant="footnote" tone="muted">Check the selected address against every displayed Policy Registry entry.</Text><div className="mt-4 flex gap-2"><Input value={checkAddress} onChange={(e) => setCheckAddress(e.target.value)} placeholder="Enter address (0x…)" /><Button size="sm" variant="outline" onClick={onCheck}>Check</Button></div></Card><div className="grid gap-5 lg:grid-cols-2"><Card className="bg-white p-5 dark:bg-white/5"><Text variant="headline">Token details</Text><dl className="mt-4 space-y-3 text-[13px]"><Row label="Address" value={shortAddress(token.address)} /><Row label="Variant" value={token.variant} /><Row label="Decimals" value={String(token.decimals)} /><Row label="Total supply" value={formatAmount(token.supply, token.decimals)} /><Row label="Supply cap" value={token.cap === MAX_SUPPLY_CAP ? 'Unlimited' : formatAmount(token.cap, token.decimals)} /></dl><Link href={`${VIBENET_EXPLORER_PATH}/address/${token.address}`} className="mt-5 inline-block text-[12px] text-base-blue hover:underline">View on Explorer ↗</Link></Card><Card className="bg-white p-5 dark:bg-white/5"><div className="flex items-start justify-between gap-3"><div><Text variant="headline">Read from contract</Text><Text variant="footnote" tone="muted">Raw reads used by this viewer.</Text></div><CopyPromptButton prompt={READ_POLICY_PROMPT} module="policy" /></div><div className="mt-4 space-y-2 font-mono text-[12px] text-bds-gray-60 dark:text-bds-gray-40">{['factory.isB20(address)', 'token.policyId(scope)', 'registry.policyExists(id)', 'registry.policyAdmin(id)', 'registry.isAuthorized(id, account)'].map((item) => <div key={item} className="flex items-center justify-between rounded-lg border border-bds-gray-10 px-3 py-2 dark:border-white/10"><span>{item}</span><span className="text-bds-green-60">Read</span></div>)}</div></Card></div></> : null}</div>;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same problem here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in ad5a00c — this PolicyModule return is now multi-line/readable.

Comment thread app/demos/b20/B20Demo.tsx Outdated
} catch (error) { setError(walletErrorMessage(error)); }
};

if (token && tokenAccess === 'sample') return <div className="flex flex-col gap-5"><ModuleHeading icon="▤" title="Memos" description="View bytes32 memos attached to B20 token operations." action={<CopyPromptButton prompt={READ_MEMO_PROMPT} module="memos" />} /><Card className="bg-white p-5 dark:bg-white/5"><div className="flex flex-wrap items-start justify-between gap-3"><div><span className="rounded-full bg-bds-blue-0 px-2 py-1 text-[11px] text-base-blue dark:bg-bds-blue-100/40 dark:text-bds-blue-20">Sample transaction</span><Text className="mt-3" variant="headline">Transfer with memo</Text><Text variant="footnote" tone="muted">A real transaction on the sample token, decoded by the Vibenet explorer.</Text></div><Link href={`${VIBENET_EXPLORER_PATH}/tx/${SAMPLE_MEMO_TX}`} className="text-[12px] text-base-blue hover:underline">View transaction ↗</Link></div><dl className="mt-5 grid gap-3 rounded-xl border border-bds-gray-10 p-4 text-[13px] sm:grid-cols-2 dark:border-white/10"><div><dt className="text-[11px] text-bds-gray-50">Operation</dt><dd className="mt-1 font-mono">transferWithMemo</dd></div><div><dt className="text-[11px] text-bds-gray-50">Amount</dt><dd className="mt-1">0.001 {token.symbol}</dd></div><div><dt className="text-[11px] text-bds-gray-50">Memo</dt><dd className="mt-1 font-medium">sending test</dd></div><div><dt className="text-[11px] text-bds-gray-50">Encoding</dt><dd className="mt-1 font-mono text-[11px]">bytes32</dd></div></dl><div className="mt-5 flex flex-wrap items-center justify-between gap-3 border-t border-bds-gray-10 pt-4 dark:border-white/10"><p className="text-[12px] text-bds-gray-50">Deploy your own token to create memo transactions.</p><Button size="sm" onClick={onDeploy}>Create your own token</Button></div></Card><MemoHistory address={token.address} decimals={token.decimals} symbol={token.symbol} /></div>;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in ad5a00c — MemoModule returns are now multi-line.

Comment thread app/demos/b20/B20Demo.tsx Outdated
} catch (error) { setError(walletErrorMessage(error)); }
};

if (token && tokenAccess === 'sample') return <div className="flex flex-col gap-5"><ModuleHeading icon="▤" title="Memos" description="View bytes32 memos attached to B20 token operations." action={<CopyPromptButton prompt={READ_MEMO_PROMPT} module="memos" />} /><Card className="bg-white p-5 dark:bg-white/5"><div className="flex flex-wrap items-start justify-between gap-3"><div><span className="rounded-full bg-bds-blue-0 px-2 py-1 text-[11px] text-base-blue dark:bg-bds-blue-100/40 dark:text-bds-blue-20">Sample transaction</span><Text className="mt-3" variant="headline">Transfer with memo</Text><Text variant="footnote" tone="muted">A real transaction on the sample token, decoded by the Vibenet explorer.</Text></div><Link href={`${VIBENET_EXPLORER_PATH}/tx/${SAMPLE_MEMO_TX}`} className="text-[12px] text-base-blue hover:underline">View transaction ↗</Link></div><dl className="mt-5 grid gap-3 rounded-xl border border-bds-gray-10 p-4 text-[13px] sm:grid-cols-2 dark:border-white/10"><div><dt className="text-[11px] text-bds-gray-50">Operation</dt><dd className="mt-1 font-mono">transferWithMemo</dd></div><div><dt className="text-[11px] text-bds-gray-50">Amount</dt><dd className="mt-1">0.001 {token.symbol}</dd></div><div><dt className="text-[11px] text-bds-gray-50">Memo</dt><dd className="mt-1 font-medium">sending test</dd></div><div><dt className="text-[11px] text-bds-gray-50">Encoding</dt><dd className="mt-1 font-mono text-[11px]">bytes32</dd></div></dl><div className="mt-5 flex flex-wrap items-center justify-between gap-3 border-t border-bds-gray-10 pt-4 dark:border-white/10"><p className="text-[12px] text-bds-gray-50">Deploy your own token to create memo transactions.</p><Button size="sm" onClick={onDeploy}>Create your own token</Button></div></Card><MemoHistory address={token.address} decimals={token.decimals} symbol={token.symbol} /></div>;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in ad5a00c — same reformat applied here.

Comment thread app/demos/b20/B20Demo.tsx Outdated
return <div className="flex flex-col gap-5"><ModuleHeading icon="▤" title="Memos" description="Attach an indexed bytes32 memo to B20 transfers, mints, and burns." action={<CopyPromptButton prompt={READ_MEMO_PROMPT} module="memos" />} /><Card className="bg-white p-5 dark:bg-white/5">{!token ? <EmptyToken /> : <><div className="flex flex-wrap gap-2">{(['transfer', 'transferFrom', 'mint', 'burn'] as const).map((item) => <button key={item} type="button" onClick={() => setKind(item)} className={cn('rounded-full px-3 py-1.5 text-[12px]', kind === item ? 'bg-base-blue text-white' : 'bg-bds-gray-5 text-bds-gray-60 dark:bg-white/10 dark:text-bds-gray-30')}>{item}</button>)}</div><div className="mt-5 grid gap-4 md:grid-cols-2">{kind === 'transferFrom' ? <Field label="From"><Input value={from} onChange={(e) => setFrom(e.target.value)} placeholder="0x…" /></Field> : null}{kind !== 'burn' ? <Field label={kind === 'mint' ? 'Recipient' : 'To'}><Input value={to} onChange={(e) => setTo(e.target.value)} placeholder="0x…" /></Field> : null}<Field label={`Amount (${token.symbol})`}><Input value={value} onChange={(e) => setValue(e.target.value)} placeholder="0.00" inputMode="decimal" /></Field><Field label="Memo" help={B20_HELP.memo}><Input value={memo} onChange={(e) => setMemo(e.target.value)} placeholder="Text up to 32 bytes, or 0x bytes32" /></Field></div><p className="mt-3 font-mono text-[11px] text-bds-gray-50">{memo ? (() => { try { return memoToBytes32(memo); } catch { return 'Memo is too long'; } })() : 'Memo preview appears here'}</p><ErrorNote message={error} /><Button className="mt-5" onClick={() => void submit()} disabled={!!busy}>{busy ? 'Waiting for wallet…' : `Submit ${kind} with memo`}</Button></>}</Card>{token ? <MemoHistory address={token.address} decimals={token.decimals} symbol={token.symbol} /> : null}</div>;
}

function SampleAnnouncementViewer({ onDeploy }: { onDeploy: () => void }) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

move this to a helper file or config file

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in ad5a00c — moved the sample announcement mock data to app/demos/b20/lib/samples.ts (SAMPLE_ANNOUNCEMENTS) and import it here.

Comment thread app/demos/b20/B20Demo.tsx Outdated
// confirmation — the Activity log alone was too easy to miss.
const [published, setPublished] = useState<{ id: string; summary: string; hash: Hex } | null>(null);
const [error, setError] = useState<string | null>(null);
const loadTemplate = useCallback(() => { setAnnouncementType('multiplier'); setId(`demo-split-${Date.now().toString(36)}`); setDescription('2:1 forward split demonstration'); setUri('https://example.com/disclosures/demo-split'); setMultiplier('2'); setEffectiveAt(futureDatetimeLocal()); }, []);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fix the syntax here, there should be a new line after a semi colon.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in ad5a00c — chained declarations and multi-statement bodies are now one statement per line throughout the file.

- Break the dense single-line JSX returns and multi-statement lines into
  readable multi-line form (ran prettier over the file; one statement per line).
- Move the sample announcement mock data out of B20Demo.tsx into lib/samples.ts.

Formatting-only for the component logic; typecheck, lint, build, and the b20
tests pass, and Policy/Deploy render unchanged (verified in-browser).

Co-Authored-By: Claude <noreply@anthropic.com>
The demo was one 2k-line file with every module and primitive inline. Extract
them so each concern lives in its own file and B20Demo.tsx is just the
orchestrator (state + wallet/inspect/send wiring + layout), 434 lines down
from ~2050.

New files:
- lib/types.ts       — Module, TokenAccess, RecentToken, TokenInfo, ActivityItem, CreatedToken
- lib/constants.ts   — shared viem client, chain id, sample/config constants, MODULES
- lib/recent.ts      — readRecent/writeRecent localStorage helpers
- components/primitives.tsx — Input, Field, ModuleHeading, EmptyToken, ErrorNote, Row
- components/Activity.tsx    — the decoded-events log
- components/PolicyModule.tsx
- components/MemoModule.tsx
- components/AnnouncementModule.tsx (+ SampleAnnouncementViewer)
- components/DeployModule.tsx (+ CreatedView, confetti)

MemoHistory now reuses the shared lib/constants client instead of creating its
own. Behavior unchanged — formatting-only moves; typecheck, lint, build, and 73
tests pass, and the Policy/Memos/Announcements/Deploy tabs render identically
(verified in-browser).

Co-Authored-By: Claude <noreply@anthropic.com>
@montycheese montycheese changed the title [preview] B20 docs demo — Vercel deployment stub B20 Playground demo (/demos/b20) + explorer announcement/memo decoding Aug 7, 2026
Register base-0005 (B20) in vibenetAvailability with demo: '/demos/b20' so the
changelog detail page surfaces the "Test Now" card, mirroring how eip-8130
links to /demos/account. Left unfeatured — the link belongs on the changelog
entry, not the featured-changes rail.

Typed vibenetAvailability as VibenetAvailability[] (dropping `as const`) so
entries can omit optional fields. Extended the upgrades test for the new demo
link.

Co-Authored-By: Claude <noreply@anthropic.com>
soheimam and others added 2 commits August 7, 2026 18:47
Empty commit to nudge a fresh Vercel preview build for b20-docs-vercel-preview.
No source changes.

Co-Authored-By: Claude <noreply@anthropic.com>
The Advanced policy settings inputs (Transfer sender, etc.) accept a Policy
Registry rule ID (uint64) that updatePolicy references — but the labels +
numeric placeholders read like they want a wallet address. Relabel each field
"… rule", attach the policyId info tooltip, and spell out in the section intro
that these take a rule ID number from the Policy Registry (which holds the
allowed addresses), not an address. No behavior change.

Co-Authored-By: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants