@cashu/coco-core
v1.0.1
Published
Modular, storage-agnostic core for working with Cashu mints and wallets.
Downloads
971
Keywords
Readme
@cashu/coco-core
Modular, storage-agnostic core for working with Cashu mints and wallets.
⚠️ Release candidate: Coco is stabilizing for v1, but breaking changes may still land before the final 1.0 release. Pin versions in production.
- Storage-agnostic: Repositories are interfaces; bring your own persistence.
- Typed Event Bus: Subscribe to mint, proof, quote, and counter events with strong types.
- High-level APIs:
MintApi,WalletApi,AuthApi,PaymentRequestsApi,SubscriptionApi,HistoryApi,KeyRingApi, andmanager.ops.*for common flows. - Background watchers: Optional services to track quote/payment and proof states.
Install
npm install @cashu/coco-coreFor a real application you will usually install a storage adapter alongside the
core package, for example @cashu/coco-sqlite, @cashu/coco-indexeddb, or
@cashu/coco-expo-sqlite. Bun applications can use @cashu/coco-sqlite-bun.
Protocol Support
- [x] NUT-00
- [x] NUT-01
- [x] NUT-02
- [x] NUT-03
- [x] NUT-04
- [x] NUT-05
- [x] NUT-06
- [x] NUT-07
- [x] NUT-08
- [x] NUT-09
- [x] NUT-10
- [x] NUT-11
- [x] NUT-12
- [x] NUT-13
- [ ] NUT-14
- [ ] NUT-15
- [ ] NUT-16
- [x] NUT-17
- [x] NUT-18
- [ ] NUT-19
- [ ] NUT-20
- [x] NUT-21
- [x] NUT-22
- [x] NUT-23
- [ ] NUT-24
- [ ] NUT-25
Quick start
import { initializeCoco, MemoryRepositories, ConsoleLogger } from '@cashu/coco-core';
// Provide a deterministic 64-byte seed for wallet key derivation
const seedGetter = async () => seed;
const repos = new MemoryRepositories();
const logger = new ConsoleLogger('example', { level: 'info' });
const manager = await initializeCoco({
repo: repos,
seedGetter,
logger,
});
// Subscribe to events (typed)
const unsubscribe = manager.on('counter:updated', (c) => {
console.log('counter updated', c);
});
// Register a mint
await manager.mint.addMint('https://nofees.testnut.cashu.space');
// Create a mint operation, pay externally, then redeem
const pendingMint = await manager.ops.mint.prepare({
mintUrl: 'https://nofees.testnut.cashu.space',
amount: 100,
method: 'bolt11',
methodData: {},
});
// Optionally, wait via subscription API instead of polling
await manager.subscription.awaitMintQuotePaid(
'https://nofees.testnut.cashu.space',
pendingMint.quoteId,
);
// pay pendingMint.request externally, then:
await manager.ops.mint.execute(pendingMint.id);
// Check balances
const balances = await manager.wallet.balances.byMint();
const total = await manager.wallet.balances.total();
console.log('balances', balances);
console.log('wallet total', total.total);
// Inspect spendable vs reserved funds explicitly when needed
console.log('spendable balance', balances['https://nofees.testnut.cashu.space']?.spendable ?? 0);
console.log('reserved balance', balances['https://nofees.testnut.cashu.space']?.reserved ?? 0);Watchers & processors (optional)
Start background watchers or processors to automatically react to changes:
// Watch mint operation quote updates on startup and while running (default true)
await manager.enableMintOperationWatcher({ watchExistingPendingOnStart: true });
// Process queued mint operations from live events (auto-enabled by initializeCoco)
await manager.enableMintOperationProcessor({ processIntervalMs: 3000 });
// Watch proof state updates (e.g., to move inflight proofs to spent)
await manager.enableProofStateWatcher();
// Later, you can stop them
await manager.disableMintOperationWatcher();
await manager.disableMintOperationProcessor();
await manager.disableProofStateWatcher();initializeCoco options
initializeCoco sets up repositories, plugins, watchers, and processors for you. You can configure it via CocoConfig:
repo:Repositoriesimplementation (required)seedGetter: async seed provider (required)logger: optional logger (defaults toNullLogger)webSocketFactory: optional WebSocket factoryplugins: optional plugin listwatchers: enable/disable watcher services (mintOperationWatcher,proofStateWatcher)processors: enable/disable processors (mintOperationProcessor) and tune intervalssubscriptions: polling intervals for hybrid WebSocket + polling (slowPollingIntervalMs,fastPollingIntervalMs)
If you prefer manual wiring, construct Manager directly and call initPlugins() before enabling watchers/processors.
Architecture
Manager: Facade wiring services together; exposesmint,wallet,ops,paymentRequests, andsubscriptionAPIs plus watcher helpers.MintService: FetchesmintInfo, keysets and persists via repositories.WalletService: Caches and constructsWalletfrom stored keysets.ProofService: Manages proofs, selection, states, and counters.- Legacy mint quote orchestration has been replaced by
MintOperationServiceandmanager.ops.mint. MeltQuoteService: Creates and pays melt quotes (spend via Lightning).CounterService: Simple per-(mint,keyset) numeric counter with events.EventBus<CoreEvents>: Lightweight typed pub/sub used internally (includessubscriptions:pausedandsubscriptions:resumed).
Repositories
Interfaces in packages/core/repositories/index.ts:
MintRepositoryKeysetRepositoryCounterRepositoryProofRepositoryMintQuoteRepositoryMeltQuoteRepositoryHistoryRepositoryKeyRingRepositoryAuthSessionRepositorySendOperationRepositoryMeltOperationRepositoryMintOperationRepositoryReceiveOperationRepository
In-memory reference implementations are provided under repositories/memory/ for testing.
Public API surface
Manager
mint,wallet,auth,paymentRequests,ops,subscription,history, andkeyringext: PluginExtensionson/once/offforCoreEventsenableMintOperationWatcher(),disableMintOperationWatcher()enableMintOperationProcessor(),disableMintOperationProcessor(),waitForMintOperationProcessor()enableProofStateWatcher(),disableProofStateWatcher()pauseSubscriptions(),resumeSubscriptions()use(plugin: Plugin): voidinitPlugins(): Promise<void>dispose(): Promise<void>
OpsApi
send:prepare,execute,get,listPrepared,listInFlight,refresh,cancel,reclaim, plusrecoveryanddiagnosticsreceive:prepare,execute,get,listPrepared,listInFlight,refresh,cancel, plusrecoveryanddiagnosticsmint:prepare,importQuote,execute,get,getByQuote,listPending,listInFlight,checkPayment,refresh,finalize, plusrecoveryanddiagnosticsmelt:prepare,execute,get,getByQuote,listPrepared,listInFlight,refresh,cancel,reclaim,finalize, plusrecoveryanddiagnostics
MintApi
addMint(mintUrl: string, options?: { trusted?: boolean }): Promise<{ mint: Mint; keysets: Keyset[] }>getMintInfo(mintUrl: string): Promise<MintInfo>isTrustedMint(mintUrl: string): Promise<boolean>getAllMints(): Promise<Mint[]>getAllTrustedMints(): Promise<Mint[]>trustMint(mintUrl: string): Promise<void>untrustMint(mintUrl: string): Promise<void>
WalletApi
receive(token: Token | string): Promise<void>balances.byMint(scope?: { mintUrls?: string[]; trustedOnly?: boolean }): Promise<BalancesByMint>balances.total(scope?: { mintUrls?: string[]; trustedOnly?: boolean }): Promise<BalanceSnapshot>restore(mintUrl: string): Promise<void>sweep(mintUrl: string, bip39seed: Uint8Array): Promise<void>decodeToken(tokenString: string, mintUrl?: string): Promise<Token>encodeToken(token: Token, opts?: { version?: 3 | 4 }): stringencodePaymentRequest(paymentRequest: PaymentRequest, version?: 'creqA' | 'creqB'): string
AuthApi
startDeviceAuth(mintUrl: string)login(mintUrl, tokens): Promise<AuthSession>restore(mintUrl): Promise<boolean>logout(mintUrl): Promise<void>getSession(mintUrl): Promise<AuthSession>hasSession(mintUrl): Promise<boolean>getAuthProvider(mintUrl): AuthProvider | undefinedgetPoolSize(mintUrl): number
PaymentRequestsApi
parse(paymentRequest: string): Promise<ResolvedPaymentRequest>prepare(request: ResolvedPaymentRequest, options: { mintUrl: string; amount?: number }): Promise<PreparedPaymentRequest>execute(transaction: PreparedPaymentRequest): Promise<PaymentRequestExecutionResult>
SubscriptionApi
awaitMintQuotePaid(mintUrl: string, quoteId: string): Promise<unknown>awaitMeltQuotePaid(mintUrl: string, quoteId: string): Promise<unknown>
HistoryApi
getPaginatedHistory(offset?: number, limit?: number): Promise<HistoryEntry[]>getHistoryEntryById(id: string): Promise<HistoryEntry | null>getOperationIdForHistoryEntry(id: string): Promise<string | null>
KeyRingApi
generateKeyPair(dumpSecretKey?: boolean): Promise<{ publicKeyHex: string } | Keypair>addKeyPair(secretKey: Uint8Array): Promise<Keypair>removeKeyPair(publicKey: string): Promise<void>getKeyPair(publicKey: string): Promise<Keypair | null>getLatestKeyPair(): Promise<Keypair | null>getAllKeyPairs(): Promise<Keypair[]>
Subscriptions in Node vs browser
Manager will auto-detect a global WebSocket if available (e.g., browsers). In non-browser environments, provide a webSocketFactory to the Manager constructor or use the exposed SubscriptionManager/WsConnectionManager utilities.
Core events
See the CoreEvents type for the full, current event map. Common events
include:
mint:added→{ mint, keysets }mint:updated→{ mint, keysets }mint:trusted→{ mintUrl }mint:untrusted→{ mintUrl }counter:updated→Counterproofs:saved→{ mintUrl, keysetId, proofs }proofs:state-changed→{ mintUrl, secrets, state }proofs:deleted→{ mintUrl, secrets }proofs:wiped→{ mintUrl, keysetId }proofs:reserved→{ mintUrl, operationId, secrets, amount }proofs:released→{ mintUrl, secrets }mint-op:pending→{ mintUrl, operationId, operation }mint-op:quote-state-changed→{ mintUrl, operationId, operation, quoteId, state }mint-op:requeue→{ mintUrl, operationId, operation }mint-op:executing→{ mintUrl, operationId, operation }mint-op:finalized→{ mintUrl, operationId, operation }melt-quote:created→{ mintUrl, quoteId, quote }melt-quote:state-changed→{ mintUrl, quoteId, state }melt-quote:paid→{ mintUrl, quoteId, quote }send:prepared→{ mintUrl, operationId, operation }send:pending→{ mintUrl, operationId, operation, token }send:finalized→{ mintUrl, operationId, operation }send:rolled-back→{ mintUrl, operationId, operation }receive-op:prepared→{ mintUrl, operationId, operation }receive-op:finalized→{ mintUrl, operationId, operation }receive-op:rolled-back→{ mintUrl, operationId, operation }history:updated→{ mintUrl, entry }melt-op:prepared→{ mintUrl, operationId, operation }melt-op:pending→{ mintUrl, operationId, operation }melt-op:finalized→{ mintUrl, operationId, operation }melt-op:rolled-back→{ mintUrl, operationId, operation }subscriptions:paused/subscriptions:resumedauth-session:updated/auth-session:deleted/auth-session:expired
Plugins
Overview
- Purpose: Extend the core by hooking into lifecycle events with access only to the services you declare.
- Lifecycle hooks:
onInit(after services are created),onReady(after APIs are built),onDispose(on shutdown). - Cleanup: Hooks must return a cleanup function (sync or async), similar to React’s
useEffect.
Types
import type { Plugin, ServiceKey } from '@cashu/coco-core';
// Service keys are derived from the exported ServiceMap type.
// Use ServiceKey when you want the current full set without duplicating it here.
const myPlugin: Plugin<['eventBus', 'logger']> = {
name: 'my-plugin',
required: ['eventBus', 'logger'] as const,
onInit: ({ services: { eventBus, logger } }) => {
const off = eventBus.on('mint:added', (p) => logger.info('mint added', p));
return off;
},
onReady: async () => {
// optional
},
onDispose: () => {
// optional
},
};Using plugins
// Pass plugins at construction
const manager = new Manager(repos, seedGetter, logger, undefined, [myPlugin]);
// Or register later
manager.use(myPlugin);
// Dispose (runs onDispose and registered cleanups)
await manager.dispose();Error handling
- Errors thrown in
onInit,onReady, andonDisposeare caught. Hook errors are logged with the plugin name; a failure during plugin boot is also logged by the injectedLogger.
Exports
From the package root:
Manager,initializeCoco,CocoConfig- Repository interfaces and memory implementations
- Public APIs including
MintApi,WalletApi,AuthApi,PaymentRequestsApi,SubscriptionApi, and the operation-oriented APIs - Models, services, operations, and plugin types
- Types:
CoreProof,ProofState,BalanceQuery,BalanceSnapshot,BalancesByMint,BalanceBreakdown,BalancesBreakdownByMint - Logging:
ConsoleLogger,Logger - Helpers:
getEncodedToken,getDecodedToken,normalizeMintUrl - Infrastructure helpers:
SubscriptionManager,WsConnectionManager,WebSocketLike,WebSocketFactory
