Anchoring Service¶
HTTP service meant to anchor Citizenship Bundle hashes to L2 blockchains (Base, Arbitrum, Optimism), individually or in batches under a Merkle root. Today it only records them locally (see the note below).
On-chain submission is not implemented yet. Hashes and batch roots are recorded locally with
tx_hash: nullandonchain: false; the service never makes up a transaction hash.ANCHOR_RPC_URL,ANCHOR_PRIVATE_KEYandANCHOR_CONTRACTare not used yet. State is in memory (see Persistence).
Quickstart¶
# Start the service, from the repo root after installing the root dependencies
node services/anchor/index.js
# Or with Docker, from the repo root
docker compose -f docker/docker-compose.yml up anchor
The service listens on http://localhost:3001 by default.
Endpoints¶
GET /health¶
Service health check.
total_anchored counts local records, one per batch, not hashes. A healthy answer says nothing about
anything on chain.
POST /anchor¶
Submit a bundle hash for anchoring: sha256: followed by 64 lowercase hex characters (the prefix
is optional). Anything else, or a body over 1 MB, is a 400.
curl -X POST http://localhost:3001/anchor \
-H "Content-Type: application/json" \
-d '{"bundle_hash": "sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"}'
Individual mode — Records the hash at once, locally: anchored: true means recorded by this
service, not on chain (onchain: false, tx_hash: null):
{
"anchored": true,
"hash": "sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
"count": 1,
"timestamp": "2025-01-01T00:00:00.000Z",
"tx_hash": null,
"onchain": false,
"chain": "base"
}
Batch mode — Accepts and enqueues (HTTP 202):
{
"accepted": true,
"bundle_hash": "sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
"position": 3,
"hint": "Will be anchored in next batch (interval: 60000ms)"
}
GET /status/:hash¶
Query the status of an anchored hash.
curl http://localhost:3001/status/sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08
{
"anchored": true,
"hash": "sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
"count": 1,
"timestamp": "2025-01-01T00:00:00.000Z",
"tx_hash": null,
"onchain": false,
"chain": "base"
}
If not anchored but pending:
A record is found only by the hash it stores. In individual mode, and for a batch of one hash, that is
the submitted hash (with the sha256: prefix). For a batch of several hashes it is only their Merkle
root, so each of those hashes then answers {"anchored": false, "pending": false}; there is no
per-hash inclusion proof yet. While pending, a hash is matched as it was submitted (with or without
the prefix). Records are in memory and a restart loses them.
GET /anchored¶
List all anchored records.
{
"records": [
{ "hash": "sha256:...", "count": 1, "timestamp": "...", "tx_hash": null, "onchain": false, "chain": "base" }
]
}
POST /flush¶
Force a flush of the pending batch (batch mode only). Requires the operator token; without
OPERATOR_TOKEN configured the endpoint stays locked (401).
Configuration¶
Environment Variables¶
| Variable | Default | Description |
|---|---|---|
PORT |
3001 |
HTTP port |
ANCHOR_MODE |
"batch" |
Anchoring mode: "individual" or "batch" |
BATCH_INTERVAL_MS |
60000 |
Automatic flush interval in ms (batch mode) |
ANCHOR_RPC_URL |
— | JSON-RPC URL of the L2 node (not used yet; a warning is logged when set) |
ANCHOR_PRIVATE_KEY |
— | Wallet private key for signing transactions (not used yet) |
ANCHOR_CONTRACT |
— | Address of the deployed DCPAnchor contract (not used yet) |
ANCHOR_CHAIN |
"base" |
Chain identifier |
OPERATOR_TOKEN |
— | Bearer token for POST /flush. Unset: locked |
Operating Modes¶
Individual (ANCHOR_MODE=individual): Each hash gets its own local record at once. Once on-chain submission is wired in, each would be a separate transaction.
Batch (ANCHOR_MODE=batch): Hashes are accumulated and recorded as one Merkle root every BATCH_INTERVAL_MS milliseconds, or at once with POST /flush. Once on-chain submission is wired in, each batch would be one transaction.
Integration with DCPAnchor.sol¶
Once wired in, the service will interact with the DCPAnchor.sol smart contract:
- Individual mode: Calls
anchorBundle(bytes32 bundleHash) - Batch mode: Computes the Merkle root of pending hashes and calls
anchorBatch(bytes32 merkleRoot, uint256 count)
See contracts/ethereum/README.md for contract details.
Development¶
# Start in development mode, from the repo root
PORT=3001 ANCHOR_MODE=batch node services/anchor/index.js
# Test with curl
curl http://localhost:3001/health
curl -X POST http://localhost:3001/anchor \
-H "Content-Type: application/json" \
-d '{"bundle_hash": "sha256:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"}'
License¶
Apache-2.0