6143fce8ac
Ports the Goblin Name Transfer Protocol v1 into the colocated Floonet name
authority: a seller lodges a signed kind-3402 offer to reassign a name to a
buyer for a Grin price, and the buyer claims it with an on-chain payment proof.
Strictly non-custodial: the buyer pays the seller wallet to wallet, the
authority verifies two artifacts and swaps one names row. Keys never move.
New routes, mounted only when FLOONET_TRANSFERS is set (else they 404):
POST /api/v1/transfer/offer NIP-98 (seller), body {offer}
GET /api/v1/transfer/offer/{id} public, read-limited, CORS *
DELETE /api/v1/transfer/offer/{id} NIP-98 (seller)
POST /api/v1/transfer/claim NIP-98 (buyer), body {offer_id, proof}
Claim verification runs in the spec's order: NIP-98 + replay set, offer exists
(consumed -> idempotent 200 or 409), revoked/expired grace + end_height gate,
buyer key == p tag, seller still owner, both proof signatures over the canonical
73-byte message (amount_BE || excess || sender), recipient address and exact
amount match the offer, kernel confirmed on chain >= min_conf, excess never used
(durable UNIQUE), buyer holds no name (exempt from the name-change cooldown).
The reassignment is one atomic SQLite transaction that also records the transfer,
consumes the offer, and arms the seller's cooldown. The lodge ambiguity rule
keys on proof_address alone among live offers (offer_ambiguous).
The transfer path is fully independent of the fork's GoblinPay paid-names
integration: zero calls into src/paid.rs, no GOBLINPAY_URL dependency, pure
in-process crypto plus the read-only Grin node foreign API. Transfers and paid
names toggle in any combination. Config follows the repo's FLOONET_ pattern
(from_env/validate/summary/for_test), fail-fast when transfers are on without
FLOONET_GRIN_NODE_URL, and is documented in .env.example, docker-compose.yml,
and the us-east env template.
Tests: 27 transfer integration tests (offer state machine, revoke, expiry,
late-claim grace and end_height rules, every claim failure mode with real
ed25519 proofs, successful reassign + idempotent retry + cooldown, plus a
transfers-with-pay-mode-active combination) adapted to tests/http.rs helpers,
plus proof, node and config unit tests. cargo test green (32 + 17 + 27).
124 lines
4.8 KiB
YAML
124 lines
4.8 KiB
YAML
# A full, self-contained Floonet relay with automatic HTTPS.
|
|
#
|
|
# cp .env.example .env # edit FLOONET_DOMAIN etc.
|
|
# docker compose up -d
|
|
#
|
|
# gives you:
|
|
# - relay : stock strfry (built from source at a pinned ref) + the
|
|
# Floonet write policy plugin (default-deny kind whitelist,
|
|
# optional NIP-42 and paid-write gates)
|
|
# - authority : the bundled name authority (name@domain -> pubkey, with
|
|
# optional paid names / paid write access via GoblinPay)
|
|
# - caddy : auto-TLS reverse proxy terminating HTTPS for both
|
|
# - tor : OPTIONAL Tor onion in front of the relay
|
|
# (COMPOSE_PROFILES=tor), so wallets can reach it over Tor
|
|
# without a Tor exit hop. See deploy/tor/torrc.
|
|
#
|
|
# Set FLOONET_DOMAIN / FLOONET_BASE_URL / FLOONET_RELAYS in `.env` (copy
|
|
# .env.example) BEFORE bringing it up: Caddy obtains a certificate for
|
|
# FLOONET_DOMAIN, so DNS must already point at this host.
|
|
|
|
services:
|
|
relay:
|
|
build:
|
|
context: .
|
|
dockerfile: deploy/strfry/Dockerfile
|
|
image: floonet-strfry:latest
|
|
restart: unless-stopped
|
|
environment:
|
|
# Write policy plugin configuration (the plugin inherits strfry's
|
|
# environment). See plugin/floonet_writepolicy.py and .env.example.
|
|
FLOONET_ALLOWED_KINDS: ${FLOONET_ALLOWED_KINDS:-0,3,5,13,1059,10002,10050,27235}
|
|
FLOONET_REQUIRE_AUTH: ${FLOONET_REQUIRE_AUTH:-false}
|
|
FLOONET_PAY_MODE: ${FLOONET_PAY_MODE:-off}
|
|
FLOONET_AUTHORITY_URL: http://authority:8191
|
|
FLOONET_PAID_CACHE_SECS: ${FLOONET_PAID_CACHE_SECS:-60}
|
|
volumes:
|
|
- relay-data:/strfry-db
|
|
- ./deploy/strfry/strfry.conf:/app/strfry.conf:ro
|
|
expose:
|
|
- "7777"
|
|
# Bound the relay's footprint so an unauthenticated subscription/ingest
|
|
# flood can't starve the authority or proxy on the same host.
|
|
deploy:
|
|
resources:
|
|
limits:
|
|
memory: 512M
|
|
cpus: "1.0"
|
|
|
|
authority:
|
|
build: ./name-authority
|
|
image: floonet-name-authority:latest
|
|
restart: unless-stopped
|
|
environment:
|
|
# Identity. Override these in .env for your own deployment.
|
|
FLOONET_DOMAIN: ${FLOONET_DOMAIN:-floonet.example}
|
|
FLOONET_BASE_URL: ${FLOONET_BASE_URL:-https://floonet.example}
|
|
FLOONET_RELAYS: ${FLOONET_RELAYS:-wss://floonet.example}
|
|
# In-container paths (persisted on the named volume).
|
|
FLOONET_NAMES_BIND: 0.0.0.0:8191
|
|
FLOONET_NAMES_DB: /data/names.db
|
|
# Paid mode (all optional; free by default). See .env.example.
|
|
FLOONET_PAY_MODE: ${FLOONET_PAY_MODE:-off}
|
|
FLOONET_NAME_PRICE_GRIN: ${FLOONET_NAME_PRICE_GRIN:-0}
|
|
FLOONET_WRITE_PRICE_GRIN: ${FLOONET_WRITE_PRICE_GRIN:-0}
|
|
GOBLINPAY_URL: ${GOBLINPAY_URL:-}
|
|
GOBLINPAY_TOKEN: ${GOBLINPAY_TOKEN:-}
|
|
GOBLINPAY_WEBHOOK_SECRET: ${GOBLINPAY_WEBHOOK_SECRET:-}
|
|
# Name transfers (the name marketplace; off by default, strictly
|
|
# non-custodial, no GoblinPay involvement). Independent of the pay mode.
|
|
# When on, FLOONET_GRIN_NODE_URL is REQUIRED (get_kernel/get_tip). See
|
|
# .env.example and docs-notes/name-transfer-spec.md.
|
|
FLOONET_TRANSFERS: ${FLOONET_TRANSFERS:-false}
|
|
FLOONET_GRIN_NODE_URL: ${FLOONET_GRIN_NODE_URL:-}
|
|
FLOONET_TRANSFER_MIN_CONF: ${FLOONET_TRANSFER_MIN_CONF:-10}
|
|
FLOONET_TRANSFER_MAX_OFFER_TTL: ${FLOONET_TRANSFER_MAX_OFFER_TTL:-2592000}
|
|
FLOONET_TRANSFER_CLAIM_GRACE: ${FLOONET_TRANSFER_CLAIM_GRACE:-86400}
|
|
volumes:
|
|
- authority-data:/data
|
|
expose:
|
|
- "8191"
|
|
|
|
caddy:
|
|
image: caddy:2
|
|
restart: unless-stopped
|
|
depends_on:
|
|
- authority
|
|
- relay
|
|
environment:
|
|
FLOONET_DOMAIN: ${FLOONET_DOMAIN:-floonet.example}
|
|
ports:
|
|
- "80:80"
|
|
- "443:443"
|
|
volumes:
|
|
- ./deploy/Caddyfile:/etc/caddy/Caddyfile:ro
|
|
- ./deploy/landing:/srv/landing:ro
|
|
- caddy-data:/data
|
|
- caddy-config:/config
|
|
|
|
# Optional Tor onion service in front of the relay. Off unless the `tor`
|
|
# profile is active (set COMPOSE_PROFILES=tor in .env). It runs a plain
|
|
# system tor daemon whose hidden service forwards straight to the relay's
|
|
# websocket listener (no TLS on that hop: the onion transport is already
|
|
# encrypted and authenticated end to end). Wallets can then reach the relay
|
|
# over Tor without a Tor exit hop. The .onion address is derived from a key
|
|
# in the tor-data volume and printed to the tor logs on first start; back
|
|
# the volume up, since losing it rotates the address. See deploy/tor/torrc.
|
|
tor:
|
|
build: ./deploy/tor
|
|
image: floonet-tor:latest
|
|
restart: unless-stopped
|
|
profiles: ["tor"]
|
|
depends_on:
|
|
- relay
|
|
volumes:
|
|
- ./deploy/tor/torrc:/etc/tor/torrc:ro
|
|
- tor-data:/var/lib/tor
|
|
|
|
volumes:
|
|
relay-data:
|
|
authority-data:
|
|
caddy-data:
|
|
caddy-config:
|
|
tor-data:
|