Docs · Crypto

The Onchain Module

Not installed by default. One command adds it, one removes it, and it runs on offline fakes until you add an RPC url.

An optional slice: wallet sign-in, chain reads, token swaps, and presenting a token your workspace owns. It is not installed by default -- see Getting it below, which is one command.

Once installed it is still off in the sense that matters. With no RPC url configured, the adapters return deterministic offline fakes and nothing reaches a network, so the whole surface renders and the entire test suite runs with zero external accounts.

Open /onchain in your running app. That page reports what is actually configured on your install, not what the docs wish were configured.

Getting it

The module is not installed by default. The kit ships it staged, in optional/onchain/, so an app that is not onchain carries no crypto code, no crypto routes, and no Node toolchain at all. Adding it is one command:

script/onchain install    # copies the files, applies the wiring, migrates, builds the island
script/onchain status     # staged, or installed
script/onchain remove     # the exact inverse

Install copies about 138 files to the paths they belong at, applies a small patch to thirteen shared files (the gem that runs the bundle step, the Node stage in the Dockerfile, the account associations, the token entitlement strategies), creates four tables, and builds the island. Remove reverses all of it and script/verify-shape.sh onchain proves the tree is clean afterwards.

Node is needed only once the module is installed: the browser half is a React island bundled with esbuild. Everything else in the kit stays importmap + Propshaft with no build step.

The one rule

The server owns the message and the calldata.

  • Rails builds the Sign-In With Ethereum message, because Rails verifies it.
  • Rails calls the swap aggregator, because Rails holds that key.
  • Rails reads the chain, so the RPC key never reaches a browser.
  • The browser signs what it is handed, sends what it is handed, and reports the hash back.

Everything else in this module follows from that sentence. It is enforced rather than remembered: spec/onchain/no_client_side_chain_logic_spec.rb greps the browser code for calldata encoding, unit conversion, message construction, Number() on uint256 values, and direct chain reads.

Two consequences worth understanding:

connect-src stays 'self' for reads. Because every read goes through /onchain/rpc on your own origin, the Content Security Policy does not need to name an RPC provider, whatever provider you configure. That endpoint's method allowlist is its security model: without it, it is an open relay anyone on the internet can point at your provider account.

A visitor's address is never handed to a third party on page load, which is what would happen if the browser talked to an RPC provider directly.

What it costs

Adding this module is the only reason this kit needs Node. bin/dev runs an esbuild watcher alongside Rails, and the browser bundle is real:

Bundle Gzipped When it loads
external ~382 KB Any page with the wallet island
identity/Basename ~468 KB Only when an ENS or Basename is rendered
privy ~865 KB Only if you switch to embedded wallets

bin/js-budget prints these on every bin/check and fails if one exceeds its ceiling, so a dependency that doubles the bundle is a red build rather than something nobody notices.

If you install it and change your mind, script/onchain remove is the exact inverse, and script/verify-shape.sh onchain proves the tree is clean afterwards.

Where to go next

  • Wallet Sign-In: how SIWE works here and why the cold-signup flow has two steps.
  • Chains and Reads: the Chain adapter, the RPC proxy, and the offline fake.
  • Swaps: quoting through 0x, and the fake that deliberately cannot be signed.
  • Token Offerings: registering a token, and why deploying one is not built.
  • Token Gating: using a token balance instead of a subscription.
  • Extending It: adding an onchain action without breaking the boundary.