Support Saturn directly:
Bitcoin:
bc1qf5f04hsx72k88hq9kz0ycat45eg5c2uxl4lwsf
Lightning:
quickzebra13@primal.net
Saturn is the Rust reference implementation for A2A Commerce Protocol: BTC-only settlement with Nostr-native identity, receipts, and relay redundancy.
- Buyer agent to seller agent commerce flow
- BTC-only settlement: Lightning first, optional on-chain fallback
- Nostr-native identity anchoring and receipt publication
- Rust stable,
axum,serde,sqlx,thiserror,tracing - Deterministic state machine and anti-replay request signing
.
├── Cargo.toml
├── LICENSE
├── README.md
├── docker-compose.yml
├── apps
│ └── website
│ ├── docs
│ ├── public
│ ├── src
│ ├── index.html
│ ├── package.json
│ └── vite.config.js
├── docs
│ ├── nostr-events.md
│ ├── protocol-spec.md
│ └── examples
│ ├── capability-event.json
│ ├── payment-receipt-event.json
│ ├── quote-reference-event.json
│ └── status-update-event.json
├── migrations
│ └── 0001_init.sql
├── package.json
├── src
│ ├── api
│ ├── app
│ ├── domain
│ ├── nostr
│ ├── payments
│ ├── persistence
│ ├── privacy
│ ├── security
│ ├── services
│ ├── errors.rs
│ ├── lib.rs
│ └── main.rs
└── tests
├── common
└── happy_path.rs
See docs/protocol-spec.md and docs/nostr-events.md.
Architecture notes live in docs/architecture.md.
The Rust scaffold lives under src/lib.rs and src/main.rs.
Endpoints:
GET /capabilitiesPOST /quotePOST /checkout-intentPOST /payment/confirmPOST /order/:id/fulfillGET /order/:id
- Copy env:
cp .env.example .env- Start Postgres:
docker compose up -d postgres- Run migrations:
sqlx database create
sqlx migrate run- Start server:
cargo run --bin saturn-server4a. Run the official website from the monorepo:
npm install
npm run website:devThe Svelte + Vite site lives in apps/website. The landing page is at / and the docs
landing page is at /docs/.
Live landing page: https://saturn-xyz.live
To enable the real LDK-backed adapters, set APP__LIGHTNING_BACKEND=ldk and/or
APP__ONCHAIN_BACKEND=ldk, then provide the shared LDK seed, storage path, and chain source
settings in .env.
Joinstr is available only as an optional sidecar for post-settlement on-chain privacy. To enable
it, set APP__COINJOIN_BACKEND=joinstr_sidecar and point APP__JOINSTR_SIDECAR_URL at a sidecar
endpoint that accepts POST requests with confirmed on-chain outputs. Saturn will enqueue those
outputs after a successful on-chain payment confirmation; it does not change the checkout flow or
block buyer settlement if the sidecar is unavailable.
Expected sidecar payload:
{
"order_id": "8b0f2643-e783-4f7a-81d4-52b3559b6d14",
"merchant_nostr_pubkey": "4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa",
"network": "testnet",
"address": "tb1q...",
"txid": "abc123...",
"vout": 1,
"amount_sats": 21000,
"confirmations": 6,
"receipt_event_id": "nostr-event-id",
"queued_at": "2026-03-08T15:00:00Z"
}- Run tests:
cargo test
npm run website:buildFor the live LDK regtest path, there are also ignored tests that boot bitcoind and electrs locally and verify real settlement flows:
cargo test --test ldk_regtest -- --ignored --nocapture
cargo test payments::tests::ldk_lightning_adapter_round_trips_real_payment -- --ignored --nocapture
cargo test payments::tests::saturn_router_completes_real_lightning_checkout -- --ignored --nocapture
cargo test payments::tests::saturn_router_completes_real_onchain_checkout -- --ignored --nocaptureIf cargo is not on your shell PATH, use:
export PATH="$(dirname "$(rustup which rustc)"):$PATH"- Export a signing secret:
export APP__MERCHANT_REQUEST_SIGNING_SECRET_KEY=2222222222222222222222222222222222222222222222222222222222222222Saturn keeps its request-signing key separate from the Nostr relay identity key in .env.
For request signing, sign-payload will also read APP__MERCHANT_REQUEST_SIGNING_SECRET_KEY
directly from the environment.
- Create a quote request body:
cat > /tmp/quote.json <<'JSON'
{
"message_id": "11111111-1111-4111-8111-111111111111",
"timestamp": "2026-03-08T15:00:00Z",
"nonce": "quote-nonce-1",
"public_key": "",
"signature": "",
"buyer_nostr_pubkey": "02cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
"seller_nostr_pubkey": "034f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa",
"callback_relays": ["wss://relay.damus.io"],
"items": [
{
"sku": "agent-plan",
"description": "Autonomous procurement plan",
"quantity": 1,
"unit_price_sats": 21000
}
],
"settlement_preference": "lightning_with_onchain_fallback",
"buyer_reference": "demo-order-1"
}
JSON- Sign and submit the quote:
cargo run --bin sign-payload < /tmp/quote.json > /tmp/quote.signed.json
curl -s http://127.0.0.1:3000/quote \
-H 'content-type: application/json' \
--data @/tmp/quote.signed.json | tee /tmp/quote.response.json- Start checkout:
QUOTE_ID="$(jq -r '.quote_id' /tmp/quote.response.json)"
cat > /tmp/checkout.json <<JSON
{
"message_id": "22222222-2222-4222-8222-222222222222",
"timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
"nonce": "checkout-nonce-1",
"public_key": "",
"signature": "",
"quote_id": "$QUOTE_ID",
"selected_rail": "lightning",
"buyer_reference": "demo-checkout-1",
"return_relays": ["wss://nos.lol"]
}
JSON
cargo run --bin sign-payload < /tmp/checkout.json > /tmp/checkout.signed.json
curl -s http://127.0.0.1:3000/checkout-intent \
-H 'content-type: application/json' \
-H 'Idempotency-Key: checkout-1' \
--data @/tmp/checkout.signed.json | tee /tmp/checkout.response.json- Confirm Lightning payment:
ORDER_ID="$(jq -r '.order_id' /tmp/checkout.response.json)"
PAYMENT_HASH="$(jq -r '.lightning_payment_hash' /tmp/checkout.response.json)"
cat > /tmp/payment-confirm.json <<JSON
{
"message_id": "33333333-3333-4333-8333-333333333333",
"timestamp": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
"nonce": "payment-nonce-1",
"public_key": "",
"signature": "",
"order_id": "$ORDER_ID",
"rail": "lightning",
"settlement_proof": {
"type": "lightning",
"payment_hash": "$PAYMENT_HASH",
"preimage": "mock-preimage",
"settled_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
"amount_sats": 21000
}
}
JSON
cargo run --bin sign-payload < /tmp/payment-confirm.json > /tmp/payment-confirm.signed.json
curl -s http://127.0.0.1:3000/payment/confirm \
-H 'content-type: application/json' \
-H 'Idempotency-Key: payment-1' \
--data @/tmp/payment-confirm.signed.json | tee /tmp/payment.response.json- Fetch the order:
curl -s "http://127.0.0.1:3000/order/$ORDER_ID" | jqThis repository is published as an open source project under the MIT license in LICENSE.
Community and governance files: