Skip to content
outofreservePublic

About

Nostr-native cryptocurrency relay/ledger node in Rust

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

7 Commits

Folders and files

Repository files navigation

coin

A Nostr-native cryptocurrency relay. coin-relay is a standard NIP-01 WebSocket relay extended to validate custom transaction events, maintain account balances in SQLite, require Proof-of-Work from block producers, and gossip ledger state between peer relays.

See docs/ARCHITECTURE.md for the full design, event schema, sequence diagram, and double-spend prevention model.

Building

cargo build

This is a Cargo workspace with a single member crate, coin-relay, which produces both a library (coin_relay, used by integration tests) and a binary (coin-relay).

Running tests

cargo test

Covers: schnorr signature verification (valid + tampered events), nonce / double-spend rejection, insufficient-balance rejection, PoW difficulty checking, mempool dedup/eviction, NIP-01 filter matching, and an integration test that submits two transfers through the full ingestion pipeline, seals a PoW block over them, and checks resulting balances plus independently re-verifies the block's PoW and hash linkage.

Running a relay

cp config.toml my-node.toml   # edit listen_addr, sqlite_path, seed_peers, etc.
cargo run --release -- my-node.toml
# (defaults to ./config.toml if no path is given)

On startup the relay:

  • Opens/creates the SQLite ledger at sqlite_path.
  • Generates an ephemeral relay identity keypair (used to sign sealed blocks and peer HELLOs) and logs its public key. v1 simplification: this key is not currently persisted across restarts.
  • Connects out to each seed_peers URL, performs a HELLO + catch-up + live-gossip subscription.
  • If is_block_producer = true, spawns a background task that seals pending mempool transfers into a PoW block every block_interval_secs.
  • Serves the NIP-01 WebSocket endpoint at ws://<listen_addr>/ and a plain-text health check at http://<listen_addr>/health.

Running two local peers

# terminal 1: block-producer node
cat > node-a.toml <<'EOF'
relay_name = "node-a"
listen_addr = "127.0.0.1:7000"
sqlite_path = "node-a.sqlite"
seed_peers = []
pow_difficulty = 8
is_block_producer = true
block_interval_secs = 5
EOF
cargo run --release -- node-a.toml

# terminal 2: peer that syncs from node-a
cat > node-b.toml <<'EOF'
relay_name = "node-b"
listen_addr = "127.0.0.1:7001"
sqlite_path = "node-b.sqlite"
seed_peers = ["ws://127.0.0.1:7000/"]
pow_difficulty = 8
is_block_producer = false
EOF
cargo run --release -- node-b.toml

Submitting a Transfer event

Transfers are ordinary NIP-01 EVENT messages of kind = 3000, whose content is a JSON-encoded {"recipient", "amount", "nonce"} payload, signed over the standard NIP-01 id computation (sha256([0,pubkey,created_at,kind,tags,content])) with a BIP-340 schnorr signature.

The example below uses websocat plus a short Python helper (using only the standard library's hashlib, and a lightweight secp256k1 schnorr binding) to build and sign the event. If you don't have a signer handy, the easiest path is to adapt one of the crate's own tests (coin-relay/src/event.rs::Event::new_signed) or use any NIP-01-compatible Nostr client library in your language of choice - the relay only depends on standard NIP-01 event framing and BIP-340 schnorr signatures, nothing bespoke.

# Install websocat (or use any WebSocket client)
cargo install websocat

# Given a pre-signed event JSON file transfer_event.json, e.g.:
# ["EVENT", {"id": "...", "pubkey": "...", "created_at": 1700000000,
#            "kind": 3000, "tags": [], "content": "{\"recipient\":\"<hex pubkey>\",\"amount\":10,\"nonce\":1}",
#            "sig": "..."}]
cat transfer_event.json | websocat ws://127.0.0.1:7000/

# Expect a reply like:
# ["OK","<event id>",true,""]

To subscribe and watch live ledger activity (transfers + sealed blocks):

echo '["REQ","sub1",{"kinds":[3000,3001]}]' | websocat ws://127.0.0.1:7000/

A runnable Rust example that builds a signed Transfer, sends it, and prints the relay's OK/EOSE replies is included at coin-relay/examples/smoke_client.rs:

cargo run --release --example smoke_client

Repository layout

coin-relay/
  src/
    main.rs         - config load, starts the WS server + peer-sync + block producer tasks
    lib.rs           - re-exports modules for the binary and for integration tests
    config.rs        - TOML config struct
    event.rs         - NIP-01 Event struct, id computation, schnorr verify/sign
    event_types.rs    - Transfer / StateBlock / PeerAnnounce payloads (custom kinds 3000-3002)
    validation.rs     - signature + semantic (balance/nonce/PoW) validation
    ledger.rs         - SQLite-backed accounts/transactions/blocks/peers state
    mempool.rs        - in-memory pending-transaction pool
    pow.rs            - PoW difficulty check + mining loop
    peer_sync.rs      - outbound peer WebSocket client (HELLO, catch-up, live gossip)
    ws_server.rs       - axum WebSocket server, NIP-01 message dispatch, shared ingestion pipeline
  tests/
    integration_test.rs - end-to-end ingest -> validate -> apply -> seal -> verify test
config.toml            - example relay configuration
docs/ARCHITECTURE.md    - full design doc + sequence diagram

Consensus monetary policy

The relay uses a 210,000,000,000,000 base-unit supply cap and a 500,000,000-unit initial block reward, halving every 210,000 blocks. PoW starts at a deliberately low one-bit target and retargets every 2016 blocks toward 10-second blocks, with adjustments limited to 4x. A signed StateBlock event is limited to 100 KB; producers trim the oldest pending transfers to fit.

About

Nostr-native cryptocurrency relay/ledger node in Rust

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages