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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,6 @@ src/data/generated/*
!src/data/generated/.keep

.wrangler
.claude
.claude
# Local Netlify folder
.netlify
45 changes: 45 additions & 0 deletions design-patterns.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Design Patterns

This document captures high-signal UI conventions for the docs site so brand and implementation choices stay consistent.

## Core Tokens

- Use CSS custom properties in `src/styles.css` for global tokens.
- Prefer semantic token names (`--ens-*`) over hardcoded values in component styles.

## Buttons

- All button controls use a 4px corner radius.
- Source of truth: `--ens-radius-button` in `src/styles.css`.
- Applied to: `button`, `input[type='button']`, `input[type='submit']`, and `input[type='reset']`.

## Tables

- All documentation tables use `rounded-md` corners (6px).
- Source of truth: `--ens-radius-table` in `src/styles.css`.
- Applied to Vocs table wrappers/content to keep corners visibly clipped.

## Typography

- Primary sans serif: `ABCMonumentGrotesk`.
- Serif: `ABCMarist`.
- Monospace: `ABCMonumentGroteskMono` and `ABCMonumentGroteskSemiMono`.
- Keep `font-display: swap` for self-hosted font loading behavior.

## Color System

- Use `--ens-*` palette tokens and semantic aliases (`--ens-background`, `--ens-text`, etc.).
- Define both light (`:root`) and dark (`.dark`) token values in `src/styles.css`.
- Figma source of truth: [`ENS Core` color tokens](https://www.figma.com/design/2sH9SoYCbfma5clol8dFP4/%F0%9F%93%93--ENS-Core?node-id=514-12&p=f&t=TTwQnH0qKnppCExL-0) (node `514:12`).

### Figma Palette Extract (current)

- **Lapis**: `100 #DBF0F8`, `300 #80C4E0`, `400 #39B4EA`, `500 #0082BB`, `900 #02293B`
- **Quartz**: `100 #EEEDED`, `300 #C7C6C4`, `400 #737373`, `500 #595755`, `900 #191919`
- **Peridot**: `100 #D1EEDF`, `300 #65D388`, `400 #1CBF46`, `500 #007C20`, `900 #033010`
- **Garnet**: `100 #FEEAF0`, `300 #FFB0D0`, `400 #F569AB`, `500 #E72A96`, `900 #5A0024`
- **Citrine**: `100 #F8F7E2`, `300 #E1B77E`, `400 #E7A259`, `500 #984D1B`, `900 #441B03`

### Current Mapping Rule

- `--ens-blue-primary` maps to **Lapis 500** (`#0082BB`) and is the default accent blue used by Vocs theme variables.
143 changes: 143 additions & 0 deletions scripts/ensv2-deployments.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
import fs from 'fs/promises'
import path from 'path'
import { Hex } from 'viem'

// ENSv2 deployment addresses, sourced from the auto-generated addresses file
// in contracts-v2 (`bun run docs:addresses` output).
//
// Pinned to a commit so the build cannot break if the branch moves. To pick up
// a new deployment set, bump the SHA to a commit whose
// `contracts/docs/addresses/sepolia.md` and `contracts/deployments/sepolia/`
// describe the same deployment, then delete
// src/data/generated/ensv2-deployments.json to re-fetch.
const CONTRACTS_V2_COMMIT = '97a57293f3b4279d94b571e678edb53ce62638f4'

const ADDRESSES_MD_RAW = `https://raw.githubusercontent.com/ensdomains/contracts-v2/${CONTRACTS_V2_COMMIT}/contracts/docs/addresses/sepolia.md`
const ARTIFACTS_RAW = `https://raw.githubusercontent.com/ensdomains/contracts-v2/${CONTRACTS_V2_COMMIT}/contracts/deployments/sepolia`
const ARTIFACTS_BLOB = `https://github.com/ensdomains/contracts-v2/blob/${CONTRACTS_V2_COMMIT}/contracts/deployments/sepolia`
const CONTRACTS_BLOB = `https://github.com/ensdomains/contracts-v2/blob/${CONTRACTS_V2_COMMIT}/contracts`

type ENSv2Deployment = {
name: string
address?: Hex
sourceUrl?: string
abiUrl?: string
}

export type ENSv2DeploymentsByChain = {
name: string // Sepolia, etc.
slug: string // sepolia, etc.
contracts: ENSv2Deployment[]
}

// Source links for contracts whose implementation is NOT a plain in-repo file
// (e.g. it lives in a git submodule, which has no blob URL at our pin). All
// other source links are derived from the deployment artifact's
// `metadata.settings.compilationTarget`.
const SOURCE_URL_OVERRIDES: Record<string, string> = {
VerifiableFactory:
'https://github.com/ensdomains/verifiable-factory/blob/main/src/VerifiableFactory.sol',
}

// Derive the source blob URL from an artifact's compilation target, e.g.
// "project/src/hca/StandaloneSingleOwnerHCA.sol" -> contracts/src/... at the
// pinned commit. Submodule paths (project/lib/...) have no blob URL; skip.
function sourceUrlFromArtifact(artifact: {
metadata?: string | { settings?: { compilationTarget?: Record<string, string> } }
}): string | undefined {
let metadata = artifact.metadata
if (typeof metadata === 'string') {
try {
metadata = JSON.parse(metadata)
} catch {
return undefined
}
}
const target = (metadata as { settings?: { compilationTarget?: Record<string, string> } })
?.settings?.compilationTarget
const sourcePath = target ? Object.keys(target)[0] : undefined
if (!sourcePath?.startsWith('project/') || sourcePath.startsWith('project/lib/'))
return undefined
return `${CONTRACTS_BLOB}/${sourcePath.slice('project/'.length)}`
}

// Parse `| Name | [0x...](explorer link) |` rows from the addresses markdown.
function parseAddressesMd(source: string): Map<string, Hex> {
const rows = new Map<string, Hex>()
const entry = /^\|\s*([A-Za-z0-9]+)\s*\|\s*\[(0x[0-9a-fA-F]{40})\]/gm
for (const match of source.matchAll(entry)) {
rows.set(match[1], match[2] as Hex)
}
if (rows.size === 0)
throw new Error(
'ensv2-deployments: no address rows parsed from sepolia.md, format changed?'
)
return rows
}

// Generate a JSON file with ENSv2 contract deployment info from contracts-v2.
// Only runs once; delete the generated file to re-fetch.
export async function ensv2Deployments() {
const outFile = path.join(
__dirname,
'..',
'src/data/generated/ensv2-deployments.json'
)

const alreadyExists = await fs
.access(outFile)
.then(() => true)
.catch(() => false)
if (alreadyExists) {
return
}

console.log('Fetching ENSv2 deployments (contracts-v2)')

const source = await fetch(ADDRESSES_MD_RAW).then((res) => {
if (!res.ok)
throw new Error(`ensv2-deployments: fetch failed (${res.status})`)
return res.text()
})
const rows = parseAddressesMd(source)

const contracts: ENSv2Deployment[] = await Promise.all(
[...rows.entries()].map(async ([name, address]) => {
const contract: ENSv2Deployment = {
name,
address,
sourceUrl: SOURCE_URL_OVERRIDES[name],
}
// Guard: the deployment artifact must exist and carry the same address
// as the addresses file. A mismatch means the two describe different
// deployment sets and the pin needs attention.
const res = await fetch(`${ARTIFACTS_RAW}/${name}.json`)
if (res.ok) {
const artifact = (await res.json()) as {
address?: string
metadata?: string
}
if (
artifact.address &&
artifact.address.toLowerCase() !== address.toLowerCase()
)
throw new Error(
`ensv2-deployments: address mismatch for ${name}: addresses file ${address} vs artifact ${artifact.address}`
)
contract.abiUrl = `${ARTIFACTS_BLOB}/${name}.json`
contract.sourceUrl ??= sourceUrlFromArtifact(artifact)
} else {
console.warn(
`ensv2-deployments: no artifact for ${name} (${res.status}), skipping ABI link`
)
}
return contract
})
)

const result: ENSv2DeploymentsByChain[] = [
{ name: 'Sepolia', slug: 'sepolia', contracts },
]

await fs.writeFile(outFile, JSON.stringify(result, null, 2))
}
3 changes: 2 additions & 1 deletion scripts/run-plugins.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { daoProposalsSidebar } from './dao-proposals'
import { deployments } from './deployments'
import { ensips } from './ensips'
import { ensv2Deployments } from './ensv2-deployments'

const plugins = [ensips, deployments, daoProposalsSidebar]
const plugins = [ensips, deployments, ensv2Deployments, daoProposalsSidebar]

for (const plugin of plugins) {
await plugin()
Expand Down
2 changes: 1 addition & 1 deletion src/components/AddressRecords.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const AddressRecords = ({ name, coinTypes }: UseEnsAddressesProps) => {
src={avatar || '/img/fallback-avatar.svg'}
className="h-8 w-8 rounded"
/>
<span className="font-semibold">{name}</span>
<span className="font-semibold font-semi-mono">{name}</span>
</div>

<Table
Expand Down
6 changes: 3 additions & 3 deletions src/components/EnsProfile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,14 @@ function Profile({
const { data: avatar } = useEnsAvatar({ name: name || undefined, chainId: 1 })

return (
<div className="border-grey-light flex w-fit min-w-fit items-center justify-center rounded-full border bg-white p-1 pr-6 shadow-sm dark:bg-black">
<div className="border-grey-light flex w-fit min-w-fit items-center justify-center rounded-lg border bg-white p-1 pr-6 dark:bg-black">
<div className="flex items-center gap-2">
<img
src={avatar || '/img/fallback-avatar.svg'}
className="border-grey-light h-10 w-10 rounded-full border"
className="border-grey-light h-10 w-10 rounded-sm border-0.5"
/>
<div className="flex flex-col gap-0.5 leading-none">
<span className="font-semibold">{name || 'No name'}</span>
<span className="font-semibold font-semi-mono">{name || 'No name'}</span>

{!hideAddress && (
<span className={cn('text-grey text-xs')}>
Expand Down
2 changes: 1 addition & 1 deletion src/components/TextRecords.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export const TextRecords = ({ name, keys }: UseEnsTextsProps) => {
src={avatar || '/img/fallback-avatar.svg'}
className="h-8 w-8 rounded"
/>
<span className="font-semibold">{name}</span>
<span className="font-semibold font-semi-mono">{name}</span>
</div>

<Table
Expand Down
Loading