Class NostrVoucherLedgerRepository
- All Implemented Interfaces:
VoucherLedgerPort
This adapter implements the voucher ledger using Nostr's NIP-33 (parameterized replaceable events) to create a public, queryable audit trail of voucher lifecycle.
Hexagonal Architecture
This is an adapter that implements the VoucherLedgerPort defined in
the application layer. It translates domain operations into Nostr protocol operations.
NIP-33 Storage Model
Each voucher is stored as a kind 30078 parameterized replaceable event:
- Event ID: Unique per update (hash of event content)
- d tag: "voucher:{voucherId}" - ensures replaceability per voucher
- Replaceability: New events with same d tag replace old ones
- Content: JSON serialized SignedVoucher + status
- Tags: status, amount, unit, expiry for queryability
Key Features
- Public audit trail - anyone can query voucher status
- Automatic deduplication via NIP-33 replaceability
- Multi-relay redundancy for availability
- Status history via timestamped events
Usage Example
// Initialize repository
NostrClientAdapter client = new NostrClientAdapter(relays, 5000, 3);
PublicKey issuerPubKey = new PublicKey("issuer_hex_pubkey");
NostrVoucherLedgerRepository ledger = new NostrVoucherLedgerRepository(client, issuerPubKey);
try {
client.connect();
// Publish new voucher
SignedVoucher voucher = ...;
ledger.publish(voucher, VoucherStatus.ISSUED);
// Query status
Optional<VoucherStatus> status = ledger.queryStatus(voucherId);
// Update status
ledger.updateStatus(voucherId, VoucherStatus.REDEEMED);
} finally {
client.disconnect();
}
Thread Safety
This class is thread-safe as it delegates to the thread-safe NostrClientAdapter.
Error Handling
All methods may throw VoucherNostrException for:
- Network failures (relay unreachable)
- Serialization errors (invalid voucher data)
- Publishing failures (all relays reject event)
- Query timeouts (no response from relays)
- See Also:
-
Constructor Summary
ConstructorsConstructorDescriptionNostrVoucherLedgerRepository(@NonNull NostrClientAdapter nostrClient, @NonNull nostr.base.PublicKey issuerPublicKey) Creates a NostrVoucherLedgerRepository with default timeouts.NostrVoucherLedgerRepository(@NonNull NostrClientAdapter nostrClient, @NonNull nostr.base.PublicKey issuerPublicKey, long publishTimeoutMs, long queryTimeoutMs) Creates a NostrVoucherLedgerRepository with custom timeouts. -
Method Summary
Modifier and TypeMethodDescriptionnostr.base.PublicKeyGets the issuer's public key used for signing events.Gets the underlying Nostr client adapter.voidpublish(@NonNull SignedVoucher voucher, @NonNull VoucherStatus status) Publishes a voucher to the Nostr ledger with the given status.queryStatus(@NonNull String voucherId) Queries the current status of a voucher from the Nostr ledger.queryVoucher(@NonNull String voucherId) Queries the full voucher details from the Nostr ledger.voidupdateStatus(@NonNull String voucherId, @NonNull VoucherStatus newStatus) Updates the status of an existing voucher in the Nostr ledger.Methods inherited from class java.lang.Object
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, waitMethods inherited from interface xyz.tcheeric.cashu.voucher.app.ports.VoucherLedgerPort
exists
-
Constructor Details
-
NostrVoucherLedgerRepository
public NostrVoucherLedgerRepository(@NonNull @NonNull NostrClientAdapter nostrClient, @NonNull @NonNull nostr.base.PublicKey issuerPublicKey) Creates a NostrVoucherLedgerRepository with default timeouts.- Parameters:
nostrClient- the Nostr client adapter (must not be null)issuerPublicKey- the issuer's public key for signing events (must not be null)- Throws:
IllegalArgumentException- if parameters are null
-
NostrVoucherLedgerRepository
public NostrVoucherLedgerRepository(@NonNull @NonNull NostrClientAdapter nostrClient, @NonNull @NonNull nostr.base.PublicKey issuerPublicKey, long publishTimeoutMs, long queryTimeoutMs) Creates a NostrVoucherLedgerRepository with custom timeouts.- Parameters:
nostrClient- the Nostr client adapter (must not be null)issuerPublicKey- the issuer's public key for signing events (must not be null)publishTimeoutMs- timeout for publishing events in millisecondsqueryTimeoutMs- timeout for querying events in milliseconds- Throws:
IllegalArgumentException- if parameters are invalid
-
-
Method Details
-
publish
public void publish(@NonNull @NonNull SignedVoucher voucher, @NonNull @NonNull VoucherStatus status) Publishes a voucher to the Nostr ledger with the given status.This method:
- Creates a VoucherLedgerEvent (kind 30078) from the voucher
- Sets the issuer's public key
- Publishes to all connected relays
- Waits for at least one relay to confirm
If a voucher with the same ID already exists, NIP-33 ensures this event replaces the previous one (based on created_at timestamp).
- Specified by:
publishin interfaceVoucherLedgerPort- Parameters:
voucher- the signed voucher to publish (must not be null)status- the initial status (must not be null)- Throws:
IllegalArgumentException- if parameters are nullVoucherNostrException- if publishing fails on all relays
-
queryStatus
Queries the current status of a voucher from the Nostr ledger.This method:
- Constructs a subscription ID for the query
- Queries all connected relays for events with d tag = "voucher:{voucherId}"
- Extracts status from the most recent event (highest created_at)
- Specified by:
queryStatusin interfaceVoucherLedgerPort- Parameters:
voucherId- the unique voucher identifier (must not be null or blank)- Returns:
- the current status, or empty if voucher not found
- Throws:
IllegalArgumentException- if voucherId is null or blankVoucherNostrException- if query fails
-
updateStatus
public void updateStatus(@NonNull @NonNull String voucherId, @NonNull @NonNull VoucherStatus newStatus) Updates the status of an existing voucher in the Nostr ledger.This method:
- Queries the current voucher from the ledger
- Creates a new VoucherLedgerEvent with updated status
- Publishes the new event (replaces old via NIP-33)
Note: This requires retrieving the full voucher first, so we can re-publish it with the new status. The NIP-33 d tag ensures the new event replaces the old.
- Specified by:
updateStatusin interfaceVoucherLedgerPort- Parameters:
voucherId- the unique voucher identifier (must not be null or blank)newStatus- the new status to set (must not be null)- Throws:
IllegalArgumentException- if parameters are invalidVoucherNostrException- if update fails or voucher not found
-
queryVoucher
Queries the full voucher details from the Nostr ledger.This method retrieves the complete SignedVoucher from the event content. It's used internally by
updateStatus(String, VoucherStatus)to get the voucher before republishing with new status.- Specified by:
queryVoucherin interfaceVoucherLedgerPort- Parameters:
voucherId- the unique voucher identifier (must not be null or blank)- Returns:
- the complete voucher, or empty if not found
- Throws:
IllegalArgumentException- if voucherId is null or blankVoucherNostrException- if query fails or deserialization fails
-
getIssuerPublicKey
public nostr.base.PublicKey getIssuerPublicKey()Gets the issuer's public key used for signing events.- Returns:
- the issuer's public key
-
getNostrClient
Gets the underlying Nostr client adapter.- Returns:
- the Nostr client adapter
-