Dart · Bitcoin SV · SPV
An SPV wallet you can replay, audit and embed.
An actor-based Bitcoin SV SPV wallet library for Dart: event-sourced wallets, BEEF payments with merkle-proof validation, ARC broadcast, payment channels, BRC-100 key operations and pluggable token scripts.
dart pub add libspiffyRuns where Dart runs
- Flutter
- iOS
- Android
- macOS · Windows · Linux
- #1
wallet.createdbob - #2
invoice.created100000 sats - #3
wallet.utxo.receivedfrom a validated BEEF - #4
invoice.paidSEEN_ON_NETWORK - #5
wallet.transaction.confirmedproof matches header
What's in the box
Everything a wallet needs. Nothing it has to trust.
Event-sourced wallets
Every state change is an immutable event in the journal. State is rebuilt by replaying it, so any balance can be traced to the events behind it.
SPV with BEEF & BUMP
Payments arrive with their merkle proofs. libspiffy checks them against block headers it syncs itself, with no full node required.
Invoice payments
Multi-output invoices: P2PKH, P2MS multisig, OP_RETURN metadata and plugin-delegated outputs.
Payment channels
Off-chain micropayments between two peers, settled on chain and tracked as their own aggregate.
Script & token plugins
ScriptPlugin and TransactionBuilderPlugin add custom locking scripts and token protocols.
Isar or PostgreSQL
Embedded Isar storage for Flutter and desktop apps, PostgreSQL for server deployments.
One entry point
Ask the coordinator. Follow the events.
ask() sends a request and returns its own reply, or throws if it failed. on<E>() follows what happens without a request, from a new wallet to a confirmed payment.
final libspiffy = LibSpiffyActorSystem();
await libspiffy.initialize(
isar: isar,
secureStorage: secureStorage,
networkType: 'regtest',
peerAddresses: ['127.0.0.1:18444'], // localnet-teranode
arcConfig: ArcServiceConfig(baseUrl: 'http://127.0.0.1:23011'),
);
final coordinator = libspiffy.coordinator;final libspiffy = LibSpiffyActorSystem();
await libspiffy.initialize(
isar: isar,
secureStorage: secureStorage,
networkType: 'test',
arcConfig: ArcServiceConfig.bsvaArcadeTestnet(),
);
final coordinator = libspiffy.coordinator;final libspiffy = LibSpiffyActorSystem();
await libspiffy.initialize(
isar: isar,
secureStorage: secureStorage,
networkType: 'main',
arcConfig: ArcServiceConfig.taalMainnet(apiKey: taalApiKey),
);
final coordinator = libspiffy.coordinator;final mnemonic = await DartSVCryptoService().generateMnemonic();
final wallet = await coordinator.ask(CreateWalletCommand(
walletId: 'bob',
name: 'Bob',
mnemonic: mnemonic, // the wallet's backup: store it safely
));
print(wallet.rootAddress);final invoice = await coordinator.ask(CreateInvoiceCommand(
walletId: 'bob',
amount: BigInt.from(100000), // satoshis
description: 'Coffee',
));
// Share invoice.invoiceId, invoice.addresses and invoice.amount
// with the payer.final payment = await coordinator.ask(PayInvoiceCommand(
walletId: 'alice',
invoiceId: invoice.invoiceId,
addresses: [invoice.addresses.first],
amount: invoice.amount,
));
// payment.beefBytes: the transaction with its ancestors and merkle
// proofs. Not broadcast: your app hands the BEEF to the payee.try {
final verdict = await coordinator.ask(ValidateBEEFCommand(
walletId: 'bob',
beefHex: beefHex, // the payer's beefBytes, hex-encoded
invoiceId: invoice.invoiceId,
));
// Checked against Bob's own headers and the invoice, then broadcast.
print(verdict.networkStatus); // SEEN_ON_NETWORK
} on CoordinatorFailure catch (e) {
print('Refused: ${e.message}');
}coordinator.on<InvoicePaidEvent>(walletId: 'bob').listen((e) {
print('Paid: ${e.amountReceived} sats in ${e.txid}');
});
coordinator.on<TransactionConfirmedEvent>().listen((e) {
print('${e.txid} confirmed at ${e.blockHeight}');
});
coordinator.on<BalanceUpdatedEvent>().listen((e) {
print('${e.walletId}: ${e.totalBalance} sats');
});Develop locally
A whole Teranode network on your laptop.
localnet-teranode runs a regtest Teranode in Docker, with Arcade for ARC-compatible broadcast, Merkle Service for proofs, an autominer and a faucet. Mine a block whenever you need one.
- Point
ArcServiceConfigat Arcade on:23011 - Sync headers over the wire protocol on
:18444 - libspiffy's own integration tests run against the same stack
$ git clone https://github.com/twostack/localnet-teranode
$ cd localnet-teranode
$ ./scripts/start.sh
$ ./scripts/faucet.sh send <address> 1000000 --arcade
arcade RECEIVED
txid <txid>
sent 1000000 sats to <address>
$ ./scripts/mine.sh 1