Lab kit

The shared pieces for the Lab pages. Everything here is simulated except the figures marked Live, which follow real Base blocks.

Brackets

Four 10 px L corners, 1.5 px, ink at 50%. The frame for stages and cards; the parent is position: relative.

Content sits inside the corners.

CodePane · single file

Server-highlighted (paper in light, espresso in dark). The verify range stays lit; the button pulses the pay range.

checkout.ts
TS21 lines
import { getPaymentStatus, pay } from '@base-org/account'const MERCHANT = '0xYourMerchantAddress'// In the browser: one call opens Base Pay. No account, no card form.export async function checkout(orderId: string) {  const payment = await pay({ amount: '3.50', to: MERCHANT, testnet: false })  await fetch('/api/orders/paid', {    method: 'POST', headers: { 'content-type': 'application/json' },    body: JSON.stringify({ orderId, paymentId: payment.id }),  })}// On the server: check the payment against your own order record, then fulfil.// Make fulfil() idempotent, so one payment id never ships twice.export async function confirm(paymentId: string, order: { amount: string; fulfil: () => Promise<void> }) {  const status = await getPaymentStatus({    id: paymentId,    expectedPayment: { amount: order.amount, recipient: MERCHANT },    testnet: false,  })  if (status.status === 'completed') await order.fulfil()  return status.status}

CodePane · tabs

Three files, one pane. Tabs are a tablist: Left, Right, Home and End move between them. Each file keeps its own lit range.

17 lines
import { createPublicClient, createWalletClient, custom, erc20Abi, http, parseUnits, type Address } from 'viem'import { base } from 'viem/chains'const USDC = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913' // native USDC on Base, 6 decimalsconst wallet = createWalletClient({ chain: base, transport: custom(window.ethereum!) })const client = createPublicClient({ chain: base, transport: http() })export async function send(to: Address, dollars: string) {  const [account] = await wallet.requestAddresses()  // Dry-run the transfer first: it fails here, not onchain, if the balance is short.  const { request } = await client.simulateContract({    account, address: USDC, abi: erc20Abi, functionName: 'transfer',    args: [to, parseUnits(dollars, 6)],  })  // The wallet asks the sender to confirm, then broadcasts.  const hash = await wallet.writeContract(request)  // Preconfirmed in about 200 ms, included in a 2 s block.  return client.waitForTransactionReceipt({ hash })}

CodePane · collapse

swap.ts is 38 lines, so it opens with 16 and a footer. A lit range below the fold opens the block itself.

swap.ts
TS33 lines
import { createPublicClient, createWalletClient, custom, erc20Abi, http, parseAbi, parseUnits } from 'viem'import { base } from 'viem/chains'const USDC = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'const WETH = '0x4200000000000000000000000000000000000006'const QUOTER = '0x3d4e44Eb1374240CE5F1B871ab261CD16335B76a' // Uniswap v3 QuoterV2const ROUTER = '0x2626664c2603336E57B271c5C0b26F421741e481' // Uniswap v3 SwapRouter02const quoterAbi = parseAbi([  'function quoteExactInputSingle((address tokenIn, address tokenOut, uint256 amountIn, uint24 fee, uint160 sqrtPriceLimitX96) params) returns (uint256 amountOut, uint160 sqrtPriceX96After, uint32 initializedTicksCrossed, uint256 gasEstimate)',])const routerAbi = parseAbi([  'function exactInputSingle((address tokenIn, address tokenOut, uint24 fee, address recipient, uint256 amountIn, uint256 amountOutMinimum, uint160 sqrtPriceLimitX96) params) payable returns (uint256 amountOut)',])// Connect a wallet on Base and pick an amount: 1,000 USDC, which has 6 decimals.const wallet = createWalletClient({ chain: base, transport: custom(window.ethereum!) })const client = createPublicClient({ chain: base, transport: http() })const [account] = await wallet.requestAddresses()const amountIn = parseUnits('1000', 6)// Ask the 0.05% pool what that buys right now. The Quoter simulates the swap; nothing is sent.const { result: [quoted] } = await client.simulateContract({  address: QUOTER, abi: quoterAbi, functionName: 'quoteExactInputSingle',  args: [{ tokenIn: USDC, tokenOut: WETH, amountIn, fee: 500, sqrtPriceLimitX96: 0n }],})// Let the router pull the USDC.const approval = await wallet.writeContract({  account, address: USDC, abi: erc20Abi, functionName: 'approve', args: [ROUTER, amountIn],})await client.waitForTransactionReceipt({ hash: approval })// Swap, and refuse to fill for less than 99.5% of the quote.const swap = await wallet.writeContract({  account, address: ROUTER, abi: routerAbi, functionName: 'exactInputSingle',  args: [{ tokenIn: USDC, tokenOut: WETH, fee: 500, recipient: account, amountIn, amountOutMinimum: quoted * 9950n / 10000n, sqrtPriceLimitX96: 0n }],})await client.waitForTransactionReceipt({ hash: swap })
deposit.ts
TS26 lines
import { createPublicClient, createWalletClient, custom, erc20Abi, http, parseAbi, parseUnits } from 'viem'import { base } from 'viem/chains'const USDC = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'const VAULT = '0xBEEFE94c8aD530842bfE7d8B397938fFc1cb83b2' // Steakhouse Prime USDC, on Morphoconst vaultAbi = parseAbi([  'function deposit(uint256 assets, address receiver) returns (uint256 shares)',  'function balanceOf(address owner) view returns (uint256)',  'function convertToAssets(uint256 shares) view returns (uint256)',])// Connect a wallet on Base and pick an amount: 100 USDC, which has 6 decimals.const wallet = createWalletClient({ chain: base, transport: custom(window.ethereum!) })const client = createPublicClient({ chain: base, transport: http() })const [account] = await wallet.requestAddresses()const amount = parseUnits('100', 6)// Let the vault pull the USDC, then deposit it in exchange for vault shares.const approval = await wallet.writeContract({  account, address: USDC, abi: erc20Abi, functionName: 'approve', args: [VAULT, amount],})await client.waitForTransactionReceipt({ hash: approval })const deposit = await wallet.writeContract({  account, address: VAULT, abi: vaultAbi, functionName: 'deposit', args: [amount, account],})await client.waitForTransactionReceipt({ hash: deposit })// The shares grow in value every block. This is what they're worth right now.const shares = await client.readContract({ address: VAULT, abi: vaultAbi, functionName: 'balanceOf', args: [account] })const value = await client.readContract({ address: VAULT, abi: vaultAbi, functionName: 'convertToAssets', args: [shares] })

CodeFrame · plain lines (JSON)

Built on the client, not by Shiki, through the same chrome. The passing checks are lit.

validity.json
JSON10 lines
{  "tx": "0x3b1c…9e04",  "checks": {    "signature": true,    "nonce": true,    "balance": true,    "gasLimit": false  },  "valid": false}

CodePane · no gutter, no wrap

A one-line shell command, as a static block.

install.sh
BASH1 line
npm install viem @base-org/account

Flourish · FallingPixel and Stamp

A pixel that falls into a changed figure, and a mono stamp that rises under it. Parent is position: relative.

$1,000.00

Readout

Label + tags, value + unit, as-of. minCh reserves the digits' width so an odometer never shifts layout. hero seats it in the page's one well.

Liquidity within ±2%Sim
$1.31M
as of block 52,511,750
Vault APYSim
6.24%
as of block 52,511,750
Base blockLive
Waiting
as of the next block
ReceiveSim
1,000.00USDC
as of block 52,511,750

Panel · KV rows

A titled group with one provenance footer; KV rows with dotted leaders, read together.

Fee receipt

Sim
Price impact
0.06%
Pool fee
$23.00
Price after
+0.13%
Gas
$0.0112
AccountSim
Deposited
$10,000.00
Earned
$36.18
Withdrawn
$2,500.00
Balance
$7,536.18

Stat group

2–4 equal figures side by side; auto-fit minmax(140px, 1fr).

A preconfirmation every
—ms
measuring
A block every
—s
measuring
Preconfirmations per block
10
fixed by the sequencer

Ledger

Rows that are appended or compared. The newest row carries the rail; past 8 rows the body scrolls under a top fade.

Swap receipt

Sim
WhoBeforeAfterChange
me · USDC10,000.009,900.00−100.00
pool · USDC25,899.1825,999.18+100.00
me · WETH4.205.19+0.99
pool · WETH1,204.111,203.12−0.99

Interest per block

Sim
rows also land with each real block
BlockInterest
52,511,751+$0.0000012
52,511,750+$0.0000012
52,511,749+$0.0000012
52,511,748+$0.0000012
52,511,747+$0.0000012
52,511,746+$0.0000012
52,511,745+$0.0000012
52,511,744+$0.0000012
52,511,743+$0.0000012
52,511,742+$0.0000012
52,511,741+$0.0000012
52,511,740+$0.0000012
Balances simulated · blocks live from Base

Unavailable

The pending well: hatched texture, one line, WAITING — never LIVE.

Pool

Waiting

Pool unavailable at block 52,511,752 · retrying on the next block

Instrument next to an article card

Editorial: serif prose, hover ripple. Instrument: mono, hairlines, tabular numbers, still.

Pool

Sim
Liquidity within ±2%Sim
$1.31M
as of block 52,511,750

LiveTag

Live reads Base; Sim is simulated; Waiting marks an unavailable or empty well and never reads as Live.

LiveSimWaiting

Odometer

Changed digits roll 8 px over 300 ms. Cells key by digit place, so the value can grow without jitter.

SimRandom cents
$1,234.56
Live+$0.000037 per Base block · 0 blocks
$1,000.000000

PhoneFrame

Solid bezel, island, status row, home indicator. By default the screen is fixed and scrolls; fit lets the device grow with its content.

Default · shortFixed screen, content fits, indicator shown
Ledger
Balance · SIM
$2,418.06
  • Coffee−$4.20
  • Transfer from Ana+$120.00
  • Groceries−$38.15
Default · scrollsFixed screen, internal scroll; indicator fades at the end
Ledger
Balance · SIM
$2,418.06
  • Coffee−$4.20
  • Transfer from Ana+$120.00
  • Groceries−$38.15
  • Rent share−$640.00
  • Refund+$12.99
  • Train pass−$54.00
  • Lunch−$11.40
  • Payout+$310.00
  • Books−$22.80
  • Gym−$29.00
  • Transfer to Sam−$45.00
  • Interest+$0.84
  • Phone bill−$35.00
  • Market−$17.60
fit · shortGrows with content; keeps the full screen at desktop
Savings
Balance · SIM
$2,418.06
  • Coffee−$4.20
  • Transfer from Ana+$120.00
fit · tallGrows past 740 px, no internal scroll
Savings
Balance · SIM
$2,418.06
  • Coffee−$4.20
  • Transfer from Ana+$120.00
  • Groceries−$38.15
  • Rent share−$640.00
  • Refund+$12.99
  • Train pass−$54.00
  • Lunch−$11.40
  • Payout+$310.00
  • Books−$22.80
  • Gym−$29.00
  • Transfer to Sam−$45.00
  • Interest+$0.84
  • Phone bill−$35.00
  • Market−$17.60
Default app name · emptyNo props
Your app

Scrubber · log $100 – $1,000,000

A native range under a drawn track. Keyboard steps are a share of the track, so a log scale moves in log steps.

Deposit

Segmented

A tablist with a sliding indicator; selection follows focus. The badge is a 5 px square after a tab's label: something changed there.

Selected: day

Ink for a change the reader made, blue for one the chain made. The selected tab never shows one.

Flash

A tint behind the changed value, fading over 600 ms. Ink for what the reader did, --chain for what the chain did. Replay with the button.

User · inkSim
$1,250.00
Chain · --chainSim
52,511,744

Delta chip

The signed change beside a value: + and a real minus sign (U+2212). Enters, holds 1.2 s, fades. Never for a zero change or a block tick.

UpSim
$37,000.00
DownSim
4.18%
Zero (never shown)Sim
$1,000.00

LabStage

One action, three views: app, code, chain. Signals flash each pane where a change landed, 60 ms apart; on mobile a hidden pane's tab gains a badge.

App
Savings

Balance

$1,000.00

Each tap moves the balance and lands in a block. Next block changes the chain only.

Code
import { parseUnits } from 'viem'

// USDC has 6 decimals
const assets = parseUnits('1000.00', 6)
Try the app to see what runs
Chain
BlockSim
52,511,744
Vault balanceSim
$1,000.00
as of block 52,511,744
Try the app to see what runs

Signals: app 0 · code 0 · chain 0

Tray

Prose behind a disclosure. The one sanctioned height animation, out of its trigger.

Base · Block — · —