Skip to content

Routstr/sixty-nuts

Repository files navigation

Sixty Nuts - A NIP-60 Cashu Wallet in Python

A lightweight, stateless Cashu wallet implementation following NIP-60 specification for Nostr-based wallet state management.

Features

  • 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

Installation

pip install sixty-nuts

For QR code support in the CLI:

pip install sixty-nuts[qr]
# or
pip install qrcode

Quick Start

CLI Usage (Recommended)

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...

Python API Usage

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())

Command Line Interface

The nuts CLI provides a complete interface for wallet operations:

Basic Commands

nuts status

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

nuts balance

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

nuts mint <amount>

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

nuts send <amount>

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

nuts redeem <token>

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

nuts pay <invoice>

Pay Lightning invoices:

# Pay BOLT11 invoice
nuts pay lnbc...

Management Commands

nuts info

Show detailed wallet information:

nuts info

nuts history

View transaction history:

# Show recent transactions
nuts history

# Limit number of entries
nuts history --limit 10

nuts relays

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

nuts cleanup

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

nuts erase

Delete wallet data (⚠️ DANGEROUS):

# 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

nuts debug

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

Global Options

Most commands support these options:

  • --mint, -m: Specify mint URLs
  • --help: Show command help
  • --yes, -y: Skip confirmations (where applicable)

Environment Configuration

The CLI automatically manages configuration through environment variables and .env files:

Required Configuration

  • NSEC: Your Nostr private key (nsec1... or hex format)

Optional Configuration

  • CASHU_MINTS: Comma-separated list of mint URLs
  • NOSTR_RELAYS: Comma-separated list of relay URLs

Example .env file

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.

Python API

Basic Wallet Setup

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())

Temporary Wallets

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

Core Operations

Minting (Receiving via Lightning)

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")

Sending Tokens

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)

Redeeming Tokens

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}")

Lightning Payments

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}")

LNURL/Lightning Address Support

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)

Wallet State Management

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)")

Complete Example

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())

Architecture

NIP-60 Implementation

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

Multi-Mint Strategy

  • 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

Proof Management

  • 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

Security Features

  • 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

Development

Project Structure

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

Running Tests

Unit Tests

# Run all unit tests
pytest tests/unit/ -v

# Run with coverage
pytest tests/unit/ --cov=sixty_nuts --cov-report=html

Integration Tests

# 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

Code Quality

# Type checking
mypy sixty_nuts/

# Linting  
ruff check sixty_nuts/

# Formatting
ruff format sixty_nuts/

Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Add tests for new functionality
  4. Ensure all tests pass
  5. Submit a pull request

Please follow the existing code style and add comprehensive tests for new features.

Implementation Status

✅ Completed 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

🚧 Work in Progress

  • 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

Troubleshooting

Common Issues

"No mint URLs configured"

# Set mint URLs via environment
export CASHU_MINTS="https://mint.minibits.cash/Bitcoin"

# Or use CLI to select
nuts status --init

"Could not connect to relays"

# Test relay connectivity
nuts debug --nostr

# Configure relays manually
nuts relays --configure

"Insufficient balance"

# Check actual balance with validation
nuts balance --validate

# Clean up corrupted state
nuts cleanup

QR codes not displaying

# Install QR code support
pip install qrcode

# Disable QR codes
nuts mint 1000 --no-qr

Debug Commands

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

Related Projects

License

MIT License - see LICENSE file for details.

Support


⚡ Start using Cashu with Nostr today!

pip install sixty-nuts
nuts status --init
nuts mint 1000

About

a lightweight, stateless bitcoin L3 wallet implementation

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 2

  •  
  •  

Languages