Skip to main content

Sixty Nuts - NIP-60 Cashu Wallet Implementation

Project description

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

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

sixty_nuts-0.1.4.tar.gz (167.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

sixty_nuts-0.1.4-py3-none-any.whl (98.6 kB view details)

Uploaded Python 3

File details

Details for the file sixty_nuts-0.1.4.tar.gz.

File metadata

  • Download URL: sixty_nuts-0.1.4.tar.gz
  • Upload date:
  • Size: 167.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.6.13

File hashes

Hashes for sixty_nuts-0.1.4.tar.gz
Algorithm Hash digest
SHA256 6b934fecee6bc8c2017313cfd2bc80e94bb9baa4ea1d176657c80b34030a52b9
MD5 5c52e50fb353d7e07c172fb271db0cc7
BLAKE2b-256 4b24ecab6ba4cf78ee4e32ddc1e820fdd72234850d80ccb365227ee46b5ddfe1

See more details on using hashes here.

File details

Details for the file sixty_nuts-0.1.4-py3-none-any.whl.

File metadata

  • Download URL: sixty_nuts-0.1.4-py3-none-any.whl
  • Upload date:
  • Size: 98.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.6.13

File hashes

Hashes for sixty_nuts-0.1.4-py3-none-any.whl
Algorithm Hash digest
SHA256 94ffd7fd28967413e141d7deb5b132d7e4ea6e952e35b9f3ce77d95812481145
MD5 bcf63da9b669f859b2423ea2101fe873
BLAKE2b-256 add666da093747f36728ebd5b7f1917241d3337cbd888c614454e61e134dcbbe

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page