Queries
This page lists the queries you can send to the wallet coordinator in libspiffy 5.0.0, the response that answers each one, and how to wait for it with ask(). Queries read state; they change nothing. They are defined in lib/src/actors/coordinator_messages.dart and exported by package:libspiffy/coordinator.dart.
| Query | Response | Failure | Default timeout |
|---|---|---|---|
GetBalanceQuery |
BalanceResponse |
ErrorEvent, source getBalance |
1 minute |
GetTransactionsQuery |
TransactionsResponse |
ErrorEvent, source getTransactions |
1 minute |
GetTransactionDetailQuery |
TransactionDetailResponse |
ErrorEvent, source getTransactionDetail |
1 minute |
ExportTransactionQuery |
TransactionExportedEvent |
TransactionExportedEvent with success: false |
1 minute |
GetDeferredPaymentsQuery |
DeferredPaymentsResponse |
ErrorEvent, source getDeferredPayments |
1 minute |
GetHeaderSyncStatusQuery |
HeaderSyncStatusResponse |
none | 1 minute |
Asking a query
Section titled “Asking a query”Every query is a CoordinatorRequest<R>, where R is its response type. libspiffy.coordinator.ask() sends the query and completes with its own response, whatever else is on the event stream:
// Sketch: assumes an initialized LibSpiffyActorSystem named `libspiffy`.import 'dart:async';
import 'package:libspiffy/coordinator.dart';
Future<void> showBalance(String walletId) async { try { final balance = await libspiffy.coordinator.ask(GetBalanceQuery(walletId: walletId)); print('spendable: ${balance.totalBalance} sats'); } on CoordinatorFailure catch (failure) { print('no balance: ${failure.message}'); } on TimeoutException { print('no answer within a minute'); }}requestId. Every query takes an optionalrequestId. When you leave it out, a unique one is generated when the query is made. The response carries it back, so two queries of the same kind running at once each get their own answer. In 4.x this field wasqueryId; 5.0.0 renames it torequestIdon every query and response.- Failures. A query that cannot be answered produces an
ErrorEventthat carries the query’srequestId.ask()then throwsCoordinatorFailure, whoseeventis theErrorEvent.ExportTransactionQueryreports its failure in its own response instead, andask()throwsCoordinatorFailurewith that response asevent. - Timeouts.
ask()throwsTimeoutExceptionwhen no response arrives within the query’sreplyTimeout(one minute for every query). Passtimeout:to change it. - The event stream. Responses are still published on
libspiffy.coordinatorEvents, so a listener there sees them too.
Queries
Section titled “Queries”GetBalanceQuery
Section titled “GetBalanceQuery”Reads a wallet’s balances from the read model.
| Field | Type | Required |
|---|---|---|
walletId |
String |
yes |
requestId |
String? |
no |
Response: BalanceResponse. confirmedBalance and unconfirmedBalance are the spendable payment UTXOs split by whether they have a block height; totalBalance is their sum. pendingBalance (outputs the network is not known to hold, or whose proof a reorg removed), watchOnlyBalance (watch addresses) and reservedBalance (reserved UTXOs, including a deferred payment’s held inputs) are the wallet’s too, but are not part of totalBalance.
| Field | Type |
|---|---|
walletId |
String |
requestId |
String? |
confirmedBalance |
BigInt |
unconfirmedBalance |
BigInt |
totalBalance |
BigInt |
pendingBalance |
BigInt |
watchOnlyBalance |
BigInt |
reservedBalance |
BigInt |
On failure: ErrorEvent with source getBalance, the walletId and the query’s requestId.
To hear about balance changes without polling, follow BalanceUpdatedEvent, which carries the same numbers:
libspiffy.coordinator .on<BalanceUpdatedEvent>(walletId: 'alice') .listen((e) => print('alice: ${e.totalBalance} sats'));GetTransactionsQuery
Section titled “GetTransactionsQuery”Reads a page of a wallet’s transaction history.
| Field | Type | Required |
|---|---|---|
walletId |
String |
yes |
limit |
int |
no (default 50) |
offset |
int |
no (default 0) |
requestId |
String? |
no |
Response: TransactionsResponse. transactions is a list of BitcoinTransaction (exported by package:libspiffy/libspiffy.dart).
| Field | Type |
|---|---|
walletId |
String |
requestId |
String? |
transactions |
List<BitcoinTransaction> |
On failure: ErrorEvent with source getTransactions, the walletId and the query’s requestId.
GetTransactionDetailQuery
Section titled “GetTransactionDetailQuery”Reads one transaction of a wallet.
| Field | Type | Required |
|---|---|---|
walletId |
String |
yes |
txid |
String |
yes |
requestId |
String? |
no |
Response: TransactionDetailResponse. When the wallet has no such transaction, found is false and transaction is null. That is an answer, not a failure: ask() returns it.
| Field | Type |
|---|---|
walletId |
String |
requestId |
String? |
transaction |
BitcoinTransaction? |
found |
bool |
error |
String? |
On failure: ErrorEvent with source getTransactionDetail, the walletId and the query’s requestId.
ExportTransactionQuery
Section titled “ExportTransactionQuery”Exports a transaction of the wallet as BEEF with its merkle proof, for another wallet to import with ImportTransactionCommand. This is the sending side of a hand-off: a service’s xpub wallet passing on a payment it received for an offline payee, or a payer passing on a type-42 payment it broadcast itself.
| Field | Type | Required |
|---|---|---|
walletId |
String |
yes |
txid |
String |
yes |
requestId |
String? |
no |
Response: TransactionExportedEvent. beef is the BEEF bytes. delegatedIndices and type42Derivations are what the importing wallet passes to ImportTransactionCommand.
| Field | Type |
|---|---|
walletId |
String |
txid |
String |
requestId |
String? |
success |
bool |
beef |
List<int>? |
delegatedIndices |
List<int> |
type42Derivations |
List<Type42Derivation> |
error |
String? |
On failure: TransactionExportedEvent with success: false and error. The export is refused while the transaction has no proof verified against the local header chain.
// Sketch: hand a proven payment from one wallet to another.final export = await libspiffy.coordinator .ask(ExportTransactionQuery(walletId: 'service', txid: txid));await libspiffy.coordinator.ask(ImportTransactionCommand( walletId: 'payee', beef: export.beef!, delegatedIndices: export.delegatedIndices, type42Derivations: export.type42Derivations,));GetDeferredPaymentsQuery
Section titled “GetDeferredPaymentsQuery”Lists or searches a wallet’s deferred payments: payments built by PayInvoiceCommand (and Benford split transactions, which are recorded the same way) that the network is not known to hold. By default it returns outstanding payments only, newest first, 50 per page, each with its BEEF rebuilt from storage. Use olderThan or createdBefore to find payments the recipient has not broadcast, and includeResolved or states to include seen, mined, failed, cancelled, reclaimed and completed ones. Nothing is ever deleted.
| Field | Type | Required |
|---|---|---|
walletId |
String |
yes |
states |
Set<DeferredPaymentState>? |
no |
includeResolved |
bool |
no (default false) |
createdBefore |
DateTime? |
no |
createdAfter |
DateTime? |
no |
olderThan |
Duration? |
no |
lastNetworkStatuses |
Set<String>? |
no |
invoiceId |
String? |
no |
recipientAddress |
String? |
no |
dueBefore |
DateTime? |
no |
limit |
int |
no (default 50) |
cursor |
String? |
no |
oldestFirst |
bool |
no (default false) |
includeBeef |
bool |
no (default true) |
requestId |
String? |
no |
Response: DeferredPaymentsResponse. nextCursor is the cursor for the next page, or null on the last page.
| Field | Type |
|---|---|
walletId |
String |
requestId |
String? |
payments |
List<DeferredPaymentDetail> |
nextCursor |
String? |
On failure: ErrorEvent with source getDeferredPayments, the walletId and the query’s requestId.
states overrides includeResolved. olderThan and createdBefore combine; the earlier bound wins. limit is 1 to 1000. Set includeBeef: false to skip rebuilding each BEEF when you only need the list.
Each DeferredPaymentDetail has payment (DeferredPayment), rawTxHex (String?), beef (Uint8List?) and beefError (String?, why beef is null although it was requested). It also forwards the payment’s fields: txid, invoiceId, recipientAddresses, amount, fee, createdAt, heldInputs, lastNetworkStatus, lastCheckedAt, competingTxids, state, purpose and resolutionReason, plus reclaimsTxid and isReclaim for a reclaim’s self-spend.
Page through every outstanding payment:
// Sketch: collect all outstanding deferred payments of a wallet.Future<List<DeferredPaymentDetail>> outstanding(String walletId) async { final all = <DeferredPaymentDetail>[]; String? cursor; do { final page = await libspiffy.coordinator.ask(GetDeferredPaymentsQuery( walletId: walletId, cursor: cursor, includeBeef: false, )); all.addAll(page.payments); cursor = page.nextCursor; } while (cursor != null); return all;}Act on a payment with the deferred-payment commands.
GetHeaderSyncStatusQuery
Section titled “GetHeaderSyncStatusQuery”Asks where header sync stands. The coordinator passes it to the header sync actor.
| Field | Type | Required |
|---|---|---|
requestId |
String? |
no |
Response: HeaderSyncStatusResponse. status is a HeaderSyncStatus with height (the active chain’s tip), networkHeight (the higher of height and the heights peers reported when they connected; 0 while none did), synced (the last peer answer held fewer than 2,000 headers) and peerCount.
| Field | Type |
|---|---|
requestId |
String? |
status |
HeaderSyncStatus |
On failure: no failure event.
final status = (await libspiffy.coordinator.ask(GetHeaderSyncStatusQuery())).status;To follow sync without polling, listen for HeaderSyncStatusEvent (emitted when synced changes) and BlockHeadersStoredEvent (each batch stored).