siwa-server is the shared Sign-In With Anything service for the Regent apps, run by
Regents Labs on Fly.io. It answers one kind of question — is this request really from this
agent, for this audience, and has it been seen before — and nothing else.
Important
This service holds signing keys. It runs an encrypted key store on a mounted volume and exposes internal signer routes. Treat every deployment and every secret here as security-critical.
It owns:
- public SIWA sign-in routes under
/api/shared/siwa - protected request verification
- internal keyring routes under
/api/shared/keyringfor signer operations - health, metrics, and the served shared services contract
- strict receipt, request-expiry, and replay checks for shared agent sign-in
It does not own product-specific app logic or Regent account registration. The platform calls this service over HTTP rather than serving shared SIWA itself, and owns Regent staking routes and client generation. Techtree proof and Fold policy stay in Techtree. SIWA only proves request identity and audience when a product route needs a signed agent request.
The shared services HTTP contract is owned by regents-cli, at
docs/regent-services-contract.openapiv3.yaml in that repository. The served copy at
priv/static/regent-services-contract.openapiv3.yaml here is a generated artifact and must
match it.
You need Elixir and a local PostgreSQL.
mix setup
mix test
mix phx.servermix setup fetches dependencies, then creates, migrates, and seeds the database.
Note
Sign-in state lives in this service's own PostgreSQL database: nonces, receipts, and replay records, nothing else. It holds no product data. What leaves the machine in production: the configured Base RPC endpoint, for on-chain checks.
client surfaces
ios mobile app, wallet, action signing
regents-cli operator control surface
regents-techtree-hermes-plugin Hermes mission-control tab
│
▼
platform
ash-platform Phoenix, LiveView, Ash: web, API, product domains
│
▼
services and chain
siwa-server agent request signing, nonce and replay state ◀ this repository
media-web hosted card images and video
fly-sentinel operator health checks
regent-contracts canonical Solidity, ABIs, deployment records
autolaunch-contracts frozen Autolaunch V1 Solidity
shared libraries and standalone tools
elixir-utils SIWA, ENS, XMTP, cache, Credo checks
design-system tokens and regent_ui components
python-cli offline Techtree skill-tree inspection
videocontrol video project and timeline workflows
| Route | Method | Purpose |
|---|---|---|
/ |
GET | Service root. |
/api/shared/siwa/nonce |
POST | Issue a nonce for a sign-in attempt. |
/api/shared/siwa/verify |
POST | Verify a sign-in. |
/api/shared/siwa/http-verify |
POST | Verify a signed HTTP request. |
/healthz |
GET | Liveness. |
/readyz |
GET | Readiness. |
/metrics |
GET | Prometheus scrape endpoint. |
/regent-services-contract.openapiv3.yaml |
GET | The served shared services contract. |
The current Agent account shape is mandatory across these routes: wallet, chain, registry address, token ID, audience, nonce, and the request body when a protected request has one. Protected request verification also expects the signed path to include the query string when one is present, and callers must send the app audience that owns the request.
Malformed expiry values, expired requests, receipt-binding mismatches, and replayed requests must fail closed. The SIWA library and the service tests cover these cases.
| Route | Method | Purpose |
|---|---|---|
/api/shared/keyring/health |
GET | Keyring liveness. |
/api/shared/keyring/create-wallet |
POST | Create a wallet in the key store. |
/api/shared/keyring/has-wallet |
POST | Ask whether a wallet exists. |
/api/shared/keyring/get-address |
POST | Read a wallet address. |
/api/shared/keyring/sign-message |
POST | Sign a message. |
/api/shared/keyring/sign-raw-message |
POST | Sign raw bytes. |
/api/shared/keyring/sign-transaction |
POST | Sign a transaction. |
/api/shared/keyring/sign-authorization |
POST | Sign an authorization. |
Warning
These routes produce real signatures from real keys. They are internal by design. Do not expose them beyond the private network, and do not point them at a key store you would not want used.
| Variable | Required | What it is for |
|---|---|---|
DATABASE_URL |
Yes in production | PostgreSQL connection string for the sign-in state. |
SECRET_KEY_BASE |
Yes in production | Endpoint signing secret. |
PHX_HOST |
Yes in production | Public hostname the endpoint builds URLs from. |
SIWA_RECEIPT_SECRET |
Yes in production | Secret that binds and validates receipts. |
KEYSTORE_PASSWORD |
Yes in production | Password for the encrypted key store. |
KEYRING_PROXY_SECRET |
Yes in production | Shared secret guarding the internal keyring routes. |
BASE_RPC_URL |
Yes in production | Base mainnet JSON-RPC endpoint. |
| Variable | Default | What it is for |
|---|---|---|
PHX_SERVER |
unset | true starts the HTTP server in a release. |
PORT |
4000 |
HTTP port. The Fly deployment uses 8080. |
POOL_SIZE |
10 |
Database connection pool size. |
ECTO_IPV6 |
unset | true connects to PostgreSQL over IPv6. |
DNS_CLUSTER_QUERY |
unset | DNS query used for clustering. |
SIWA_DOMAIN |
regent.cx |
Domain expected in the canonical SIWA sign-in message. |
SIWA_VERIFY_URI |
https://regent.cx/api/shared/siwa/verify |
URI expected in the canonical SIWA sign-in message. |
SIWA_NONCE_TTL_SECONDS |
300 |
How long an issued nonce stays valid. |
SIWA_RECEIPT_TTL_SECONDS |
3600 |
How long a receipt stays valid. |
SIWA_HTTP_SIGNATURE_TOLERANCE_SECONDS |
300 |
Clock skew allowed on a signed HTTP request. |
SIWA_WALLET_ORIGINS |
unset | Approved audience=origin pairs for EOA wallet sign-in, comma separated. Unset disables it. |
SIWA_CLEANUP_ENABLED |
true |
Whether expired nonce and replay rows are swept. |
SIWA_CLEANUP_INTERVAL_MS |
60000 |
How often that sweep runs. |
SIWA_CLEANUP_BATCH_SIZE |
1000 |
Rows removed per sweep. |
KEYSTORE_BACKEND |
encrypted_file |
Key store backend. encrypted_file is the only accepted value; anything else stops the boot. |
KEYSTORE_PATH |
/data/siwa-server-keystore.bin |
Where the encrypted key store lives. |
Warning
SIWA_RECEIPT_SECRET, KEYSTORE_PASSWORD, and KEYRING_PROXY_SECRET are the three values
that keep this service honest. Supply them from the deployment's secret store. Never commit
one, and never write one into a checked-in example file.
One command must pass before a change is proposed:
mix precommitIt compiles with warnings as errors, checks unused dependency locks and formatting, runs
Sobelow, verifies the served services contract and the release packaging, holds the
compile-connected xref graph at zero, and runs the test suite with warnings as errors. It
needs a local PostgreSQL, because the test alias creates and migrates a database first.
Warning
fly.toml deploys this as the Fly.io app siwa-server. Its release command runs database
migrations on every deploy, it keeps one machine running, and it mounts /data for the
encrypted key store — so a deploy touches both the live sign-in database and the volume the
signing keys live on. Confirm the target before running one.
The launch and maintenance checklist is in docs/regent-local-and-fly-launch-testing.md.
| Repository | What it is | What it deliberately does not do |
|---|---|---|
ash-platform |
The Phoenix, LiveView, and Ash application: public web pages, the HTTP API, product domains, human identity, billing, and the Techtree and Autolaunch product areas. | It does not hold Solidity source or user signing keys; wallet actions remain browser-signed. |
autolaunch-contracts |
A clean-room Solidity implementation of the founder-frozen Autolaunch V1 system, controlled by its own SPEC.md. |
It authorises no deployment, signature, or value movement; the older Autolaunch code in regent-contracts is historical reference only. |
design-system |
The shared Regent visual language: the style guide, design tokens, logos, fonts, and the regent_ui Phoenix component library. |
Shared components never own product workflow state, authorisation decisions, money movement, or product database behaviour. |
elixir-utils |
A collection of standalone Elixir libraries used across the family: SIWA, ENS, XMTP, a cache, agentbook helpers, and the in-house credo_ash lint checks. |
Each package is a library only; none of them runs a service or holds product behaviour. |
fly-sentinel |
A small Phoenix service that reports Fly.io observability and operator preview checks. | It observes and reports; it does not deploy, scale, or change any other application. |
ios |
The Expo and React Native mobile app: the mobile wallet, action signing, and mobile Regent records. | It consumes the platform HTTP contracts and owns no server-side product logic. |
media-web |
A standalone Phoenix service that serves hosted Regents card images and video files from media.regents.sh. |
It only serves bytes over HTTP; it holds no identity, database, or product logic. |
python-cli |
The installable regents-techtree Python package, whose shipped surface is a deterministic offline inspection of one champion/challenger skill-tree pair. |
It does not evaluate or execute an agent, and it makes no network calls once its locked dependencies are installed. |
regent-contracts |
The canonical home for Regent Solidity source, Foundry tests, deployment scripts, verified deployment records, ABIs, and the chain-contract manifest. | It holds no HTTP or CLI contracts, Ash resources, workflow logic, UI, or projection workers. |
regents-cli |
The operator control surface: the regents command line tool, its generated bindings, and its local runtime. |
It drives the platform over published contracts and owns no product database or on-chain authority. |
regents-techtree-hermes-plugin |
The Hermes plugin that presents Techtree mission control across Forge, Techtree Verify, and Uplift. | It is presentation only: no second task store, no private Verify database, no identity model, no payment system, and no Hermes runtime of its own. |
videocontrol |
A separate product: video project workflows, timeline editing, preview rendering, and Codex plugin media control. | It shares the house style but no runtime, database, or contract with the Regent platform. |
MIT — see LICENSE.
The optional EOA flow uses POST /api/shared/siwa/wallet/nonce and
POST /api/shared/siwa/wallet/verify. Sign the exact ERC-4361 message returned by
the nonce endpoint. Base (8453) is the only supported chain. Contract-wallet
ERC-1271/6492 signatures are not supported. No private key is sent to this service.
SIWA_WALLET_ORIGINS lists the approved audiences and the HTTPS origin each one signs
in from, as comma-separated audience=origin pairs, for example
patchbay=https://patchbay.help,keyfleet=https://keyfleet.example. Leaving it empty
disables this flow for every audience. Callers never choose an origin; only this table
does. The server binds the origin, audience, address, chain and expiry in the
challenge. An origin change invalidates outstanding challenges. Rate limits use the
existing nonce/verification buckets. This option does not grant product permissions.
The wallet is any secp256k1 key the agent controls. A key generated locally on the
agent's own machine, holding no funds and registered nowhere, is enough to sign in;
the address is the agent's identity for the audience. Tested reference clients for
Python and Node live in the SIWA library repository under siwa/siwa-elixir/agent/.
Wallet receipts have type siwa_wallet_receipt and proof wallet_signature.
HTTP verification returns an explicit principal of kind wallet, with no
agent_claims. Product consumers must explicitly support this principal and check
product ownership. Payment, human identity and registered-agent ownership remain
separate. Existing registered-agent routes do not accept omitted registry fields.
The nonce migration preserves existing agent rows and enforces disjoint wallet/agent constraints. Its rollback refuses to discard remaining wallet challenges. Both nonce consumption and request replay use database-clock expiration checks; invalid proof cannot consume a valid challenge.
For an isolated checkout, set REGENT_SIWA_ROOT to a frozen siwa/siwa-elixir
export. REGENT_RELEASE_CONTEXT identifies the matching Docker input directory
containing elixir-utils/; the packaging check validates actual dependency paths
against that context. A passing packaging check is not a built or deployed image.