A lightweight, stateless Cashu wallet implementation following NIP-60 specification for Nostr-based wallet state management.
- NIP-60 Compliant: Full implementation of the NIP-60 specification for Cashu wallet state management
- NIP-44 Encryption: Secure encryption using the NIP-44 v2 standard for all sensitive data
- Stateless Design: Wallet state stored on Nostr relays with automatic synchronization
- Multi-Mint Support: Seamlessly work with multiple Cashu mints with automatic token swapping
- Modern Python: Async/await implementation with full type hints (Python 3.11+)
- LNURL Support: Send to Lightning Addresses and other LNURL formats
- CLI Interface: Full-featured command-line interface for all wallet operations
- Temporary Wallets: Ephemeral wallets for one-time operations without key storage
- Auto-Discovery: Automatic relay and mint discovery with intelligent caching
- QR Code Support: Built-in QR code generation for invoices and tokens
pip install sixty-nuts
For QR code support in the CLI:
pip install sixty-nuts[qr]
# or
pip install qrcode
The easiest way to get started is with the CLI:
# Check status and initialize if needed
nuts status
# Check balance
nuts balance
# Create Lightning invoice to add funds
nuts mint 1000
# Send tokens
nuts send 100
# Redeem received token
nuts redeem cashuA...
# Send to Lightning Address
nuts send 500 --to-lnurl user@getalby.com
# Pay Lightning invoice
nuts pay lnbc...
import asyncio
from sixty_nuts import Wallet
async def main():
# Create wallet with automatic relay/mint discovery
async with Wallet(nsec="your_nsec_private_key") as wallet:
# Check balance
balance = await wallet.get_balance()
print(f"Balance: {balance} sats")
# Create invoice and wait for payment
invoice, task = await wallet.mint_async(1000)
print(f"Pay: {invoice}")
paid = await task
if paid:
# Send tokens
token = await wallet.send(100)
print(f"Token: {token}")
asyncio.run(main())
The nuts
CLI provides a complete interface for wallet operations:
Check wallet initialization status and configuration:
# Check if wallet is initialized
nuts status
# Initialize wallet if needed
nuts status --init
# Force re-initialization
nuts status --force
Check your wallet balance:
# Quick balance check
nuts balance
# Detailed breakdown by mint
nuts balance --details
# Skip proof validation for speed
nuts balance --no-validate
Create Lightning invoice to add funds:
# Mint 1000 sats
nuts mint 1000
# With custom timeout
nuts mint 1000 --timeout 600
# Without QR code display
nuts mint 1000 --no-qr
Send sats as Cashu token or to Lightning Address:
# Create Cashu token
nuts send 100
# Send directly to Lightning Address
nuts send 500 --to-lnurl user@getalby.com
# Send without QR code
nuts send 100 --no-qr
Redeem received Cashu tokens:
# Redeem token to wallet
nuts redeem cashuA...
# Redeem and forward to Lightning Address
nuts redeem cashuA... --to-lnurl user@getalby.com
# Disable auto-swap from untrusted mints
nuts redeem cashuA... --no-auto-swap
Pay Lightning invoices:
# Pay BOLT11 invoice
nuts pay lnbc...
Show detailed wallet information:
nuts info
View transaction history:
# Show recent transactions
nuts history
# Limit number of entries
nuts history --limit 10
Manage Nostr relay configuration:
# List configured relays
nuts relays --list
# Test relay connectivity
nuts relays --test
# Discover relays from profile
nuts relays --discover
# Interactive configuration
nuts relays --configure
# Clear relay cache
nuts relays --clear-cache
Clean up wallet state:
# Show what would be cleaned up
nuts cleanup --dry-run
# Clean up old/corrupted events
nuts cleanup
# Skip confirmation
nuts cleanup --yes
Delete wallet data (
# Delete wallet configuration
nuts erase --wallet
# Delete transaction history
nuts erase --history
# Delete token storage (affects balance!)
nuts erase --tokens
# Clear locally stored NSEC
nuts erase --nsec
# Nuclear option - delete everything
nuts erase --all
# Skip confirmation
nuts erase --all --yes
Debug wallet issues:
# Debug Nostr connectivity
nuts debug --nostr
# Debug balance/proof issues
nuts debug --balance
# Debug proof state
nuts debug --proofs
# Debug wallet configuration
nuts debug --wallet
# Debug history decryption
nuts debug --history
Most commands support these options:
--mint, -m
: Specify mint URLs--help
: Show command help--yes, -y
: Skip confirmations (where applicable)
The CLI automatically manages configuration through environment variables and .env
files:
NSEC
: Your Nostr private key (nsec1... or hex format)
CASHU_MINTS
: Comma-separated list of mint URLsNOSTR_RELAYS
: Comma-separated list of relay URLs
NSEC="nsec1your_private_key_here"
CASHU_MINTS="https://mint.minibits.cash/Bitcoin,https://mint.cubabitcoin.org"
NOSTR_RELAYS="wss://relay.damus.io,wss://nostr.wine"
The CLI will prompt for missing configuration and automatically cache your choices.
import asyncio
from sixty_nuts import Wallet
async def main():
# Create wallet with explicit configuration
wallet = await Wallet.create(
nsec="your_nsec_private_key", # hex or nsec1... format
mint_urls=["https://mint.minibits.cash/Bitcoin"],
relays=["wss://relay.damus.io", "wss://nostr.wine"]
)
# Or use context manager for automatic cleanup
async with Wallet(nsec="your_nsec_private_key") as wallet:
# Wallet operations here
pass
asyncio.run(main())
For one-time operations without storing keys:
import asyncio
from sixty_nuts import TempWallet
async def main():
# Create temporary wallet with auto-generated keys
async with TempWallet() as wallet:
# Use wallet normally - keys are never stored
balance = await wallet.get_balance()
print(f"Balance: {balance} sats")
# Perfect for redeeming tokens to Lightning Address
token = "cashuA..."
amount, unit = await wallet.redeem(token)
await wallet.send_to_lnurl("user@getalby.com", amount)
asyncio.run(main())
TempWallet Use Cases:
- One-time token redemption
- Privacy-focused operations
- Testing and development
- Receiving tokens without account setup
async def mint_tokens(wallet: Wallet):
# Create Lightning invoice
invoice, payment_task = await wallet.mint_async(1000)
print(f"Pay: {invoice}")
# Wait for payment (5 minute timeout)
paid = await payment_task
if paid:
balance = await wallet.get_balance()
print(f"New balance: {balance} sats")
async def send_tokens(wallet: Wallet):
# Check balance
balance = await wallet.get_balance()
if balance >= 100:
# Create Cashu token (V4 format by default)
token = await wallet.send(100)
print(f"Token: {token}")
# Or use V3 format for compatibility
token_v3 = await wallet.send(100, token_version=3)
async def redeem_tokens(wallet: Wallet):
token = "cashuA..." # Token from someone else
try:
amount, unit = await wallet.redeem(token)
print(f"Redeemed: {amount} {unit}")
balance = await wallet.get_balance()
print(f"New balance: {balance}")
except WalletError as e:
print(f"Failed: {e}")
async def pay_invoice(wallet: Wallet):
invoice = "lnbc..."
try:
await wallet.melt(invoice)
print("Payment successful!")
except WalletError as e:
print(f"Payment failed: {e}")
async def send_to_lightning_address(wallet: Wallet):
# Send to Lightning Address
amount_sent = await wallet.send_to_lnurl("user@getalby.com", 1000)
print(f"Sent: {amount_sent} sats")
# Works with various LNURL formats:
# - user@domain.com (Lightning Address)
# - LNURL1... (bech32 encoded)
# - lightning:user@domain.com (with prefix)
# - https://... (direct URL)
async def check_wallet_state(wallet: Wallet):
# Fetch complete wallet state
state = await wallet.fetch_wallet_state()
print(f"Balance: {state.balance} sats")
print(f"Proofs: {len(state.proofs)}")
print(f"Mints: {list(state.mint_keysets.keys())}")
# Show denomination breakdown
denominations = {}
for proof in state.proofs:
amount = proof["amount"]
denominations[amount] = denominations.get(amount, 0) + 1
for amount, count in sorted(denominations.items()):
print(f" {amount} sat: {count} proof(s)")
import asyncio
from sixty_nuts import Wallet
async def complete_example():
async with Wallet(nsec="your_nsec") as wallet:
# Initialize wallet events if needed
await wallet.initialize_wallet()
# Check initial state
balance = await wallet.get_balance()
print(f"Starting balance: {balance} sats")
# Mint tokens if balance is low
if balance < 1000:
invoice, task = await wallet.mint_async(1000)
print(f"Pay: {invoice}")
if await task:
print("Payment received!")
# Send some tokens
token = await wallet.send(100)
print(f"Created token: {token}")
# Send to Lightning Address
await wallet.send_to_lnurl("user@getalby.com", 200)
print("Sent to Lightning Address!")
# Final balance
final_balance = await wallet.get_balance()
print(f"Final balance: {final_balance} sats")
if __name__ == "__main__":
asyncio.run(complete_example())
Sixty Nuts implements the complete NIP-60 specification:
- Wallet Events (kind 17375): Encrypted wallet metadata and configuration
- Token Events (kind 7375): Encrypted Cashu proof storage with rollover support
- History Events (kind 7376): Optional encrypted transaction history
- Delete Events (kind 5): Proper event deletion with relay compatibility
- Primary Mint: Default mint for operations
- Auto-Swapping: Automatic token swapping from untrusted mints
- Fee Optimization: Intelligent proof selection to minimize transaction fees
- Denomination Management: Automatic proof splitting for optimal denominations
- State Validation: Real-time proof validation with mint connectivity
- Caching System: Smart caching to avoid re-validating spent proofs
- Backup & Recovery: Automatic proof backup to Nostr relays with local fallback
- Consolidation: Automatic cleanup of fragmented wallet state
- NIP-44 Encryption: All sensitive data encrypted using NIP-44 v2
- Key Separation: Separate keys for Nostr identity and P2PK ecash operations
- Local Backup: Automatic local proof backup before Nostr operations
- State Validation: Cryptographic proof validation before operations
sixty_nuts/
├── __init__.py # Package exports
├── wallet.py # Main Wallet class implementation
├── temp.py # TempWallet for ephemeral operations
├── mint.py # Cashu mint API client
├── relay.py # Nostr relay WebSocket client
├── events.py # NIP-60 event management
├── crypto.py # Cryptographic primitives (BDHKE, NIP-44)
├── lnurl.py # LNURL protocol support
├── types.py # Type definitions and errors
└── cli.py # Command-line interface
tests/
├── unit/ # Unit tests (fast, no external deps)
├── integration/ # Integration tests (require Docker)
└── run_integration.py # Integration test orchestration
examples/
├── basic_operations.py # Basic wallet operations
├── multi_mint.py # Multi-mint examples
├── lnurl_operations.py # LNURL examples
└── one_off_redeem.py # TempWallet examples
# Run all unit tests
pytest tests/unit/ -v
# Run with coverage
pytest tests/unit/ --cov=sixty_nuts --cov-report=html
# Automated with Docker
python tests/run_integration.py
# Manual control
docker-compose up -d
RUN_INTEGRATION_TESTS=1 pytest tests/integration/ -v
docker-compose down -v
# Type checking
mypy sixty_nuts/
# Linting
ruff check sixty_nuts/
# Formatting
ruff format sixty_nuts/
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Ensure all tests pass
- Submit a pull request
Please follow the existing code style and add comprehensive tests for new features.
- NIP-60 wallet state management
- NIP-44 v2 encryption for all sensitive data
- Multi-mint support with automatic swapping
- Lightning invoice creation and payment
- Cashu token sending and receiving (V3/V4 formats)
- LNURL/Lightning Address support
- Proof validation and state management
- Automatic relay discovery and caching
- Complete CLI interface
- TempWallet for ephemeral operations
- Automatic proof consolidation
- Transaction history tracking
- QR code generation
- Comprehensive error handling
- P2PK Ecash Support (NIP-61): Partially implemented
- Quote Tracking: Implement full NIP-60 quote tracking (kind 7374)
- Multi-Mint Transactions: Atomic operations across multiple mints
- Advanced Coin Selection: Privacy-optimized proof selection algorithms
- Offline Operations: Enhanced offline capability with delayed sync
# Set mint URLs via environment
export CASHU_MINTS="https://mint.minibits.cash/Bitcoin"
# Or use CLI to select
nuts status --init
# Test relay connectivity
nuts debug --nostr
# Configure relays manually
nuts relays --configure
# Check actual balance with validation
nuts balance --validate
# Clean up corrupted state
nuts cleanup
# Install QR code support
pip install qrcode
# Disable QR codes
nuts mint 1000 --no-qr
The CLI provides extensive debugging capabilities:
# Comprehensive wallet debugging
nuts debug --wallet --nostr --balance --proofs
# Debug specific issues
nuts debug --history # Transaction history issues
nuts debug --balance # Balance calculation issues
nuts debug --proofs # Proof validation issues
- Cashu Protocol - Chaumian ecash protocol specification
- NIP-60 - Cashu Wallet specification
- NIP-44 - Encryption specification
- Nostr Protocol - Decentralized communication protocol
MIT License - see LICENSE file for details.
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Nostr: Follow development updates via Nostr
⚡ Start using Cashu with Nostr today!
pip install sixty-nuts
nuts status --init
nuts mint 1000