Community

Relays

Wallet Relay

Wallet Relay is a focused relay for wallet-service traffic. It accepts NWC events, Cashu wallet state, Nutzaps and Zaps, so the review is about event-kind policy, LMDB persistence, operator setup and the difference between carrying wallet events and being a wallet.

strfry visual
Wallet Relay icon
Relays Network infrastructure Relays, relay tools, moderation policy, search and availability.
Back to Nostr
Relays

Relays shelf

Relay pages cover the servers, filters, management tools, monitoring services and policy choices that make Nostr usable at scale.

Relays27 min readWallet-service relay for NWC, Cashu wallet state, Nutzaps, Zaps, LMDB storage and operator checks

Wallet Relay

Wallet Relay is a focused relay for wallet-service traffic. It accepts NWC events, Cashu wallet state, Nutzaps and Zaps, so the review is about event-kind policy, LMDB persistence, operator setup and the difference between carrying wallet events and being a wallet.

The quick readWallet Relay is not a wallet, not a Lightning node and not a Cashu mint. It is an open-source Go relay from Bitvora for wallet service providers who need a Nostr relay that only accepts wallet-related event kinds. The README describes it as a specialized relay for NWC and Cashu Wallet events, built on Khatru. The inspected `main.go` code allows NWC wallet info, request and response kinds; NutZap and NutZap info; Zap request and Zap receipt kinds; and Cashu wallet kinds 17375, 7375, 7376 and 7374. It rejects subscriptions and published events when the requested or submitted kinds are outside that wallet list. Unlike NWCLay, it is not NWC-only and it does not add short-retention cleanup code in the repository version checked here. It uses LMDB through fiatjaf/eventstore, reads relay metadata from `.env`, sets software to `github.com/bitvora/wallet-relay`, version to `0.1.0`, and documents a source-build, systemd, nginx and Certbot deployment path. Before you rely on it, test exact kind compatibility, NWC notification behavior, Cashu wallet deletion flows, LMDB growth, backup policy, NIP-11 output, TLS websocket proxying and whether your clients handle relay rejections clearly.

A wallet-event relay, not a wallet

Wallet Relay is a relay project from Bitvora. Its README describes it as a specialized relay for wallet service providers to process NWC and Cashu Wallet events. That sentence should be read carefully. The relay carries wallet-related Nostr events; it does not hold sats, issue ecash, run a Lightning node or become the user's wallet.

The project is still important because wallet events are not ordinary timeline chatter. NWC requests, Cashu wallet state, Nutzaps and Zap receipts can all affect what a user believes about money. A relay that accepts only wallet-related kinds can reduce unrelated traffic and make the operational surface easier to reason about.

Wallet Relay belongs under Relays because its main job is event transport and storage policy. It appears in the Apps hub so the project is discoverable from the product floor, but the article has to teach relay behavior rather than app UX. The reader needs to know what it will carry, what it will reject and where the wallet responsibility really sits.

The safest short definition is this: Wallet Relay is Bitvora's open-source, Khatru-based relay for wallet service providers who want a Nostr relay narrowed around NWC, Cashu wallet, Nutzap and Zap event kinds.

Bitvora's repository is the source of truth

The public repository is `bitvora/wallet-relay`. GitHub metadata checked for this article identifies it as a Go project created in February 2025, published under the MIT license, with the description `High performance relay for enabling NWC & Cashu Wallets`.

The repository is small. The key files are `README.md`, `.env.example`, `main.go`, `go.mod` and `LICENSE.md`. That small size makes the project easier to inspect. You do not have to reverse-engineer a large application to find the relay policy. The allowed-kind list and rejection hooks are in one file.

The owner organization is Bitvora Inc on GitHub. The organization profile points to Bitvora's public web domain, but the source code and repository metadata are the more reliable materials for this article because the relay behavior is expressed directly in the code.

For any future deployment, use the repository commit you actually run. A public article can describe the version inspected here, but a relay operator should pin and document a commit or release. Wallet-adjacent infrastructure should not depend on a vague memory of what the code did last time someone looked.

The allowed event kinds define the product

The `main.go` file defines a `walletKinds` slice. It includes NWC wallet info, NWC wallet request and NWC wallet response kinds from `go-nostr`; NutZap and NutZap info; Zap and Zap request; and four numeric Cashu wallet kinds: 17375, 7375, 7376 and 7374.

That list is the heart of Wallet Relay. When a client subscribes, the relay checks whether every requested kind is in the wallet-kind set. When a client publishes, the relay checks whether the event kind is in the same set. If not, it rejects with messages that say only wallet kinds are allowed.

This is broader than NWCLay. NWCLay focuses on NWC event traffic. Wallet Relay includes NWC, Cashu wallet state, Nutzap receiving information and classic Lightning Zap events. It is a wallet-event relay rather than a strictly NIP-47 relay.

That breadth is useful, but it increases the review burden. A project that allows Cashu wallet state and Nutzaps is touching different standards and different user expectations than a relay that only moves NWC requests and responses. The article has to keep those layers separate.

The NWC lane

NWC is specified in NIP-47. It connects a client app to a wallet service through encrypted Nostr events. A wallet service publishes info, receives requests and sends responses. The relay carries those events but does not decide whether a payment is allowed.

Wallet Relay accepts the standard NWC wallet info, request and response constants exposed by `go-nostr`: 13194, 23194 and 23195. Those are enough for the core request-response shape. The inspected code does not include NWC notification kind 23197 or the older 23196 notification kind in the allowed list.

That omission may be fine for some integrations and a problem for others. NWC implementations differ in how much notification behavior they use. If a wallet service depends on notification events, test before production rather than assuming the phrase `NWC` means every adjacent kind will pass.

The NWC docs also emphasize that the connection string includes the relay URL. If Wallet Relay is used in that position, both the app and the wallet service must agree on it. The relay can be perfectly healthy and the connection can still fail if one side points to a different relay.

The Cashu wallet lane

NIP-60 defines Cashu wallet operations on Nostr. It describes a wallet event, token events and spending-history events. A Cashu wallet in this model stores wallet information in relays so it can move across applications, while sensitive content is encrypted.

Wallet Relay allows kind 17375, the replaceable wallet event. It also allows kind 7375 token events, which hold encrypted unspent proofs, and kind 7376 spending-history events. It additionally allows kind 7374, which appears in the NIP-60 wallet flow around quote handling.

This is a materially different relay responsibility from carrying NWC messages. Cashu wallet state may live in relays as encrypted wallet data. The relay may not be able to read the wallet contents, but it can store event metadata, see timing, serve historical events and keep copies until deleted or pruned by policy.

That means storage policy matters. The inspected Wallet Relay code saves allowed events to LMDB and does not include a cleanup job like NWCLay. If you run it for Cashu wallet events, decide how long wallet state stays, how deletion requests are handled and how backups treat encrypted but sensitive wallet data.

The Nutzap lane

NIP-61 defines Nutzaps, which are Cashu tokens used as Nostr-native payments. A sender fetches the recipient's kind 10019 information event, mints or swaps ecash on an approved mint, locks the token to a public key listed by the recipient and publishes a kind 9321 Nutzap event.

Wallet Relay allows both kind 9321 and kind 10019. That means it can carry the information people need to send Nutzaps and the Nutzap events themselves. For wallet service providers that want ecash receiving flows near NWC, this is the part that makes the relay broader than a plain NWC transport.

NIP-61 is careful about risk. The recipient lists mints they accept, clients should not send to mints outside that list and the P2PK public key used for receiving must not be the user's main Nostr public key. If a relay makes these events easy to store and fetch, clients still need to perform the validation.

The relay cannot protect a sender from choosing the wrong mint, using the wrong recipient information event or publishing to relays the recipient never reads. It can only accept and serve the relevant event kinds. Wallet safety lives in client behavior, mint choice and key handling.

The Zap lane

Wallet Relay also allows NIP-57 Zap request and Zap receipt kinds. NIP-57 defines kind 9734 as a Zap request and kind 9735 as a Zap receipt. In the classic Lightning Zap flow, the request is sent to the recipient's LNURL-pay callback and the receipt is published to relays after the invoice is paid.

Including Zap kinds makes sense for a wallet-service relay because zaps are wallet-adjacent social payments. They are not the same as NWC. They are not the same as Cashu. They are a separate Lightning receipt and request flow that many Nostr clients already understand.

NIP-57 has its own caveats. A Zap receipt is signed by the recipient's LNURL provider and clients validate it against the LNURL metadata and invoice details. The relay storing the receipt does not prove the payment by itself. It is part of the data path, not the payment oracle.

For a Wallet Relay deployment, decide whether you really want Zap traffic alongside NWC and Cashu traffic. The code allows it. Your operational policy should explain it. If a relay is meant only for private wallet-service traffic, accepting public Zap receipt traffic may change storage and abuse expectations.

The filter rule is simple and permissive

Wallet Relay's subscription rule is simpler than NWCLay's. It checks whether a filter's `Kinds` list contains only wallet kinds. If the filter asks for any non-wallet kind, the relay rejects the subscription. It does not require authors, `#p` tags or `#e` tags in the inspected code.

That simplicity is easy to understand and easy to test. A filter for allowed wallet kinds can pass. A filter that includes a social note kind should fail. This keeps the relay from becoming a general-purpose Nostr database while still allowing broad wallet-kind queries.

The tradeoff is load and privacy. A broad filter for allowed wallet kinds could still scan or return more than a tightly scoped wallet-service flow needs. For NWC, a precise filter around authors or tags is usually better. For Cashu wallet state, authors and specific event kinds are also important.

Client authors should treat the relay's policy as a lower bound, not as ideal query design. Ask for the exact kinds, authors and tags you need. Close subscriptions. Avoid polling the whole wallet-event surface just because the relay permits the kind.

Publish rejection is equally direct

The publish rule is also direct. If an event's kind is not in the wallet-kind list, the relay rejects it with an invalid-event message. That is useful because accidental writes fail immediately instead of turning the relay into an unplanned social archive.

This behavior should be visible in tests. Publish a kind 1 note and confirm it fails. Publish a harmless allowed test event in a development environment and confirm the storage path behaves as expected. Then test the exact NWC, Cashu, Nutzap or Zap events your application will use.

The code does not, by itself, validate every semantic rule inside those standards. For example, allowing a kind 9321 event is not the same as proving the Nutzap uses one of the recipient's listed mints. Allowing a kind 9735 event is not the same as validating every Zap receipt rule.

That distinction matters. Wallet Relay enforces a kind boundary. Client and wallet code must still validate content, tags, signatures, encryption, mint policy, LNURL metadata and payment-specific rules.

LMDB persistence changes the risk profile

Wallet Relay stores accepted events using the LMDB backend from fiatjaf/eventstore. The `.env.example` sets `LMDB_PATH="db/"` and includes `LMDB_MAPSIZE=0`. The code initializes an LMDB backend with the configured path and map size field.

In the inspected `LoadConfig` function, the code reads `LMDB_PATH` but does not parse `LMDB_MAPSIZE` from the environment into the config. Operators should review that before relying on the example variable for production sizing. Storage tuning should be confirmed in code and in the running process, not assumed from an env file.

Persistence is useful because wallet-related events may need to be fetched later. Cashu wallet state, spending history and Zap receipts are not all ephemeral. But persistence also means the relay can accumulate sensitive encrypted wallet data and metadata. Backups, exports and disk retention become part of the wallet privacy story.

If you run Wallet Relay, decide whether LMDB is long-lived state, a cache, or a service database with backup obligations. Then document that choice. A relay carrying Cashu wallet events is not a throwaway websocket endpoint.

NIP-11 metadata is operator work

The code sets relay name, pubkey, description, icon, software and version on the Khatru relay info object. The `.env.example` names the example deployment `Bitvora Wallet Service Relay`, uses a Bitvora relay pubkey, describes the relay as high performance infrastructure for NWC and Cashu wallets, and points the icon to a Nostr Build image.

The code sets software to `https://github.com/bitvora/wallet-relay` and version to `0.1.0`. Those are useful signals for anyone fetching a NIP-11 information document from a live deployment.

The inspected code does not explicitly set a supported-NIPs list. Khatru may expose defaults or leave that field absent depending on configuration and version. A production operator should fetch the live NIP-11 document and confirm it says what clients need to know.

Relay metadata is not just decoration. If a wallet app or service operator sees a relay URL in a connection string, NIP-11 is the fastest way to identify what it claims to be. Bad metadata makes debugging and trust decisions harder.

Deployment is source build, systemd and nginx

Wallet Relay's README documents a traditional Go deployment rather than a Docker or compose path. It tells the operator to install Go, clone the repository, copy `.env.example` to `.env`, set environment variables, run `go build` and then optionally create a systemd service.

The systemd example runs the built binary from a working directory and restarts it automatically. That is a straightforward Linux service model. It also means the operator owns binary updates, environment-file handling, file permissions, logs and restarts.

The README then shows an nginx websocket proxy configuration and a Certbot path for HTTPS. That matters because Nostr relays are websocket services, and NWC connection strings normally use secure websocket URLs in real deployments.

Before using Wallet Relay with real users, test the proxy path. Confirm websocket upgrade headers, TLS, host forwarding, restart behavior and error responses. A relay can be correct locally on port 6102 and still fail behind a misconfigured reverse proxy.

Khatru and eventstore do the relay plumbing

Wallet Relay is built on Khatru. Khatru is a Go framework for custom Nostr relays. That choice makes sense for a project whose value is specific relay policy rather than a new network protocol.

The project also uses fiatjaf/eventstore with LMDB as the storage backend, `go-nostr` for Nostr event constants and protocol types, and `godotenv` for local environment loading. The Go module file pins versions of those dependencies.

Using familiar Nostr libraries helps readers understand the shape of the project. The relay is not inventing Nostr from scratch. It is taking standard relay machinery and applying a wallet-event kind filter on top.

Dependency familiarity is not a free pass. A wallet-service provider should still watch upstream versions, storage behavior and security updates. The small codebase makes audit easier, but production operation still requires maintenance.

What it does not do

Wallet Relay does not create a NWC wallet service. It does not expose a Lightning backend. It does not create Cashu proofs. It does not mint ecash. It does not validate every semantic rule inside NIP-60, NIP-61 or NIP-57.

It does not appear to include authentication, rate-limit, paid-relay or moderation logic in the inspected code. It narrows by kind, stores accepted events and serves queries through Khatru and the LMDB backend.

It also does not include the short cleanup job NWCLay uses. That is not automatically wrong because Cashu wallet state and Zap receipts are more archival than transient NWC requests. It does mean retention is an operator question, not an obvious code default.

This is the line to keep in mind: Wallet Relay is an allowlist relay for wallet-related Nostr event kinds. The wallet, mint, LNURL server, NWC service and client remain responsible for money logic.

Security and privacy checks

Start with keys. For NWC, each app connection should use limited permissions where the wallet service supports them. For Cashu wallets, NIP-60 says wallet content and proofs are encrypted, and NIP-61 warns that the receiving P2PK key must not be the user's main Nostr key.

Then check metadata. Even encrypted wallet events reveal event kind, author, created time, tags, relay choice and query patterns. A relay with LMDB persistence can retain that metadata for as long as the database exists or backups survive.

Next check deletion. NIP-60 token rollover uses NIP-09 deletion when proofs are spent, and the delete event should tag the deleted kind. Test whether your clients publish deletion events, whether the relay stores and serves the resulting state correctly and how old encrypted token events remain in backups.

Finally check exposure. The README shows nginx and Certbot, but you still need firewall rules, log hygiene, disk monitoring, systemd hardening, update procedures and recovery notes. A wallet relay should be treated like financial-adjacent infrastructure even when it never sees decrypted funds.

Good use cases

Wallet Relay is a good fit for builders working across NWC and Nostr-native ecash. If your product needs wallet-service requests, Cashu wallet state, Nutzap receiving information and Zap receipts near the same relay surface, this project gives you a narrow starting point.

It is also useful as reference code. The allowed-kind list shows how a relay can be specialized without becoming a full application server. A developer can see exactly where the project draws the boundary and then decide whether to add stricter filters, authentication, retention or observability.

It is less ideal for general social clients, public discovery, profile storage, long-form content, search or a mixed community relay. The code is intentionally not trying to serve the whole Nostr network.

The cleanest first use is a test deployment with harmless wallet-like events. Confirm kind rejection, query behavior, LMDB persistence, proxying and NIP-11 metadata before connecting it to a wallet service with real users.

What to test before production

Test exact kind coverage. For NWC, check info, request and response events. If your implementation needs notification kinds, verify behavior because the inspected allowlist does not include 23197 or 23196.

Test Cashu flows. Publish and fetch kind 17375, 7375 and 7376 in a development wallet. Spend or roll over token state and confirm NIP-09 deletion behavior. Check whether old encrypted proof events still appear in queries or backups.

Test Nutzaps and Zaps separately. For Nutzaps, verify kind 10019, accepted mints, P2PK receiving keys, mint URL normalization and redemption history. For Zaps, verify zap request and receipt validation in the client or LNURL server rather than trusting the relay.

Test operation. Restart the systemd service, rotate logs, proxy through nginx, renew TLS, fill a small LMDB test database, restore from backup and fetch NIP-11. The relay is small enough that this test suite should be boring. That is exactly what you want.

How it compares with NWCLay

NWCLay and Wallet Relay are both specialized relays, but they make different tradeoffs. NWCLay is NWC-focused and includes stricter filter shape and short retention logic. Wallet Relay accepts a broader wallet-event surface and stores accepted events in LMDB without a visible cleanup job in the inspected code.

If your use case is only NWC request-response transport, NWCLay may be closer to the narrow mental model. If your use case spans NWC, Cashu wallet state, Nutzaps and Zaps, Wallet Relay is the more relevant project.

Neither project should be described as a wallet. Both are relays. Both should be tested against the exact events your wallet service or client emits. Both can fail a payment experience if the app hides relay errors from the user.

The comparison is useful because it prevents category blur. A relay that says no to everything except NWC is different from a relay that says yes to multiple wallet-related protocols. The right choice depends on the wallet surface you actually need.

The bottom line

Wallet Relay is a compact, inspectable relay for wallet-service events. Its strength is the allowlist: NWC, Cashu wallet events, Nutzaps and Zaps pass; unrelated Nostr traffic should not.

That makes it useful for wallet providers and developers who want a focused relay surface without starting from a general social relay. It also makes the operator responsible for decisions the code does not settle: retention, authentication, backups, LMDB sizing, NIP-11 metadata and proxy hardening.

Use it only after you separate the layers. NWC is request-response wallet control. NIP-60 is Cashu wallet state. NIP-61 is Nutzaps. NIP-57 is Lightning Zap receipts. Wallet Relay carries those events; it does not validate every money rule inside them.

If you can explain the allowed kinds, storage behavior, deletion path, notification gap and deployment model, Wallet Relay is a useful infrastructure piece. If you cannot, keep it in a test environment until the boundaries are boringly clear.

Sources worth opening

Start with the Wallet Relay README and `main.go`, because the code-level allowed-kind list is the product. Then read NIP-47, NIP-60, NIP-61 and NIP-57 so the NWC, Cashu, Nutzap and Zap layers stay separate.

Back to the Crays Nostr page
Relays route visual cue 1
Relays route visual cue 2
Relays route visual cue 3
Relays route visual cue 4
Relays route visual cue 5

How to use this page

Keep the relay layer visible.

Search relay tools, relay policy, monitoring and infrastructure pages when availability or moderation choices matter.

RelaysMore relay infrastructureRelay pages and policy notesRelays, monitoring, policy and infrastructure.Browse pages
Relays contribution visual cue 1
Relays contribution visual cue 2
Relays contribution visual cue 3
Relays contribution visual cue 4
Relays contribution visual cue 5

Bring something back

Ask, suggest, submit or nominate.

Ask a question, send a source, suggest a fix, submit a project or nominate a public Nostr account. The page stays stable; your contribution gets reviewed beside it.