Deployment instructions: see DEPLOYMENT.md.
Provisioning routes/workflow/deployment guide: see PROVISIONING_GUIDE.md.
Thin API clients: clients/python and clients/typescript.
Verus contentmultimap storage implementation: CONTENTMULTIMAP_STORAGE_GUIDE.md.
Phased TDD storage plan: STORAGE_IMPLEMENTATION_GUIDE.md.
Active docs:
README.md(service overview and local usage)DEPLOYMENT.md(runtime deployment and operations)PROVISIONING_GUIDE.md(provisioning routes, workflow, and deployment)MCP_GUIDE.md(MCP integration and usage)mcp_server/python/README.md(MCP server package details)
Redundant or historical docs:
PROVISIONING_REFACTOR_PLAN.md(historical migration plan; superseded byPROVISIONING_GUIDE.md)PROVISIONING_IMPLEMENTATION_PLAN.md(historical implementation design; superseded byPROVISIONING_GUIDE.md)
MCP tools currently implemented:
create_identityget_identity_request_statuswait_for_identity_completionlist_recent_identity_failuresrequeue_identity_webhook
Run MCP server from repo root:
uv sync
IDCREATE_BASE_URL="http://localhost:5003" \
IDCREATE_API_KEY="key1" \
IDCREATE_TIMEOUT_SECONDS="15" \
uv run python mcp_server/python/server.pyMCP-focused tests:
uv run pytest tests/test_mcp_tools.py tests/test_mcp_server_smoke.py -qProvisioning HTTP integration tests (default path):
./scripts/run_provisioning_http_tests.shThis command:
- starts
svc-provisioningfromdocker-compose.yaml - runs provisioning tests with
PROVISIONING_ADAPTER_MODE=http - uses
PROVISIONING_SERVICE_URL=http://127.0.0.1:5055
Operational tips:
- Keep
IDCREATE_API_KEYonly in MCP runtime environment. - Use bounded polling values with
wait_for_identity_completion(reasonabletimeout_secondsandpoll_seconds). - Triage failed requests with
list_recent_identity_failuresbefore retrying webhook delivery. - Requeue webhook only for terminal requests using
requeue_identity_webhook. - If you see
ModuleNotFoundError: No module named 'mcp', runuv sync.
For full MCP details, see MCP_GUIDE.md and mcp_server/python/README.md.
uv init --app
uv add fastapi --extra standard
uv run fastapi dev_or_run file.py --port 5003
uv add --dev pytestuv sync
uv run fastapi dev id_create_service.py --port 5003If you are replacing SQLite and do not need to keep existing data, set DATABASE_URL and deploy.
When DATABASE_URL is set to a Postgres DSN, runtime DB access uses PostgreSQL.
If DATABASE_URL is unset, the service continues using SQLite via REGISTRAR_DB_PATH.
Example:
DATABASE_URL="postgresql://postgres:postgres@127.0.0.1:5432/idcreate"Notes:
- On startup, tables are created automatically in the target database.
- Existing SQLite files are not migrated.
- Recommended for concurrent webhook/worker traffic to avoid SQLite lock contention.
Swagger/OpenAPI:
http://localhost:5003/docshttp://localhost:5003/openapi.json
- Add a package:
uv add <package_name> - Add multiple:
uv add qrcode pillow - Add a dev dependency:
uv add --dev pytest - Remove a package:
uv remove <package_name> - Sync environment:
uv sync(Run this if you manually editpyproject.toml)
RPC connections are now controlled by environment flags per daemon.
- Set
<daemon>_rpc_enabled=trueto enable a daemon. - If a daemon is disabled (or the flag is missing), it is not loaded into
DAEMON_CONFIGS. - If a daemon is enabled, all corresponding RPC vars must be set:
<daemon>_rpc_user,<daemon>_rpc_password,<daemon>_rpc_port, and<daemon>_rpc_host.
Example for VRSC:
verusd_vrsc_rpc_enabled=true
verusd_vrsc_rpc_user=...
verusd_vrsc_rpc_password=...
verusd_vrsc_rpc_port=...
verusd_vrsc_rpc_host=...The /health endpoint checks RPC connectivity by calling getinfo.
- Configure daemon name with
HEALTH_RPC_DAEMON(default:verusd_vrsc). - Pass
native_coinquery parameter (for exampleVRSC,VARRR,VDEX,CHIPS) to select daemon by native ticker. - The ticker-based check only considers enabled daemons from
DAEMON_CONFIGS. - If RPC is reachable,
/healthreturns200with RPCinfo. - If no daemon matches the requested ticker, or RPC is disabled/unconfigured/unreachable,
/healthreturns503.
Examples:
GET /health
GET /health?native_coin=VRSC
This service follows an async workflow:
POST /api/registerbroadcasts name commitment and stores request state.- Worker advances state in background.
GET /api/status/{request_id}returns current lifecycle status.
Current lifecycle statuses:
pending_rnc_confirmready_for_idridr_submittedcompletefailed
POST /api/register requires header X-API-Key.
Configure one or more keys via env:
REGISTRAR_API_KEYS="key1,key2"{
"name": "alice",
"parent": "bitcoins.vrsc",
"native_coin": "VRSC",
"primary_raddress": "RaliceAddress",
"referral_id": "referrer@",
"webhook_url": "https://example.com/hook",
"webhook_secret": "optional-per-request-secret"
}Health check by ticker:
curl -s "http://localhost:5003/health?native_coin=VRSC"Start registration:
curl -s -X POST "http://localhost:5003/api/register" \
-H "Content-Type: application/json" \
-H "X-API-Key: key1" \
-d '{
"name": "alice",
"parent": "bitcoins.vrsc",
"native_coin": "VRSC",
"primary_raddress": "RaliceAddress",
"referral_id": "referrer@",
"webhook_url": "https://example.com/hook",
"webhook_secret": "my-webhook-secret"
}'Check status:
curl -s "http://localhost:5003/api/status/<request_id>"Requeue webhook delivery (terminal requests only):
curl -s -X POST "http://localhost:5003/api/webhook/requeue/<request_id>" \
-H "X-API-Key: key1"List recent failed registrations (ops visibility):
curl -s "http://localhost:5003/api/registrations/failures?limit=20" \
-H "X-API-Key: key1"Monitoring-friendly example (count + ids):
curl -s "http://localhost:5003/api/registrations/failures?limit=50" \
-H "X-API-Key: key1" | jq '{count, ids: [.items[].id]}'Ops triage example (errors + retry fields):
curl -s "http://localhost:5003/api/registrations/failures?limit=50" \
-H "X-API-Key: key1" | jq '.items[] | {id, error_message, attempts, next_retry_at, webhook_last_error}'Run one sweep manually:
uv run python worker.pyCron example (every minute):
* * * * * cd /home/mylo/dev/sf/svc-idcreate && /home/mylo/.local/bin/uv run python worker.py >> /var/log/svc-idcreate-worker.log 2>&1Use POST /api/currency/plan for unified simple/fractional workflows and
GET /api/currency/plan/template for a current reference payload.
Fractional plan identity flags:
fractional.identity_exists:trueskips fractional VerusID namecommit/register steps.- Funding and contribution transfer steps still run before fractional define.
fractional.reserves[].identity_exists:trueskips reserve VerusID namecommit/register steps for that reserve.- Reserve funding/define and contribution transfer still run.
Fractional reserve supply rule:
fractional.reserves[].supplyis required only whenfractional.create_reserves=true.
Example fractional section:
{
"initial_supply": 325000,
"id_registration_fees": 777,
"id_referral_levels": 3,
"start_block": 1057000,
"native": {
"name": "VRSCTEST",
"weight": 0.55,
"initial_contribution": 20
},
"reserves": [
{
"name": "SPORTS",
"supply": 80000,
"identity_exists": true,
"weight": 0.2,
"initial_contribution": 0.1
}
],
"define_funding_amount": 200.001,
"create_reserves": true,
"prepare_fractional_identity": true,
"identity_exists": true
}When status reaches complete or failed, worker attempts POST delivery to webhook_url.
Delivery headers:
Content-Type: application/jsonX-Webhook-Event: registration.completeorregistration.failedX-Webhook-Signature: sha256=<hmac>when a secret is available
Secret resolution order:
webhook_secretfrom registration request- fallback env
WEBHOOK_SIGNING_SECRET
Webhook retry env settings:
WEBHOOK_TIMEOUT_SECONDS=5
WEBHOOK_MAX_RETRIES=5
WEBHOOK_RETRY_BASE_SECONDS=15Use this pattern on the receiving server to verify X-Webhook-Signature.
import hmac
import hashlib
import json
from fastapi import FastAPI, Header, HTTPException, Request
app = FastAPI()
WEBHOOK_SECRET = "my-webhook-secret"
@app.post("/webhooks/verusid")
async def verusid_webhook(request: Request, x_webhook_signature: str | None = Header(default=None)):
raw_body = await request.body()
payload = json.loads(raw_body)
expected = "sha256=" + hmac.new(
WEBHOOK_SECRET.encode("utf-8"),
json.dumps(payload, sort_keys=True).encode("utf-8"),
hashlib.sha256,
).hexdigest()
if not x_webhook_signature or not hmac.compare_digest(x_webhook_signature, expected):
raise HTTPException(status_code=403, detail="Invalid webhook signature")
# process payload here
return {"ok": True}Quick local receiver test:
curl -s -X POST "http://localhost:8001/webhooks/verusid" \
-H "Content-Type: application/json" \
-H "X-Webhook-Signature: sha256=<signature>" \
-d '{"event":"registration.complete","request_id":"abc"}'REGISTRAR_DB_PATH=registrar.db
REGISTRAR_API_KEYS=key1,key2
SOURCE_OF_FUNDS=RsourceFundsAddr
WORKER_MAX_RETRIES=5
WORKER_RETRY_BASE_SECONDS=15
WEBHOOK_TIMEOUT_SECONDS=5
WEBHOOK_MAX_RETRIES=5
WEBHOOK_RETRY_BASE_SECONDS=15
WEBHOOK_SIGNING_SECRET=
HEALTH_RPC_DAEMON=verusd_vrscuv run pytest -q
uv run pytest tests/test_worker.py -q