Skip to main content
The local development network and public GIWA Sepolia share the same chain ID, so chain ID cannot be the network boundary. What actually separates them is the GIWA_NETWORK declaration, an immutable one-time database binding, the deployment-identity service, and the provenance fences.

The core problem: a borrowed chain ID

Localnet is built to be byte-identical to GIWA Sepolia by design. Anvil starts with --chain-id 91342 and a pinned genesis timestamp of 1700000000, so that every EIP-712 domain, every x402 network string, every Permit2 chain-id-immutable, and every test vector matches the public chain exactly. That determinism is a feature for fixtures and cross-language vectors. It is also a hazard. Every surface that looks like a network identifier collides at 91342: A WHERE chain_id = 91342 filter matches both networks. A valid receipt signature verifies on both. Chain ID is not a network identity, so the system needs an explicit one.

The boundary: GIWA_NETWORK plus an immutable database binding

Network identity is a declared, per-deployment enum, required in server config:
One running stack is one network. A per-request or user-selectable network is rejected by design: the financial evidence is append-only and single-database, so a request-time toggle would let one read model mix two networks’ immutable rows. Configuration is cross-checked and fails closed: a mismatched RPC, chain manifest, cookie scope, provider, or HTTPS combination is rejected at load, not warned about. The real safety boundary is the database. One migration binds each database once, immutably, to one deployment network:
  • A one-row deployment-identity table is initialized exactly once. A second initialization with a different network fails, and UPDATE on the row fails.
  • Binding to giwa-sepolia requires a pristine evidence database. Ambiguous retained evidence fails for operator review instead of being silently reclassified.
  • Web, gateway, receipt, reconciliation, and attestation runtimes assert the bound identity at startup.
This binding matters because the ledger tables block UPDATE and DELETE by trigger. A misclassified historical row could never be corrected, so the system refuses to classify at all until the binding is explicit. Separate databases per network make cross-network joins physically impossible; a shared database with a network column would be one forgotten WHERE clause away from mixing immutable evidence.

The deployment-identity service

In both topologies, a dedicated one-shot service performs the database binding before the rest of the stack starts. Every dependent service orders after it, so no runtime can serve traffic against an unbound or misbound database. The Sepolia topology is isolated and opt-in, with its own database, volume, network, roles, and fail-closed secrets; the default remains the sealed localnet acceptance topology.

Provenance fences

Localnet’s deployer is a provenance fence, not just a deployment script. It records the pinned hashes of its upstream sources (EAS, Dojang, x402, Permit2) alongside the chain state it deployed from, and refuses to start the stack when the tree no longer matches the persisted chain. When the fence trips, the fix is operational (redeploy or reset the affected volume), never weakening the check. Editing a recorded hash alone cannot bless changed upstream source. The payment verifier pins its on-chain reads to the recorded handoff block, which is why localnet Anvil preserves historical state.

Test-provider rejection

GIWA_TEST_PROVIDER=enabled selects a deterministic fake inference provider for hermetic local tests. Configuration hard-throws if it is enabled for Sepolia or production: a public deployment must use a real credentialed provider, and the repository refuses to fabricate public availability when credentials are absent.

What this means for readers

Never trust a chain ID. Check the network label on every artifact. A receipt, payment, or attestation from localnet is cryptographically valid and carries chain ID 91342, exactly like a Sepolia artifact. The network label (and the deployment binding behind it) is the only honest discriminator.
  • The console’s network badge is server-fed from GIWA_NETWORK and renders Localnet or GIWA Sepolia. A hardcoded badge was removed so the indicator can never lie.
  • Explorer links render only for observations with trusted GIWA Sepolia provenance. Local observations stay labeled Local Anvil and never appear next to a sepolia-explorer.giwa.io link.
  • A local deployment may intentionally perform a bounded public Sepolia read; that observation is labeled independently of the deployment identity, so the two never conflate.
  • Localnet keys (the well-known Anvil mnemonic accounts) must never be treated as GIWA, testnet, or production configuration. Public operation supplies its own custody.

Next steps