Skip to content

Network, ARC and data sources

This page shows how to point libspiffy at a network: which chain it runs on, where it broadcasts transactions, what fee it pays, which peers it takes block headers from, and where it reads chain data when you import a wallet. All of it is set through LibSpiffyActorSystem.initialize().

lib/wallet.dart
// Sketch: the network options of initialize(); storage options are left out.
import 'package:libspiffy/libspiffy.dart';
final libspiffy = LibSpiffyActorSystem();
await libspiffy.initialize(
networkType: 'main',
arcConfig: ArcServiceConfig.gorillaPoolMainnet(
minimumFeeRate: const FeeRate(satoshis: 1, bytes: 1000),
),
enableP2P: true,
peerAddresses: null, // null: the network's DNS seeds
blockchainDataSource: WhatsOnChainDataSource(networkType: 'main'),
);

networkType is a string. It defaults to 'test'.

Value Network Addresses and keys
'main' (also 'mainnet', 'livenet') BSV mainnet mainnet encoding
'test' (also 'testnet'; anything unrecognised) BSV testnet testnet encoding
'regtest' a local regtest chain testnet encoding

The spellings are normalised by NetworkName (lib/src/utils/network_name.dart). Read models store the canonical form: 'mainnet', 'testnet' or 'regtest'. Regtest has its own genesis header and proof-of-work limit (NetworkParams), and no DNS seeds, so on regtest you must name peers with peerAddresses.

libspiffy broadcasts every transaction through an ARC server, reads ARC’s fee policy, and follows each transaction’s status until it is mined. You choose the server with an ArcServiceConfig (lib/src/services/arc_service_config.dart).

Preset Base URL API key
ArcServiceConfig.taalMainnet() https://arc.taal.com/v1 optional apiKey:
ArcServiceConfig.taalTestnet() https://arc-test.taal.com/v1 optional apiKey:
ArcServiceConfig.gorillaPoolMainnet() https://arc.gorillapool.io/v1 none needed
ArcServiceConfig.gorillaPoolTestnet() https://testnet.arc.gorillapool.io/v1 none needed
ArcServiceConfig.bsvaArcadeTestnet() https://arcade-v2-testnet-us-1.bsvblockchain.tech none needed

Every preset takes apiKey and minimumFeeRate as named parameters.

initialize() picks a server from networkType (see lib/src/actors/libspiffy_actor_system.dart):

  • mainnet: ArcServiceConfig.gorillaPoolMainnet()
  • anything else, regtest included: ArcServiceConfig.taalTestnet()

A regtest node needs an explicit arcConfig that points at your local ARC or Arcade.

Use the constructor, or ArcServiceConfig.custom():

final arc = ArcServiceConfig(
baseUrl: 'https://arc.example.com/v1',
apiKey: 'your-api-key', // sent as Authorization: Bearer <apiKey>
requestTimeout: const Duration(seconds: 30),
minimumFeeRate: const FeeRate(satoshis: 1, bytes: 1000),
);
Field Default Meaning
baseUrl required Root of the ARC API. libspiffy appends /tx, /tx/{txid}, /policy and /health to it.
apiKey null Sent as Authorization: Bearer <apiKey> when set.
requestTimeout 30 s Upper bound on any single HTTP request to ARC.
minimumFeeRate null Your fee floor; see below.

ArcServiceConfig.custom() takes baseUrl, apiKey, defaultCallbackUrl and minimumFeeRate, but not requestTimeout. Use the constructor when you need a different timeout.

Arcade is the Teranode-era successor to ARC. It serves the same API at its root, so its base URL has no /v1:

ArcServiceConfig(baseUrl: 'http://127.0.0.1:23011'); // local Arcade
ArcServiceConfig.bsvaArcadeTestnet(); // BSV Association testnet Arcade

Arcade answers every submission with 202 RECEIVED and takes it to the network afterwards. libspiffy treats that as in flight and asks Arcade again after each delay in ARCActor.defaultInFlightFollowDelays (1, 2, 4, 8 and 15 seconds), stopping at the first status that is no longer in flight. You can change the delays with initialize(arcInFlightFollowDelays: ...). Arcade also requires Extended Format; libspiffy submits it.

ArcService.submitBatchTransactions does not work with Arcade, whose /txs takes a different body. Nothing in libspiffy calls it.

Every transaction the wallet builds pays ARC’s published policy rate (GET /policy, miningFee) on its signed size. libspiffy has no fee rate of its own. If ARC’s policy cannot be read, the fee quote fails; libspiffy does not guess a rate.

Since 4.7.0 you can set a floor with minimumFeeRate. The wallet pays the published rate, or your floor when the published rate is lower. Some ARC servers publish a rate no miner mines at: GorillaPool’s testnet ARC publishes 0 sat/kB.

ArcServiceConfig.gorillaPoolTestnet(
minimumFeeRate: const FeeRate(satoshis: 1, bytes: 1000), // 1 sat/kB
);

FeeRate is satoshis per bytes, as ARC publishes it. FeeRate.feeFor(sizeBytes) rounds up, and FeeRate.isAbove(other) compares two rates exactly.

With the Isar backend, a broadcast that fails is put on a durable retry queue (duraq, queue name arc_broadcast_retry) with exponential backoff from 10 seconds to 5 minutes, at most 10 attempts. The coordinator emits BroadcastFailureEvent (txid, error, willRetry). The queue is stored in Isar, so it is disabled when libspiffy has no Isar instance (for example with the PostgreSQL backend); a warning is logged.

libspiffy verifies merkle proofs against block headers it holds itself. After the optional CDN header sync, it takes the rest from Bitcoin SV peers over the wire protocol.

initialize() parameter Default Meaning
enableP2P true Connect to peers and sync headers.
peerAddresses the network’s DNS seeds Peers as host:port. Each name is resolved to every address it holds and all are dialled in parallel.
userAgent LibSpiffyActorSystem.defaultUserAgent (/LibSpiffy-BSV:1.0/) The user agent sent in the version handshake.
startHeight none The height this node reports to peers in its handshake. Header sync does not start from it; it continues from the tip of the stored header chain.

The DNS seeds come from NetworkParams (lib/src/spv/network_params.dart):

  • mainnet: seed.bitcoinsv.io:8333, seed.satoshisvision.network:8333, seed.bitcoinseed.directory:8333
  • testnet: testnet-seed.bitcoinsv.io:18333, testnet-seed.bitcoincloud.net:18333, testnet-seed.bitcoinseed.directory:18333, seed.gorillapool.io:18333
  • regtest: none

Teranode’s wire-protocol service accepts a peer only if its user agent contains BSV or Bitcoin SV. It bans the IP of any other peer for 24 hours. Every client behind the same address is then locked out. Up to 4.6.x the default was /LibSpiffy:1.0/, which Teranode refuses; 4.7.0 changed the default to /LibSpiffy-BSV:1.0/. If you set your own userAgent, keep BSV in it.

If no peer connects, initialize() throws a StateError (“P2P initialization failed … LibSpiffy will fall back to API-only mode”). By then the actors are already running and libspiffy.ready has completed, so the system is still usable for everything that does not need new headers; isInitialized stays true. Catch the error and carry on.

While P2P runs, libspiffy checks every 30 seconds (LibSpiffyActorSystem.peerUpkeepInterval) whether any peer is left. If none is, it resolves the peers again, dials them, and restarts header sync once one connects. To watch progress, see Monitoring.

A blockchain data source lets libspiffy read chain data it did not receive itself: the history and UTXOs of addresses when you import a wallet, and a fallback for deferred-payment checks. Pass one as blockchainDataSource. Without it there is no import actor: the coordinator refuses an ImportWalletCommand with a failed ImportCompleteEvent, and the LibSpiffyActorSystem import methods throw StateError('ImportActor not available. ...').

libspiffy 5.0.0 ships one data source, WhatsOnChainDataSource. NodeRpcDataSource is removed in 5.0.0: it read proofs with gettxoutproof, which only an SV Node serves, and Teranode does not.

final source = WhatsOnChainDataSource(
networkType: 'main', // 'main' or 'test'
maxRetries: 3,
initialBackoffMs: 1000,
requestsPerSecondLimit: 3,
requestTimeout: const Duration(seconds: 30),
);

It calls https://api.whatsonchain.com/v1/bsv/main or .../test and reads merkle proofs in TSC format. The values shown are the defaults. It supports mainnet and testnet only: a 'regtest' network throws ArgumentError.

Implement BlockchainDataSource (lib/src/services/blockchain_data_source.dart) to read from another service, such as a regtest node or your own indexer. See the API reference for its members.