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).
154 lines
6.8 KiB
Bash
154 lines
6.8 KiB
Bash
# floonet-strfry configuration. Copy to `.env` (for docker compose) and edit
|
|
# for your deployment. Every value shown is the built-in default, so an unset
|
|
# variable behaves exactly like the line below.
|
|
|
|
# --- Identity (the part you MUST change to run your own relay) ---
|
|
|
|
# Bare host this relay and its names live under: the `@domain` in
|
|
# `name@domain` and the domain Caddy obtains a TLS certificate for.
|
|
FLOONET_DOMAIN=floonet.example
|
|
|
|
# Public base URL clients actually reach. LOAD-BEARING: NIP-98 auth events
|
|
# are verified against `<FLOONET_BASE_URL><path>`, so this MUST be https://
|
|
# and its host MUST equal FLOONET_DOMAIN (a port is allowed). A wrong value
|
|
# silently breaks every authenticated call. The authority refuses to start
|
|
# if it and FLOONET_DOMAIN disagree.
|
|
FLOONET_BASE_URL=https://floonet.example
|
|
|
|
# Comma-separated relays advertised in /.well-known/nostr.json. Point this
|
|
# at your own wss:// URL (normally wss://FLOONET_DOMAIN).
|
|
FLOONET_RELAYS=wss://floonet.example
|
|
|
|
# Co-located names (FLOONET_AUTHORITY_COLOCATED): this compose stack is single
|
|
# domain, so names AND the relay are already served on FLOONET_DOMAIN
|
|
# (`name@FLOONET_DOMAIN` resolves) — on by default, nothing to set here. Only a
|
|
# SPLIT deploy that puts the relay and the authority on separate subdomains
|
|
# behind nginx needs to opt in; see "Co-locating names on the relay domain" in
|
|
# the README and deploy/us-east/colocated-authority.conf.
|
|
|
|
# --- The kind whitelist (the keystone) ---
|
|
|
|
# Comma-separated event kinds the relay stores. DEFAULT-DENY: anything not
|
|
# listed here is rejected at ingest. LEAVE THIS COMMENTED OUT to use the
|
|
# plugin's built-in default, which is the full 24-kind set the Goblin wallet
|
|
# and Magick Market need (DEFAULT_ALLOWED_KINDS in plugin/floonet_writepolicy.py):
|
|
# 0,1,3,5,7,13,14,16,17,1059,1111,10000,10002,10050,24133,27235,
|
|
# 30000,30003,30023,30078,30402,30405,30406,31990
|
|
# This includes the marketplace kinds (30402/30405/30406); do not drop them.
|
|
# Uncomment and edit ONLY to RESTRICT the relay to a smaller set. Any value
|
|
# you set here fully REPLACES the default, so an incomplete list silently
|
|
# rejects the kinds you omit. Restart the relay after changing it.
|
|
#FLOONET_ALLOWED_KINDS=0,1,3,5,7,13,14,16,17,1059,1111,10000,10002,10050,24133,27235,30000,30003,30023,30078,30402,30405,30406,31990
|
|
|
|
# --- Public-note lockdown (kinds 1, 30023) ---
|
|
|
|
# Public notes (kind 1 text notes, kind 30023 long-form articles) are
|
|
# accepted ONLY from the authors listed here. Everything else (profiles,
|
|
# gift wraps, marketplace kinds, lists) is unaffected. Closed by default:
|
|
# leave this empty and kinds 1/30023 are rejected for everyone, so random
|
|
# notes cannot be spammed to your relay. Comma-separated, each entry a hex
|
|
# pubkey OR an npub (your choice); invalid entries are logged and skipped.
|
|
FLOONET_AUTHORIZED_AUTHORS=
|
|
|
|
# --- Authentication (optional) ---
|
|
|
|
# Require NIP-42 AUTH before accepting writes. Set to true AND flip
|
|
# relay.auth.enabled to true in deploy/strfry/strfry.conf (strfry issues the
|
|
# challenges; the plugin enforces the requirement).
|
|
FLOONET_REQUIRE_AUTH=false
|
|
|
|
# --- Charge GRIN for your relay (optional; all off by default) ---
|
|
|
|
# off = everything free
|
|
# name = claiming a name@domain costs FLOONET_NAME_PRICE_GRIN
|
|
# write = publishing to the relay needs a one-time payment of
|
|
# FLOONET_WRITE_PRICE_GRIN (clients must also NIP-42 AUTH, since
|
|
# payment grants are per pubkey)
|
|
FLOONET_PAY_MODE=off
|
|
|
|
# Prices, in GRIN (decimals allowed, e.g. 1.5). You set the price; edit and
|
|
# restart, no code change.
|
|
FLOONET_NAME_PRICE_GRIN=0
|
|
FLOONET_WRITE_PRICE_GRIN=0
|
|
|
|
# Your GoblinPay server (https://code.gri.mw/GRIN/GoblinPay). The authority
|
|
# creates invoices there and payers land on its hosted pay page.
|
|
GOBLINPAY_URL=
|
|
# The GoblinPay API token (GP_API_TOKEN on the GoblinPay side).
|
|
GOBLINPAY_TOKEN=
|
|
# Optional: GoblinPay webhook secret. When set, point a GoblinPay webhook at
|
|
# https://FLOONET_DOMAIN/api/v1/goblinpay/webhook and payments confirm
|
|
# instantly instead of on the next status poll.
|
|
GOBLINPAY_WEBHOOK_SECRET=
|
|
|
|
# Seconds the write policy plugin caches paid-status verdicts.
|
|
FLOONET_PAID_CACHE_SECS=60
|
|
|
|
# --- Tor onion (optional) ---
|
|
|
|
# Uncomment to ALSO run a Tor onion in front of the relay (the `tor` compose
|
|
# service), so wallets can reach it over Tor without a Tor exit hop. The onion
|
|
# forwards straight to the relay's websocket listener; see deploy/tor/torrc.
|
|
# tor prints the .onion address to its logs on first start and stores its key
|
|
# on the tor-data volume (back it up: losing it rotates the address). Publish
|
|
# the .onion so wallets can find it.
|
|
#COMPOSE_PROFILES=tor
|
|
|
|
# --- Name authority policy tunables ---
|
|
|
|
# Seconds a key must wait to claim a new name after releasing one (anti-churn).
|
|
FLOONET_NAME_CHANGE_COOLDOWN_SECS=600
|
|
|
|
# Max age (seconds) of an accepted NIP-98 auth event.
|
|
FLOONET_AUTH_MAX_AGE_SECS=60
|
|
|
|
# Allowed name length, in characters.
|
|
FLOONET_NAME_MIN=3
|
|
FLOONET_NAME_MAX=20
|
|
|
|
# --- Rate-limit ceilings (per X-Real-IP) ---
|
|
|
|
# Read endpoints: max requests per window / window length in seconds.
|
|
FLOONET_READ_RATE_MAX=120
|
|
FLOONET_READ_RATE_WINDOW_SECS=60
|
|
|
|
# Write endpoints (register/release/quote).
|
|
FLOONET_WRITE_RATE_MAX=10
|
|
FLOONET_WRITE_RATE_WINDOW_SECS=3600
|
|
|
|
# --- Name transfers (the name marketplace; off by default) ---
|
|
#
|
|
# A seller lodges a signed offer (kind-3402 event) 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 and independent of FLOONET_PAY_MODE: the buyer
|
|
# pays the seller wallet to wallet, the authority only verifies and swaps one
|
|
# name row, and GoblinPay is never involved. Default OFF: when off, the
|
|
# /api/v1/transfer routes are not mounted at all (they 404). See the README and
|
|
# docs-notes/name-transfer-spec.md.
|
|
|
|
# Enable the transfer routes.
|
|
#FLOONET_TRANSFERS=false
|
|
|
|
# Grin node foreign-API base URL(s), comma-separated, used to confirm payment
|
|
# kernels (get_kernel + get_tip). REQUIRED when FLOONET_TRANSFERS is true - the
|
|
# authority refuses to start without it. Multiple endpoints are tried in order.
|
|
#FLOONET_GRIN_NODE_URL=https://api.grin.money/v2/foreign
|
|
|
|
# Confirmations a payment kernel needs before a claim is accepted.
|
|
#FLOONET_TRANSFER_MIN_CONF=10
|
|
|
|
# Longest offer time-to-live in seconds (an offer's expiration must be within
|
|
# [now, now + this] when lodged). Default 30 days.
|
|
#FLOONET_TRANSFER_MAX_OFFER_TTL=2592000
|
|
|
|
# Grace window in seconds after an offer is revoked or expires during which a
|
|
# claim whose payment settled before the offer died is still honored. Default 1 day.
|
|
#FLOONET_TRANSFER_CLAIM_GRACE=86400
|
|
|
|
# --- Optional ---
|
|
|
|
# Path to a file of additional reserved names (one per line, # comments).
|
|
# Extends the built-in generic list and your domain's own labels (which are
|
|
# always reserved). Leave unset to use only those defaults.
|
|
#FLOONET_RESERVED_FILE=/etc/floonet-authority.reserved
|