Web-based smart contract interface for EVM chains (primarily PulseChain) — import by address + ABI, call functions, share via signed links
  • TypeScript 68.5%
  • JavaScript 29.1%
  • CSS 1.9%
  • Python 0.3%
  • HTML 0.1%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
dev f1dd9b4418
All checks were successful
Validate and Deploy / validate (push) Successful in 3m29s
Validate and Deploy / deploy (push) Has been skipped
Restore stock wallet account menu
Use the wallet kit account menu for copying the address and disconnecting. Retain admin session cleanup after disconnect and align browser coverage with the stock menu.

Validation: frontend lint, typecheck, configuration tests and explicit release build; 36 backend unit tests, deliberate duplicate-key and permission guard failures, 60 native PocketBase integration tests, and six browser wallet/session cases.
2026-10-15 22:52:41 -04:00
.forgejo/workflows Add signed share fallback and wallet admin sessions for v0.2.13 2026-10-10 10:15:17 -04:00
.githooks Prevent skipped release versions and finish contract UI cleanup 2026-10-09 11:45:50 -04:00
backend Restore stock wallet account menu 2026-10-15 22:52:41 -04:00
frontend Restore stock wallet account menu 2026-10-15 22:52:41 -04:00
shared Add PocketBase sharing and contract favorites 2026-10-04 12:37:57 -04:00
.gitignore Add admin moderation, publishing settings and owner permalinks 2026-10-05 13:01:54 -04:00
.ignore Initial commit 2026-07-09 17:29:59 -04:00
AGENTS.md Add admin moderation, publishing settings and owner permalinks 2026-10-05 13:01:54 -04:00
config.example.json Add signed share fallback and wallet admin sessions for v0.2.13 2026-10-10 10:15:17 -04:00
LICENSE Initial commit 2026-07-09 17:29:59 -04:00
README.md Adopt the shared modal lifecycle and harden shared operations 2026-10-13 17:17:25 -04:00

ICU Smart Contract GUI

A browser interface for EVM contracts: import an address and ABI, call functions, track transactions, and share contracts or profiles through wallet-signed links. Browser data stays in IndexedDB; the backend stores shared content and private administration state. Writes require the wallet to match the selected chain.

Build and run

Use Node.js 22 and stock PocketBase 0.40.4 with bundled JavaScript hooks and the Preact frontend.

(cd frontend && npm ci && npm run build:preview)
(cd backend && npm ci && npm run build -- --preview)

Serve backend/dist/pb_public, backend/dist/pb_hooks and backend/dist/pb_migrations with PocketBase; keep its data directory outside release artifacts. Frontend development uses npm run dev in frontend/, port 5173. Preview builds show -dev and cannot deploy to production.

Frontend installation applies an integrity-checked depth limit to braces 3.0.3; builds reject missing or unexpected patch bytes. If installation skips lifecycle scripts, run node frontend/scripts/harden-braces.mjs before building.

For a release, set VITE_RELEASE_VERSION=v<package version> when running npm run build:release in frontend/, then run npm run build -- --output dist-release in backend/. Both package versions must match.

Configuration

The backend generates /config.js from environment variables or string-valued settings in pb_hooks/.environment/settings.json. Keep the file mode 0600 and its directory mode 0700; upload as settings.pending for bootstrap activation. Nonempty environment values take precedence. Never publish administrative keys.

Variable Purpose
ICU_ENVIRONMENT production, staging or development.
ICU_PUBLIC_ORIGINS App origins allowed for wallet administration.
ALLOWED_ORIGINS Browser CORS allowlist.
ICU_TRUSTED_PROXY_CIDRS Explicit peers trusted to forward visitor IPs.
ICU_ADMIN_ADDRESSES Admin wallets; empty disables the admin API.
ICU_ADMIN_SESSION_KEY Private hexadecimal HMAC key, at least 32 bytes; alternatively use mode-0600 admin-session.key in the data directory.
ICU_WRITE_MAINTENANCE 1 pauses mutations while reads remain available.
ICU_TOKEN_ICON_SERVICE_URL Optional HTTPS icon-resolver origin; unset uses provider fallbacks.
ICU_TOKEN_ICON_SERVICE_KEY Private icon-resolver consumer key.
ICU_TOKEN_ICONS_ENABLED Set false to disable icon lookups.
ICU_TOKEN_ICON_DEXSCREENER_RPM Server-wide fallback requests per minute; default 20.
ICU_TOKEN_ICON_GECKOTERMINAL_RPM Server-wide fallback requests per minute; default 1.
ICU_WC_PROJECT_ID Optional WalletConnect project ID.
ICU_MIRROR_PUBLIC_URL Optional read-only mirror URL on r2.dev.
ICU_MIRROR_SIGNER Public address used to verify mirror snapshots.
ICU_MIRROR_ENDPOINT, ICU_MIRROR_BUCKET Mirror S3 endpoint and dedicated bucket.
ICU_MIRROR_ACCESS_KEY_ID, ICU_MIRROR_SECRET_ACCESS_KEY Private bucket-scoped object read/write credentials.
ICU_MIRROR_SIGNING_KEY Private 32-byte hexadecimal snapshot key; separate from wallet and admin keys.
ICU_CLOUDFLARE_EDGE_SECRET Private hexadecimal credential, at least 32 bytes, matching the hostname-scoped X-ICU-Edge-Auth header rule; required for production publishing.

Branding and the Chainlist endpoint are fixed in source. Static-only hosting can provide config.example.json as /config.js; an empty frontend.apiBase disables backend publishing without disabling local features.

The optional /admin dashboard uses allowlisted wallet sign-in. It provides sharing statistics, searchable records, payload inspection, confirmed deletion, permanent bans, permalink management and persisted publishing settings. Admin access requires a configured backend and an approved ICU_PUBLIC_ORIGINS entry. Admin sessions use bearer tokens held only in browser memory. Reloading requires a fresh wallet signature; no cookies or persistent login storage are used. Frontend and API may use unrelated domains; public APIs use the ALLOWED_ORIGINS allowlist.

Wallet sign-in does not use PocketBase user accounts. Migrations lock registration, record access and authentication for the unused users collection; PocketBase superuser administration remains separate.

Settings default to a 60-second signature timeout, 5 shares per wallet and 30 POST attempts per IP per minute, and daily new-link limits of 100 per wallet and 1000 per IP. Changes preserve usage counts. Existing explicit limits are imported once; their old environment variables no longer control the app.

IP, subnet, country and city bans block publishing. Owner bans also hide hosted records and release custom names; signed owner deletion remains available. Country/city data comes from authenticated Cloudflare location headers. Enable Add visitor location headers on a proxied hostname, preserve real IPv6 addresses, and verify the forwarding chain. Unknown locations are allowed; direct-origin publishing is rejected when edge authentication is configured.

Owners can claim available /c/name and /p/name permalinks by signing with their wallets. Names use 1–64 lowercase letters, digits or hyphens; 13- and 22-character ID-shaped names are reserved. Each record has one name, with separate contract and profile namespaces. Opaque IDs survive alias changes. Unbanning restores eligible names only if nobody else claimed them, even if that claimant later released them. Admin transfers cancel the former record's automatic restoration.

Read-only outage fallback

An optional R2 mirror exports public contracts and profiles in the background. Configure all mirror variables together and allow the frontend origins in the bucket's GET/HEAD CORS policy. Production and staging use separate buckets and credentials. Only the public URL and verification address reach the frontend.

The exporter publishes signed snapshots before advancing their signed head. Failed uploads retain the previous head and retry; deletions and owner bans advance the next snapshot. Retired generations are removed after ten minutes. The mirror exposes known-reference reads without a public share inventory.

Network failures, timeouts and server errors can load the last verified copy. Authoritative client errors, including missing or blocked records and throttling, do not trigger fallback. Saved content shows its snapshot time and a retry action; backend mutations remain unavailable and are never replayed automatically. Sharing links use the frontend's current origin. Browser-local editing and exports remain available independently of the backend.

Public API

  • Read: GET /read/{contracts|profiles}/{id-or-name}.
  • Share: POST /share/contract or /share/profile.
  • Delete/rotate: signed POST /delete/{table}/{nid} or /regen/{table}/{nid}.
  • Permalinks: GET or signed POST /permalink/{table}/{nid}.
  • Icons: POST /api/scgui/token-icon/batch with {chainId, addresses} (max 30); optional failedImages maps requested addresses to failed automatic image URLs. Single-token reads use GET /api/scgui/token-icon/{chain}/{address}. Responses include imageUrl, symbol, cached, retryAfter, source, state, and version. The edit form’s Fetch Icon button selects a URL for saving. Entries with no saved image may display an automatically resolved icon; saved images always take priority. Provider fallback supports Ethereum and PulseChain. Cached metadata expires after 24 hours; temporary failures use a cooldown.
  • Health/release: GET /api/health and /api/scgui/release.

Icon GET, HEAD and batch requests share an IP request budget; respect retryAfter when throttled.

Signed writes use Content-Type: application/json and contain exactly {payload, signed}; sign the literal payload string. Duplicate keys, unknown fields, stale signatures and URL mismatches are rejected. Delete/rotate payloads use schema icu-sc-gui/share-mutation/v1 with operation, nid, table, sharedByAddress, timestamp. Permalink payloads use icu-sc-gui/permalink/v1, operation claim or remove, and add slug.

Exports use schemaVersion: 2 and full-signature favoriteFunctions. Legacy imports remain supported. Public reads omit owner addresses; quotas and replay records persist. Shared and administrative SQL tables are not public collections.

Deployment and verification

The frontend is hosted on Cloudflare Pages at scgui.pages.dev and sc.gui.icu; the API stays on PocketHost at sc-api.gui.icu. Admin access requires the frontend origin to be approved in ICU_PUBLIC_ORIGINS. Browser-local data is separate per origin. frontend/deployment.json defines the public topology. The Pages preparation script exports only public settings and installs response headers; no Worker or Pages Functions are required.

Repositories with WEB_DEPLOY_ENABLED=true run main-push validation on the ubuntu-latest build runner; enable this variable only on Remote Git. Matching version-tag pushes promote one artifact through PocketHost staging/production, then upload Pages assets. Enable the local publication check with git config core.hooksPath .githooks; it rejects gaps between version tags, tags that disagree with package versions, and main pushes without a matching tag on the same commit. Tagged CI repeats the version check. CI needs repository secrets CLOUDFLARE_API_TOKEN, POCKETHOST_CONNECTION_JSON, POCKETHOST_PRODUCTION_SETTINGS, POCKETHOST_STAGING_SETTINGS, POCKETHOST_SFTP_KEY and POCKETHOST_KNOWN_HOSTS. Optional POCKETHOST_PRODUCTION_MIRROR_SETTINGS and POCKETHOST_STAGING_MIRROR_SETTINGS contain only the seven mirror variables; they overlay existing deployment settings without changing unrelated values. Provider output is withheld, keys use private temporary files, and build/test subprocesses receive no deployment credentials.

backend/scripts/deploy.mjs takes --target staging|production, --connection <protected JSON file> and --artifact <directory>. Connections define host, port, user, optional identityFile, and distinct target instances, origins and settings files. Use --dry-run for checksums; production requires --confirm-production v<package version> and protected edge configuration. Promote one immutable artifact through staging. Activation verifies hooks before publishing HTML; preserve previous modules, assets, data and session keys.

Back up before migration and pause source writes. If replay records are absent, wait 300 seconds plus the signature timeout. backend/scripts/migrate.py exports private SQLite data and generates guarded imports for empty or identical targets. On fresh instances, place the import after the base migration and before 1791158400_moderation.js. Never reset storage after a failed operation.

Frontend checks: lint, typecheck, test:config, build:preview. Backend: test, test:guards, test:integration (PocketBase on PATH or POCKETBASE_BINARY). Browser checks use test:a11y with A11Y_BASE_URL and BROWSERLESS_URL / BROWSERLESS_TOKEN; source fixtures need SCGUI_SOURCE_TESTS=1.

License

MIT — see LICENSE.