packages/web– Astro static site (landing page + whitepaper + /validators), deployed to Cloudflare Pages viamasterpackages/pitch– Astro static site (pitch.snowside.network), separate Cloudflare Pages project. noindex, nofollow — no links from web to pitch.packages/canvas– Astro static site for the Snowside Lean Canvas (PNG viewer + PDF download). Subdomain:canvas.snowside.network. Separate Cloudflare Pages project (snowside-canvas). Indexable (no noindex).packages/docs– Astro Starlight technical documentation (docs.snowside.network)packages/explorer– EVM block explorer (Astro static site + Cloudflare Pages Functions). Contains Header (network switcher, search), Footer, Etherscan-style stats cards. Subdomains:explorer.snowside.network(mainnet),explorer-testnet.snowside.network,explorer-signet.snowside.network.packages/bridge– Astro static site for BIP-300/301 deposits and withdrawals. Subdomain:bridge.snowside.network.packages/api– Cloudflare Worker (Hono + Chanfana) serving OpenAPI at/v1and proxying to Drynet 4 Esplora.packages/tour– Zero-dependency static interactive tour (no framework, no bundler). Subdomain:tour.snowside.network. Separate Cloudflare Pages project (snowside-tour). Indexable.go/subnet-evm– Subnet-EVM fork with BMM coordination precompile (Go)rust/bmm-bidder– BMM bidder and settlement monitor (Rust)contracts/– Solidity smart contracts (Foundry)
The Snowside monorepo uses language-specific top-level directories:
packages/ — JavaScript/TypeScript (pnpm workspace) web/ Main website (Astro) pitch/ Grant pitch page (Astro) canvas/ Lean Canvas viewer (Astro static, PNG + PDF download) docs/ Documentation site (Astro + Starlight) explorer/ EVM block explorer (Astro static + CF Pages Functions) bridge/ Bridge UI (Astro + Tailwind v4) api/ Cloudflare Worker (Hono + Chanfana OpenAPI) tour/ Interactive slideshow (zero-dependency static build)
go/ — Go packages subnet-evm/ Subnet-EVM fork with BMM coordination precompile
rust/ — Rust packages bmm-bidder/ BMM bidder and settlement monitor
contracts/ — Solidity smart contracts (Foundry) src/ interfaces/ Solidity interfaces for precompiles peg/ BTC peg contract (deposits/withdrawals) fees/ Contract Fee distribution test/ Foundry tests script/ Deployment scripts
docs/ — Documentation and handoff notes
Read these files at the start of every session:
AGENTS.md— this file (project rules, conventions, current state)docs/HANDOFF.md— last session's summary, known issues, and next steps
Push to production often. After every meaningful change, build, commit from the repo root, and push to master.
Never leave uncommitted work sitting locally at the end of a session.
NEVER make the user ask for CLI commands. ALWAYS output commands in a single terminal-ready code block.
CRITICAL: If markdown content inside a heredoc contains triple backticks, they will conflict with the outer code block. Use 4-space indented code blocks instead of fenced code blocks inside heredoc content.
CRITICAL: Multi-line paste blocks frequently garble in terminals. Use single-line commands or heredocs with 'EOF' delimiters. Always verify with wc -l and tail -n 15 after heredoc writes.
- Web build: cd packages/web && pnpm build # Astro static, output dist/
- Pitch build: cd packages/pitch && pnpm build # Astro static, output dist/
- Canvas build: cd packages/canvas && pnpm build # Astro static, output dist/
- Docs build: cd packages/docs && pnpm build # Astro Starlight, output dist/
- Explorer build: cd packages/explorer && pnpm build # Astro static, output dist/
- Bridge build: cd packages/bridge && pnpm build # Astro static, output dist/
- Tour build: cd packages/tour && pnpm build # node scripts/build-tour.mjs, output dist/
- Root build: pnpm run build # runs web -> pitch -> canvas
- Dev web: pnpm run dev:web
- Dev pitch: pnpm run dev:pitch
- Dev canvas: pnpm run dev:canvas
- Dev docs: pnpm --filter packages-docs run dev
- Dev explorer: pnpm --filter packages-explorer run dev
- Production URLs: https://snowside.network (web), https://pitch.snowside.network (pitch), https://canvas.snowside.network (canvas), https://docs.snowside.network (docs), https://explorer.snowside.network (explorer mainnet)
- VPS: rpc.snowside.network (Ubuntu 24.04, Nginx reverse proxy)
- Nginx Config: /etc/nginx/sites-available/default on VPS
- Cloudflare DNS: rpc.snowside.network -> VPS IP
- Deployment Tool: Avalanche-CLI (requires letters only for blockchain names, no hyphens/underscores)
- Avalanche-CLI Version: Supports flags: --evm, --evm-chain-id, --evm-token, --proof-of-authority, --validator-manager-owner, --icm, --warp, --latest, --genesis, --force, --test-defaults, --production-defaults
| Precompile | Address | Config Key |
|---|---|---|
| ContractDeployerAllowList | 0x0200000000000000000000000000000000000000 | contractDeployerAllowListConfig |
| NativeMinter | 0x0200000000000000000000000000000000000001 | contractNativeMinterConfig |
| TxAllowList | 0x0200000000000000000000000000000000000002 | txAllowListConfig |
| FeeManager | 0x0200000000000000000000000000000000000003 | feeManagerConfig |
| RewardManager | 0x0200000000000000000000000000000000000004 | rewardManagerConfig |
Source files:
- NativeMinter: subnet-evm/precompile/contracts/nativeminter/module.go
- DeployerAllowList: subnet-evm/precompile/contracts/deployerallowlist/module.go
AllowList roles: 0x00 = none, 0x01 = enabled, 0x02 = admin, 0x03 = manager
To deploy an L1 non-interactively with custom precompiles (NativeMinter, ContractDeployerAllowList), use the genesis cloning approach:
-
Export the genesis from an existing, working L1 (e.g., testnet): cp ~/.avalanche-cli/subnets/SnowsideTestnet/genesis.json /tmp/base-genesis.json
-
Patch the genesis with Python (change chainId, add precompile configs): python3 -c 'import json; g=json.load(open("/tmp/base-genesis.json")); g["config"]["chainId"]=33416; g["config"]["contractNativeMinterConfig"]={"blockTimestamp":0,"adminAddresses":["0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"]}; g["config"]["contractDeployerAllowListConfig"]={"blockTimestamp":0,"adminAddresses":["0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC"]}; json.dump(g,open("/tmp/new-genesis.json","w"),indent=4); print("done")'
-
Create the blockchain non-interactively: avalanche blockchain create SnowsideSignet --evm --evm-token ECX --proof-of-authority --validator-manager-owner 0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC --icm --warp --latest --genesis /tmp/new-genesis.json --force
-
Deploy locally: avalanche blockchain deploy SnowsideSignet --local
CRITICAL: The --genesis flag conflicts with --evm-chain-id. The chain ID must be baked into the genesis JSON, not passed as a CLI flag.
CRITICAL: The --genesis flag also conflicts with --evm-defaults, --production-defaults, --test-defaults.
NOTE: ICM Messenger/Registry contracts may fail to deploy during blockchain deploy when using a cloned genesis. The L1, PoA, and precompiles will still work. Deploy ICM separately with avalanche icm deploy.
NOTE: blockchain deploy flags: -e (use ewoq key for local/devnet), NO --force flag (only blockchain create has it). Each L1 gets its own local Avalanche node on a separate port (mainnet: 9656, testnet: 9658, signet: 9654).
NOTE: The deploy command prompts for ICM Registry addresses of other L1s (cross-chain config). Can Ctrl+C to skip — L1 deployment is already complete. Relayer deployment can also be skipped.
NOTE: Genesis files are stored at ~/.avalanche-cli/subnets//genesis.json (NOT chain.json, which is only the chain config metadata).
Read allowlist status: cast calldata "readAllowList(address)" 0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC
Mint native tokens:
cast send --rpc-url $RPC --private-key
Deploy contract (if on DeployerAllowList): cast send --rpc-url $RPC --private-key $PK --create 0x60006000f3
-
SnowsideMainnet (Chain ID: 32904 / 0x8088) — DEPLOYED & VERIFIED Session 12
- Blockchain ID: 2WGjPQF6YcV3KN19d5x21Cj8VAvxrakA72Ke7RHtZJpQBJBkdV
- Subnet ID: No8zvE8ZFDQhY8t5u2qTLjprzCqab4cYoVfTjskkZMzM34jXZ
- Local RPC: http://127.0.0.1:9656/ext/bc/2WGjPQF6YcV3KN19d5x21Cj8VAvxrakA72Ke7RHtZJpQBJBkdV/rpc
- Public RPC: https://rpc.snowside.network/mainnet
- NodeID: NodeID-PGEHenyijV18FoaRZrJqveJWac7oqWorU
- Port: 9656
- Precompiles: Warp only
- ICM Status: Deployed (Messenger: 0x253b2784c75e510dD0fF1da844684a1aC0aa5fcf, Registry: 0xB8e71012d3F55D9EbbFf74376dE180702c1D8A6F)
- Relayer: Not deployed (skipped, can deploy with
avalanche interchain relayer deploy)
-
SnowsideTestnet (Chain ID: 33160 / 0x8188) — DEPLOYED & VERIFIED Session 12
- Blockchain ID: 2A45por6NN5o17NwKFTHTjyKhJobL8UPd92Sbi4ffaMfohRXRA
- Subnet ID: KByfMHbZ8ZfTbKegC16HMkVjS8gj2SGQNVmUNC8kSCikQQK5w
- Local RPC: http://127.0.0.1:9658/ext/bc/2A45por6NN5o17NwKFTHTjyKhJobL8UPd92Sbi4ffaMfohRXRA/rpc
- Public RPC: https://rpc.snowside.network/testnet
- NodeID: NodeID-MFYa9TTeDp7JNAEwavG5JVuY3ZorMSixe
- Port: 9658
- Precompiles: Warp only
- ICM Status: Deployed (Messenger: 0x253b2784c75e510dD0fF1da844684a1aC0aa5fcf, Registry: 0xB8e71012d3F55D9EbbFf74376dE180702c1D8A6F)
- Relayer: Not deployed (skipped)
-
SnowsideSignet (Chain ID: 33416 / 0x8288) — DEPLOYED & VERIFIED Session 11
- Blockchain ID: 26XsRMLXezgJ1mK8TSVoHsRfBcy6Mwr4kJdKUAfgegb3PH4b5f
- Subnet ID: 2W9boARgCWL25z6pMFNtkCfNA5v28VGg9PmBgUJfuKndEdhrvw
- Local RPC: http://127.0.0.1:9654/ext/bc/26XsRMLXezgJ1mK8TSVoHsRfBcy6Mwr4kJdKUAfgegb3PH4b5f/rpc
- Public RPC: https://rpc.snowside.network/signet
- NodeID: NodeID-2QpdUKC81YfKoPwU4kuUA8er5FiNQ3V6w
- Precompiles: Warp, NativeMinter (admin: ewoq), ContractDeployerAllowList (admin: ewoq)
- ICM Status: NOT deployed (deploy failed during L1 creation due to cloned genesis; deploy separately with
avalanche icm deploy) - Genesis: Cloned from SnowsideTestnet, patched with chainId 33416 + two precompile configs
- Verified: NativeMinter minting works, DeployerAllowList blocks non-allowlisted deploys
- Token Name: ECX Token
- Token Symbol: ECX
- Consensus: Proof of Authority (PoA)
- ICM Messenger Address: 0x253b2784c75e510dD0fF1da844684a1aC0aa5fcf
- ICM Registry Address: 0xB8e71012d3F55D9EbbFf74376dE180702c1D8A6F
- PoA Validator Manager: 0x0C0DEbA5E0000000000000000000000000000000
- Validator Transparent Proxy: 0x0Feedc0de0000000000000000000000000000000
- Funded account (ewoq): 0x8db97C7cEcE249c2b98bDC0226Cc4C2A57BF52FC (1,000,000 ECX)
- Private Key: 56289e99c94b6912bfc12adc093c9b51124f0dc54ac7a766b2bc5ccf558d8027
- ICM Deployer: 0x18cD02DB3100cb4382B61329aA2a8cBe4A24B40f (funded with 600 ECX + 1000 minted = ~1590 ECX)
- ICM c-chain Registry: 0x17aB05351fC94a1a67Bf3f56DdbB941aE6c63E25
- Validator Messages Lib: 0x9C00629cE712B0255b17A4a657171Acd15720B8C
- Validator Proxy Admin: 0xa0AffE1234567890ABcDef1234567890ABCdEF34
- Primary Nodes: NodeID-7Xhw2mDxuDS44j42TCB6U5579esbSt3Lg (port 9650), NodeID-MFrZFVCXPv5iCn6M9K6XduxGTYp891xXZ (port 9652)
server {
listen 80;
listen [::]:80;
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name rpc.snowside.network;
ssl_certificate /etc/nginx/ssl/server.crt;
ssl_certificate_key /etc/nginx/ssl/server.key;
access_log /dev/null;
error_log /root/error_log;
root /var/www/html;
index index.html index.htm;
location / {
try_files $uri $uri/ /index.html;
}
# Snowside Mainnet (ChainID: 32904) — Updated Session 12
location /mainnet {
proxy_pass http://127.0.0.1:9656/ext/bc/2WGjPQF6YcV3KN19d5x21Cj8VAvxrakA72Ke7RHtZJpQBJBkdV/rpc;
proxy_set_header Host 127.0.0.1;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Snowside Testnet (ChainID: 33160) — Updated Session 12
location /testnet {
proxy_pass http://127.0.0.1:9658/ext/bc/2A45por6NN5o17NwKFTHTjyKhJobL8UPd92Sbi4ffaMfohRXRA/rpc;
proxy_set_header Host 127.0.0.1;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# Snowside Signet (ChainID: 33416) — Updated Session 11
location /signet {
proxy_pass http://127.0.0.1:9654/ext/bc/26XsRMLXezgJ1mK8TSVoHsRfBcy6Mwr4kJdKUAfgegb3PH4b5f/rpc;
proxy_set_header Host 127.0.0.1;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
- Hardware/OS: 6 vCPU / 16GB, 125GB local + 1TB block storage
/dev/vdcat/mnt/avax-data(blockchain db). Logs + chainData on local disk:/home/ubuntu/avax-local/<mainnet|fuji>/logs/. - Services (systemd, both
Restart=always):avalanchego-mainnet.service(v1.14.2, NodeID-Kf5bbEanWq9TsyoBXqnJzvc8Dn6mu5haS) andavalanchego-fuji.service(v1.15.0-fuji, NodeID-BxEPkQVNkC4ZYsez1GbyJVn9FVXzAzT15). - Avalanche-CLI: v1.9.6 at
/home/ubuntu/bin/avalanche(same version as snowside).
- avalanchego v1.14.2 state sync has NO resume. The sync-completed marker is only durable AFTER the post-sync snapshot wipe (
Deleting state snapshot leftovers,wipe.go) finishes. Any restart during state sync OR during the wipe = the ENTIRE state sync restarts from scratch. There is no checkpoint, no partial credit. - Incident (Sep 10 06:05 UTC): Ubuntu unattended-upgrades upgraded glibc (
libc6+ 22 other packages) → needrestart stopped both avalanchego units (SIGTERM 06:05:29, SIGKILL 06:07:00, auto-restart 06:07:02) → the running wipe was interrupted at 1,052,960,000 entries / 25h6m elapsed → C-chain state sync restarted from scratch (triesRemaining=2,277,705; node-reported ETA ~60h). A prior crash (Sep 8 12:32, FATALduplicate metrics collector registration attemptedin X Chain handler — avalanchego-internal bug, NOT OOM) had already forced the first wipe pass. Total cost of the incident: days of sync progress. - HARD RULES for the
avalancheVPS (non-negotiable):- NEVER
systemctl stop/restartanyavalanchego-*unit without explicit Project Lead approval. - NEVER run
apt upgrade,apt dist-upgrade, orunattended-upgrademanually on this box without explicit Project Lead approval. - During a wipe window (C.log shows
Deleting state snapshot leftovers): ZERO restarts and ZERO package operations of any kind. - Check
systemctl show avalanchego-mainnet -p NRestartsfor unexpected restarts before trusting process uptime (psuptime is misleading — verify full args, the Fuji and Mainnet procs look alike).
- NEVER
- Prevention applied (Session 20, Project Lead approved BOTH):
/etc/needrestart/conf.d/avalanchego.conf→$nrconf{override_rc}{q(^avalanchego)} = 0;— needrestart can never restart anyavalanchego*unit, even on manual apt runs./etc/apt/apt.conf.d/20auto-upgrades→ both"0"— unattended-upgrades fully disabled. Original backed up to20auto-upgrades.bak-20260910.Unattended-Upgrade::Automatic-Rebootwas already false (default).
- Snowside VPS is NOT exposed to this failure class: its 5 avalanchego procs run via avalanche-cli (NO systemd units), so needrestart cannot restart them; auto-reboot is off; unattended-upgrades deliberately left ON there (public-facing box — worst case is a seconds-long nginx/docker blip, not a state-sync wipe).
The signet block explorer (explorer-signet.snowside.network) was showing stale block height (build-time fetch in Astro frontmatter).
FIX (Session 12): Rewrote explorer pages to use client-side <script> fetching with 15s auto-refresh. Block height now updates in real-time.
Root cause: Astro frontmatter await getNetworkData() runs at build time, freezing the block number at whatever it was when the static site was generated.
- All source files must include a comment at the file's path relative to the monorepo root (e.g.,
// packages/web/src/components/Hero.astro). - Packages use
pnpmwith workspace filtering. Never usenpminside packages — alwayspnpm. package-lock.jsonmust NOT exist in any package. Delete it if found. Onlypnpm-lock.yamlat root.- Nav hash links must use
/#prefix (e.g.,href="/#about", nothref="#about") so they work from any page, not just the homepage. This is a static multi-page site, not a SPA. - Footer hash links already follow this convention (
/#about,/#tech,/#value,/#roadmap).
/— Landing page (index.astro): Nav, Hero, About, WhyAvalanche, ValueProposition, NodeRunr, ECash, Team, Roadmap, CTA, Footer/whitepaper— Whitepaper viewer page (whitepaper.astro) embedding/whitepaper.pdf/whitepaper.pdf— Static PDF endpoint generated bysrc/pages/whitepaper.pdf.ts(jsPDF)/validators— Validator onboarding page (validators.astro): hero, prerequisites, 5-step NodΞRunr deployment, monitoring, stopping/unstaking, references, CTA
/— Introduction/architecture/*— Overview, BMM, Consensus, Gas Model, ICM Bridge, Security Model/guides/connect-wallet— Web3 wallet connection guide (MetaMask/Rabby) with RPC URLs, Chain IDs (Hex format escaped\|), and Explorer URLs for Mainnet, Testnet, Signet./guides/*— Running a Validator, Deploying Contracts, Bridging USDC/reference/*— Glossary, Configuration
/— Mainnet explorer (renders directly at root)/[network]/index.astro— Dynamic routes for/mainnet,/testnet,/signetfunctions/_middleware.js— Cloudflare Pages Functions middleware. Intercepts subdomains (explorer-testnet,explorer-signet) and rewrites root/and deep links (e.g./tx/0x...) to their respective static paths (/testnet/,/signet/) without URL redirects.- Layout:
src/layouts/Base.astro. Components:Header.astro(larger favicon, network switcher, dynamic title),Search.astro(handles Block/Tx/Address routing),Footer.astro(Avalanche & eCash links). - Index UI: Etherscan-style stats grid (Latest Block, Gas Price, Chain ID, RPC URL).
- KNOWN ISSUE: Network config has stale Blockchain IDs from Session 7. Signet shows block 7 instead of 11+ because it is querying the old (dead) Blockchain ID.
/— Bridge main page with network selector and QR code display/scanner/[network]/index.astro— Dynamic routes for/mainnet,/testnet,/signetfunctions/_middleware.js— Cloudflare Pages Functions middleware for subdomain routing
/— Lean Canvas page (index.astro): hero + full-res Lean Canvas PNG (4724x2233) inline viewer + download buttons (PDF, PNG, whitepaper).- Assets:
public/snowside-lean-canvas.png(4724x2233, inline view),public/snowside-lean-canvas.pdf(1 page, download),public/snowside-lean-canvas-poster.png(1200x630 OG poster). - Layout:
src/layouts/Base.astro(full OG + Twitter meta, 1200x630 poster). Components:Nav.astro(Download PDF button),Footer.astro(links to web, whitepaper, docs, GitHub). - Cloudflare Pages project:
snowside-canvas. Custom domain:canvas.snowside.network. - Deploy:
cd packages/canvas && pnpm run deploy(builds +wrangler pages deploy --project-name snowside-canvas --branch master).
/— Interactive 12-slide tour. ONE document serves all viewports; there is no/mobilepage. Slide order: Cover, What is Snowside, Two native assets, How it works, Why Avalanche, Agentic Payments, Comparison, Risks, Roadmap, Opportunities, FAQ, Connect with Us.- Stack: zero dependencies. No Slidev/Vite/Vue/UnoCSS, no bundler.
scripts/build-tour.mjs(plain Node) rendersdist/index.htmlfromcontent.mjs+slides.mjs;assets/tour.css+assets/tour.jsare copied in. ~28KB HTML total. - Two layouts, one document:
data-mode="deck"(fixed 980×552 canvas, Slidev look) vsdata-mode="mobile"(reflowing, one slide per screen). The switch is scale-based, not width-based: mobile whenmin(vw/980, vh/552) < 0.65. This keeps ALL tablets and landscape phones on the deck; only portrait phones reflow. Threshold is duplicated inassets/tour.css,assets/tour.js(MOBILE_SCALE) and the inline boot script inscripts/build-tour.mjs— change all three together. - Head is built in, not injected post-build: Simple Analytics, OG/Twitter meta (1200x630 poster), favicon, an inline pre-paint mode boot script (prevents FOUC on phones), and a
<noscript>fallback that stacks all slides into one page. content.mjsis the single source of truth for copy (16 exports).slides.mjsis the slide model. Editing copy means editingcontent.mjsonly.- CSS cascade caution: cover/connect rules are scoped
.snow-cover-root/.snow-connect-rootbecause bare class selectors lose to.snow-bg h1/.snow-bg p..slide-leadis intentionally unstyled to match the original cascade. See comments inassets/tour.css. - Requires a global
nodebinary only (no package deps at all). Local preview:pnpm serve(port 4321). - Cloudflare Pages project:
snowside-tour. Custom domain:tour.snowside.network. - Deploy:
cd packages/tour && pnpm build && pnpm exec wrangler pages deploy dist --project-name snowside-tour --branch master --commit-dirty=true. - PDF export: print the page (
@media printrenders one 980×552 page per slide); replacesslidev export.
- All 6 packages (web, pitch, canvas, docs, explorer, tour) have Simple Analytics installed or available.
- Standard embed — no site ID needed, auto-detects domain.
- Script URL:
https://scripts.simpleanalyticscdn.com/latest.js - NoScript image:
https://queue.simpleanalyticscdn.com/noscript.gif - Web & Pitch:
<script is:inline async defer ...>+<noscript><img ...>inBase.astro<head>. Theis:inlinedirective is required so Astro does not bundle the external script. - Tour: written into the generated
dist/index.htmlbyscripts/build-tour.mjs(there is no post-build injection step any more).
- CRITICAL: Use
@tailwindcss/postcss(NOT@tailwindcss/vite). The Vite plugin has a rolldown incompatibility (Missing field tsconfigPaths) that breaks on Cloudflare Pages build servers even when it passes locally. - Each Astro package needs a
postcss.config.mjswith:export default { plugins: { '@tailwindcss/postcss': {} } }; - Remove the
tailwindcss()Vite plugin fromastro.config.mjs— PostCSS is auto-detected by Vite. - CRITICAL FIX (ENOENT): In
global.css, use@import 'tailwindcss/index.css';instead of@import 'tailwindcss';. Vite/Rolldown's native CSS parser sometimes fails to resolve the package import before the PostCSS plugin runs, causingENOENT: no such file or directory, open '.../tailwindcss'. - Keep
@themeblocks inglobal.css— the PostCSS plugin processes them identically.
- CRITICAL: In
.mdfiles, Starlight auto-injects ALL components (Steps,Card,CardGrid,Item,Tabs,LinkCard, etc.). Do NOT addimportstatements — they render as literal text on the page. - Use
.mdfiles (NOT.mdx) for pages that use<Steps>with<Item>.Itemis NOT exported from@astrojs/starlight/components, so you cannot import it explicitly in.mdx. - Starlight social icons: use
"x.com"(not"x") for X/Twitter. Check valid icon names in the error message if unsure. - Build output:
dist/with oneindex.htmlper page + Pagefind search index + sitemap. - The
headarray inastro.config.mjsaccepts{ tag, attrs, content? }objects for injecting<head>tags without modifying layout files.
- CRITICAL (pnpm 10+):
pnpm.onlyBuiltDependenciesmust be inpnpm-workspace.yamlat the root, NOT inpackage.json. pnpm 10 ignores thepackage.jsonfield. - CRITICAL: When changing dependencies (e.g., upgrading Astro), you MUST explicitly
git add pnpm-lock.yamland commit it. Cloudflare uses--frozen-lockfileand will fail withERR_PNPM_OUTDATED_LOCKFILEif the lockfile doesn't matchpackage.json. .gitignoremust exclude:node_modules/,dist/,.astro/,.env*(except.env.example).- Never commit
node_modules/— if accidentally committed, rungit rm -r --cached node_modules, add.gitignore, and amend the unpushed commit. - Pages projects (aBitSuite account, ID
2cdd50405dc13f86476f4d03e1ad1282, email hello@abitsuite.com):snowside(web),snowside-pitch(pitch),snowside-canvas(canvas),snowside-docs(docs),snowside-explorer(explorer),snowside-bridge(bridge). OAuth token stored at~/.config/.wrangler/config/default.toml. - Deploying a Pages project from CLI:
cd <pkg> && pnpm exec wrangler pages deploy dist --project-name <snowside-*> --branch master --commit-dirty=true. Use--commit-dirty=trueto silence the uncommitted-changes warning. - Custom domains: Adding a custom domain to a Pages project via API (
POST /accounts/{id}/pages/projects/{project}/domains) does NOT auto-create the DNS CNAME. Thewrangler loginOAuth token LACKS theDNS:Editscope, so DNS records cannot be created/listed via API (error code 10000 "Authentication error"). Either: (a) create the CNAME in the Cloudflare dashboard, (b) re-runwrangler loginand check "Edit Cloudflare DNS" scope on the consent screen, or (c) create a Cloudflare API Token withZone:DNS:Editand setCLOUDFLARE_API_TOKEN+CLOUDFLARE_ACCOUNT_IDenv vars. - Cache purge after asset swap:
POST /accounts/{id}/pages/projects/{project}/deployments/purge_cachewith the OAuth bearer token (oauth_tokenfield in~/.config/.wrangler/config/default.toml) purges the Pages CDN so changed static assets (e.g., OG posters) are served immediately. The fresh deployment URL (<hash>.pages.dev) always serves the new file regardless.
- Current version: v0.4 (meta.ts
WHITEPAPER_VERSION = '0.4') - PDF generated at build time via
packages/web/src/pages/whitepaper.pdf.ts(Astro static endpoint using jsPDF). - Content lives in
packages/web/src/data/whitepaper/content.ts(15 sections, auto-numbered at render time). - Figures are vector
Figureobjects inpackages/web/src/data/whitepaper/figures/— 9 figures. - Fonts (
NotoSans-Regular/Bold/Italic.ttf) inpackages/web/src/fonts/. - Viewer page at
/whitepaperembeds the PDF via<iframe src="/whitepaper.pdf">. - PDF.js is at
packages/web/public/pdfjs/for any custom viewer needs.
The following files were modified in the v0.3 → v0.4 refactor:
- content.ts — Sections 4.9, 4.10, 4.11 (new: Fallback Settlement), 5.3, 5.4, 5.5, 5.7 (new: USDC Bridging), 5.8 (new: Foundation), 5.9 (new: Avalanche Value Flow), 9.2, Section 12 Roadmap. Tl;dr updated.
- meta.ts — Version 0.3 → 0.4, date updated.
- figures/fee-model.ts — Diagram updated: Contract Fee opt-in, Owner+Treasury vesting, Treasury distribution (Foundation/Proposers/Validators).
- figures/role-separation.ts — Settlement Proposers: "Reimbursed via Base Fees" → Treasury-compensated.
- figures/validator-economics.ts — Revenue: "Contract Fees (BTC, vesting split)" → "Treasury distribution (85%, proportional to bonded BTC)".
- figures/icm-bridge.ts — Added reverse USDC flow (Snowside → C-Chain via ICM).
- docs/architecture/bmm.md — Updated economic incentives text.
- docs/architecture/gas-model.md — Updated fee model text.
- docs/architecture/icm-bridge.md — Added reverse USDC flow note.
- docs/reference/glossary.md — Added Treasury, Foundation, USDC auto-bridging terms.
- global.css — Applied Tailwind ENOENT fix (
@import 'tailwindcss/index.css'). - FIXED: content.ts orphaned sections (5.7–5.9) moved inside Section 5 paragraphs array. Build passes, PDF generates successfully.
- Consensus terminology (v0.4): Use "Snowman consensus" when referring to the linear-chain variant. "Snowball" is the broader protocol family.
- eCash Terminology (v0.4): Use "eCash" (not Bitcoin) when referring to miners, hashrate, L1 security source. Snowside is secured by eCash's SHA-256d PoW.
- Settlement Model (v0.4): Classified as "rollup-style settlement".
- Roadmap (v0.4): Two-phase model (Phase 1: Permissioned, Phase 2: Permissionless with AVAX + BTC).
- Fee Model (v0.4): Three-part fee model updated — Contract Fees are now optional/opt-in (not required), may be denominated in BTC or USDC, and the validator portion is replaced by a Snowside Treasury. Treasury distribution: Foundation retains 10%, Settlement Proposers receive 5% (configurable), Validators receive 85% (100% proportional to bonded BTC, no equal distribution). Contract Owner vesting: 50%→80% over 18 months. Settlement Proposers compensated from Treasury (not Base Fees) — 100% of Base Fees go to eCash miners via BMM.
- All five packages (
web,pitch,canvas,docs,explorer) use the same SVG favicon atpublic/favicon.svg. - Design: two snowmen side-by-side forming a literal "88" silhouette (Drivechain ID #88).
- Dark rounded-square backdrop (#0a0f1a, rx=14) — required so white snowmen are visible in light browser themes.
- If updating the favicon, update all four files and keep the SVG bodies identical.
packages/web/public/og-image-v2.png— 1200x630px.packages/exploreruses a copy atpackages/explorer/public/og-image.png.packages/canvas/public/snowside-lean-canvas-poster.png— 1200x630px (Lean Canvas OG poster).packages/pitch/public/snowside-pitch-poster.png— 1200x630px (pitch OG poster).- Cache-busting: When replacing an OG image, purge the Cloudflare Pages cache via API (
POST /accounts/{id}/pages/projects/{project}/deployments/purge_cache) or use a versioned filename. - Cloudflare Pages cache note: After redeploying a changed static asset, the main
*.pages.dev/ custom domain URL may serve the old file from CDN cache. The fresh deployment URL (<hash>.pages.dev) always serves the new file. Purge cache or wait for TTL expiry.
- The Avalanche Foundation retro9000 grant announcement tweet:
https://x.com/AvalancheFDN/status/1932484367324229635?s=20
packages/pitch/src/layouts/Base.astrohas<meta name="robots" content="noindex, nofollow">.- Zero links to
pitch.snowside.networkfrom the web package.
The landing page alternates dark and light sections for visual rhythm.
| Section | Background | Text | Cards |
|---|---|---|---|
| Nav | surface-0/90 (blur) | white/slate-300 | — |
| Hero | surface-0->1 gradient | white/snow-400 | — |
| About | surface-1 (dark) | slate-300/snow-400 | — |
| WhyAvalanche | snow-50 (light) | slate-900 | surface-1 (dark cards) |
| ValueProposition | surface-0 (dark) | white/slate-300 | surface-2 badges |
| NodeRunr | snow-50 (light) | slate-900 | surface-1 placeholder |
| ECash | surface-1 (dark) | slate-300/snow-400 | — |
| Team | snow-50 (light) | slate-900/slate-700 | surface-1 badges |
| Roadmap | surface-0 (dark) | white/slate-200/snow-400 | — |
| CTA | snow-50 (light) | slate-900 | surface-1 buttons |
| Footer | surface-0 (dark) | slate-400/slate-500 | — |
- Sections: About (
/#about), Technology (/#tech), Value Proposition (/#value), Roadmap (/#roadmap) - Resources: Documentation (docs.snowside.network), GitHub (github.com/abitsuite/snowside), Whitepaper (
/whitepaper), Validators (/validators), NodΞRunr (layer1.run), Avalanche (avax.network) - Contact: Discord (discord.gg/jVytngEWt), X / Twitter (x.com/0xShomari), Email (shomari@abitsuite.com)
- At the end of each session, update
docs/HANDOFF.mdwith the current state and next steps. - Include: what was done, what remains, build status, and any known errors.
| Language | Tool | Workspace Config |
|---|---|---|
| JS/TS | pnpm | pnpm-workspace.yaml (packages/*) |
| Go | go | go/subnet-evm/go.mod |
| Rust | cargo | rust/bmm-bidder/Cargo.toml |
| Solidity | Foundry | contracts/foundry.toml |
| Package | Build Command |
|---|---|
| web | cd packages/web && pnpm build |
| pitch | cd packages/pitch && pnpm build |
| canvas | cd packages/canvas && pnpm build |
| docs | cd packages/docs && pnpm build |
| explorer | cd packages/explorer && pnpm build |
| bridge | cd packages/bridge && pnpm build |
| tour | cd packages/tour && pnpm build |
| subnet-evm | cd go/subnet-evm && ./scripts/build.sh |
| bmm-bidder | cd rust/bmm-bidder && cargo build |
| contracts | cd contracts && forge build |
- Stack: Cloudflare Worker (
snowside-api), Hono, Chanfana (OpenAPI), Zod. - Deployment:
wrangler deploy->snowside.network/v1*. - OpenAPI UI: Served directly at
/v1(Swagger UI). Spec at/v1/openapi.json. - Backend Proxy: Catch-all proxy forwards requests to Drynet 4 Esplora (
https://esplora.drynet4.drivechain.dev (testnet)).
- Stack: Astro static site + Tailwind v4 (
@tailwindcss/postcss). - Features: Network selector dropdown (Mainnet/Testnet/Signet), HTML5 QR code scanner (
html5-qrcode), QR code display for deposits. - Subdomains: No subdomains used for networks; network selection is handled in-session.
- ALWAYS run
wc -l <file>andtail -n 15 <file>after a heredoc to verify correctness. Multi-file pastes garble frequently, but the content is fine. - Use
'EOF'(single-quoted) delimiters to prevent shell expansion inside heredocs. - For Python one-liners, use single quotes outside and double quotes inside:
python3 -c '...'
- Phase 1 (Current): Custodial federation — federation holds keys, generates deposit addresses, mints ECX manually
- Phase 2: Register Snowside as sidechain slot on eCash Signet (M1/M2), deploy bip300301_enforcer, switch to enforcer gRPC for deposit addresses and validation
- Phase 3: Register on eCash Drynet/Testnet
- Phase 4: Register on eCash Mainnet — full trustless peg with miner-voted withdrawals
- Federation service (
packages/federation): Node.js service, holds HD wallet (stubbed), generates deposit addresses, monitors Esplora for deposits, mints ECX on Snowside via NativeMinter precompile (0x0200...0001) - API (
packages/api): Cloudflare Worker with D1 database, REST endpoints for bridge UI + federation auth endpoints - Bridge UI (
packages/bridge): Astro static site on Cloudflare Pages, client-side fetch to API, QR codes, deposit/withdraw tabs, transaction history - D1 Database:
snowside-bridge(ID: 202053ef-9607-481d-9b73-185734164ea4)
- bip300301_enforcer (Rust, on VPS): Watches eCash L1 via ZMQ, validates M5 deposits and M6 withdrawals, exposes gRPC at localhost:50051 — NOT YET RUNNING (custodial MVP first)
- Federation service (Node.js + viem, Docker on VPS bchplease): Polls API for pending deposits, derives HD wallet addresses (@scure/bip32 + ecashaddrjs), checks Esplora per-network, mints ECX via NativeMinter.mintNativeCoin(), processes withdrawals via @scure/btc-signer, 10s poll interval. Container name: snowside-federation.
- VPS: bchplease (root@bchplease, Ubuntu 24.04, Docker 29.7.2)
- Federation Docker:
docker compose up -d --buildin /root/snowside/packages/federation - pnpm version: Pinned to 10.15.1 in Dockerfile (avoids pnpm 11 esbuild build script blocking)
- Federation connects to enforcer gRPC instead of polling Esplora
- Deposits use enforcer
WalletService/CreateNewAddress(proper P2SH with sidechain commitment) - Withdrawals use M3/M4/M6 bundle process (13,150 ACKs over 26,300 blocks ≈ 6 months at 10-min block time)
- Bridge UI shows withdrawal voting progress (ACK count, blocks remaining)
- NOT YET IMPLEMENTED — enforcer not running, Snowside not registered as sidechain slot
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /v1/bridge/status | Public | Federation online status |
| POST | /v1/bridge/deposit | Public | Create deposit request |
| GET | /v1/bridge/deposit/:id | Public | Get deposit status |
| GET | /v1/bridge/deposits/:address | Public | List deposits for address |
| POST | /v1/bridge/withdraw | Public | Submit withdrawal request |
| GET | /v1/bridge/withdrawals/:address | Public | List withdrawals |
| POST | /v1/fed/checkin | Bearer token | Federation heartbeat |
| GET | /v1/fed/deposits/pending | Bearer token | Get pending deposits |
| GET | /v1/fed/deposits/funded | Bearer token | Get funded deposits (for withdrawal UTXO selection) |
| PATCH | /v1/fed/deposit/:id | Bearer token | Update deposit status |
| GET | /v1/fed/withdrawals/pending | Bearer token | Get pending withdrawals |
| PATCH | /v1/fed/withdraw/:id | Bearer token | Update withdrawal status |
| ALL | /v1/* | Public | Esplora proxy (catch-all) |
meta: key/value table (federation check-in timestamps)deposits: id, network, snowside_address, ecash_address, amount_xec, amount_ecx, status, ecash_tx_hash, mint_tx_hash, timestampswithdrawals: id, network, snowside_address, ecash_address, amount_ecx, amount_xec, burn_tx_hash, ecash_tx_hash, status, timestamps- Future BIP-300 fields needed: sidechain_slot, bundle_hash, ack_count, blocks_remaining, ctip_txid, ctip_vout
- BIP-300: Hashrate Escrows — deposits/withdrawals via miner voting, OP_DRIVECHAIN opcode — OPTIONAL (custodial ECX model first)
- BIP-301: Blind Merged Mining — miners secure sidechain without running sidechain nodes
- M5 (Deposit): L1 tx spending CTIP, creating new CTIP with more coins, includes destination L2 address
- M6 (Withdrawal): L1 tx paying out from CTIP, requires 13,150 miner ACKs over 26,300 blocks
- CTIP: Single UTXO per sidechain holding all pegged coins (no UTXO bloat)
- Sidechain slots: Up to 256, each with own CTIP, proposed via M1, activated via M2
- drynet4 block time: 10 minutes (same as Bitcoin) → withdrawal period ≈ 6 months
- bip300301_enforcer: Rust app (github.com/LayerTwo-Labs/bip300301_enforcer), gRPC API, watches L1 via ZMQ
- Enforcer RPCs: ValidatorService/GetSidechains, GetChainInfo, GetChainTip; WalletService/CreateNewAddress, CreateSidechainProposal
- Esplora (per-network):
- mainnet: https://esplora.mainnet.drivechain.dev (eCash mainnet, ECX, ecash: addresses)
- testnet: https://esplora.drynet4.drivechain.dev (eCash drynet4, ECX, ecash: addresses)
- signet: https://esplora.signet.drivechain.info (Bitcoin signet, sBTC, tb1q addresses)
- ✅ D1 database created (snowside-bridge)
- ✅ API code written (bridge + federation endpoints + Esplora proxy)
- ✅ Federation service skeleton written (monitoring + minting logic)
- ✅ Bridge UI updated (API integration, QR codes, status polling, history)
- ✅ D1 schema file written (packages/api/schema.sql)
- ✅ wrangler.toml updated with D1 binding (DB)
- ✅ D1 schema applied (meta, deposits, withdrawals tables)
- ✅ FEDERATION_TOKEN secret set on Cloudflare Worker
- ✅ API deployed to snowside.network/v1* (Cloudflare Worker)
- ✅ Bridge UI deployed to Cloudflare Pages (snowside-bridge project)
- ✅ Federation service running on VPS (bchplease) in Docker container
- ✅ "Connect Wallet" (Rabby/EIP-1193) implemented in bridge UI