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
- NIP-44 Encryption: Secure encryption using the NIP-44 v2 standard
- Stateless Design: Wallet state stored on Nostr relays
- Multi-Mint Support: Can work with multiple Cashu mints
- Async/Await: Modern Python async implementation
Installation
pip install sixty-nuts
Usage
Basic Setup
import asyncio
from sixty_nuts import Wallet
async def main():
# Create wallet with private key (hex or nsec format)
wallet = await Wallet.create(
nsec="your_nostr_private_key_hex", # or "nsec1..." bech32 format
mint_urls=["https://mint.minibits.cash/Bitcoin"],
relays=["wss://relay.damus.io", "wss://nos.lol", "wss://nostr.wine"]
)
# Or use context manager for automatic cleanup
async with Wallet(
nsec="your_nostr_private_key_hex",
mint_urls=["https://mint.minibits.cash/Bitcoin"]
) as wallet:
# Wallet operations here
pass
asyncio.run(main())
Minting Tokens (Receiving via Lightning)
async def mint_tokens(wallet: Wallet):
# Create a Lightning invoice for 1000 sats
invoice, payment_confirmation = await wallet.mint_async(1000)
print(f"Pay this Lightning invoice: {invoice}")
print("Waiting for payment...")
# Wait for payment (with 5 minute timeout)
paid = await payment_confirmation
if paid:
print("Payment received! Tokens minted.")
# Check wallet balance
state = await wallet.fetch_wallet_state()
print(f"New balance: {state.balance} sats")
else:
print("Payment timed out")
Sending Tokens
async def send_tokens(wallet: Wallet):
# Send 100 sats as a Cashu token
amount = 100
# Check balance first
state = await wallet.fetch_wallet_state()
print(f"Current balance: {state.balance} sats")
if state.balance >= amount:
# Create a Cashu token
token = await wallet.send(amount)
print(f"Send this token to recipient: {token}")
# Check new balance
new_state = await wallet.fetch_wallet_state()
print(f"New balance: {new_state.balance} sats")
Receiving Tokens
async def receive_tokens(wallet: Wallet):
# Redeem a received Cashu token
token = "cashuA..." # Token received from someone
try:
await wallet.redeem(token)
print("Token redeemed successfully!")
# Check new balance
state = await wallet.fetch_wallet_state()
print(f"New balance: {state.balance} sats")
except Exception as e:
print(f"Failed to redeem token: {e}")
Paying Lightning Invoices (Melting)
async def pay_invoice(wallet: Wallet):
# Pay a Lightning invoice using your tokens
invoice = "lnbc..." # Lightning invoice to pay
try:
await wallet.melt(invoice)
print("Invoice paid successfully!")
# Check remaining balance
state = await wallet.fetch_wallet_state()
print(f"Remaining balance: {state.balance} sats")
except Exception as e:
print(f"Payment failed: {e}")
Checking Wallet State
async def check_wallet(wallet: Wallet):
# Fetch current wallet state from Nostr relays
state = await wallet.fetch_wallet_state()
print(f"Balance: {state.balance} sats")
print(f"Number of proofs: {len(state.proofs)}")
print(f"Connected mints: {list(state.mint_keysets.keys())}")
# Show proof denominations
denominations = {}
for proof in state.proofs:
amount = proof["amount"]
denominations[amount] = denominations.get(amount, 0) + 1
print("Denominations:")
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 example_wallet_operations():
# Initialize wallet
async with Wallet(
nsec="your_nostr_private_key_hex",
mint_urls=["https://mint.minibits.cash/Bitcoin"],
currency="sat"
) as wallet:
# Check initial balance
state = await wallet.fetch_wallet_state()
print(f"Initial balance: {state.balance} sats")
# Mint some tokens
if state.balance < 1000:
invoice, task = await wallet.mint_async(1000)
print(f"Pay invoice to add funds: {invoice}")
paid = await task
if paid:
print("Funded!")
# Send tokens
if state.balance >= 100:
token = await wallet.send(100)
print(f"Token to share: {token}")
# Final balance
final_state = await wallet.fetch_wallet_state()
print(f"Final balance: {final_state.balance} sats")
if __name__ == "__main__":
asyncio.run(example_wallet_operations())
Architecture
wallet.py- Main wallet implementationcrypto.py- Cryptographic primitives (BDHKE and NIP-44 v2 encryption)mint.py- Cashu mint API clientrelay.py- Nostr relay WebSocket client
Security Notes
⚠️ Important: This implementation includes proper NIP-44 encryption for wallet data stored on relays. However:
- The Cashu blinding implementation is simplified and needs proper BDHKE implementation for production use
- Proof-to-event tracking needs to be implemented for full NIP-60 compliance
- Consider the security limitations of storing wallet state on public relays
TODO
Core Implementation
-
Proof-to-Event-ID Mapping: Implement proper mapping between proofs and their containing event IDs (wallet.py:66)
- Currently missing in
WalletStatedataclass - Required for accurate token event management and proper deletion when proofs are spent
- Currently missing in
-
Quote Tracking (NIP-60): Implement quote tracking as per NIP-60 specification (wallet.py:776)
- Need to publish kind 7374 events for mint quotes
- Track quote expiration and status
-
Minted Quote Tracking: Properly track minted quotes to avoid double-minting (wallet.py:806)
- Maintain state of which quotes have been successfully minted
- Check existing token events for quote IDs in tags
-
Coin Selection Algorithm: Implement better coin selection algorithm (wallet.py:956)
- Current implementation is naive (first-fit)
- Should optimize for privacy and minimize number of proofs used
Security & Cryptography
- Implement proper BDHKE blinding for Cashu operations
- Improve proof tracking to correctly identify which proofs belong to which token events (wallet.py:1031)
Features
- Support for P2PK ecash (NIP-61)
- Add comprehensive test suite
- Implement wallet recovery from relay state
- Add multi-mint transaction support
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file sixty_nuts-0.0.1.tar.gz.
File metadata
- Download URL: sixty_nuts-0.0.1.tar.gz
- Upload date:
- Size: 52.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.6.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bac83b4c0a057b384f1351c0c2b8c92dcd28e19c9e1afb1c50685032bffe0dc9
|
|
| MD5 |
55f201fc0e8b9166776115d36377ae0c
|
|
| BLAKE2b-256 |
fa5bcdd170ff22a49be166dedc98637fd5a6f0b728b4e08a3dc094ab15300fdb
|
File details
Details for the file sixty_nuts-0.0.1-py3-none-any.whl.
File metadata
- Download URL: sixty_nuts-0.0.1-py3-none-any.whl
- Upload date:
- Size: 24.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.6.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1cbc77a1d373625f0630eb4a162095edbca1642f36386d276a0d264ec39368cb
|
|
| MD5 |
44fe1d8d6e86ba484a91f624693c1745
|
|
| BLAKE2b-256 |
c531803689069ee0e24310e036a36744724ddf2f9f02ab6a1a0eaeb1c6a0f465
|