onCommandProcessed method
Send response messages after successful command processing Only active when aggregate is used as an actor in the actor system
Implementation
@override
Future<void> onCommandProcessed(Command command, List<Event> events) async {
await super.onCommandProcessed(command, events);
// A reply prepared by the command handler (signed or funding
// transaction) is released only now that its events are journaled.
final awaitingPersist = _repliesAwaitingPersist.remove(command.commandId);
// Send actor system responses. Key material is already in secure storage:
// _handleCreateWallet writes it before the event is persisted (audit H4).
if (_isInActorSystem()) {
final sender = _capturedSenders[command.commandId];
if (sender != null) {
if (awaitingPersist != null) {
sender.tell(awaitingPersist);
}
// A reclaim journals a whole recording (its transaction, its wallet
// outputs and its hold) and answers once, with the reclaim
// (libspiffy-87a); the per-event replies below would answer the ask
// with the first of those instead.
for (final event in command is ReclaimDeferredSpendCommand
? events.whereType<DeferredSpendReclaimedEvent>()
: events) {
if (event is WalletCreatedEvent) {
sender.tell(WalletCreatedResponse(
walletId: event.walletId,
rootAddress: event.rootAddress,
success: true,
));
} else if (event is AddressGeneratedEvent) {
sender.tell(AddressGeneratedResponse(
walletId: event.walletId,
address: event.address,
derivationIndex: event.derivationIndex,
chain: event.chain,
success: true,
publicKeyHex: event.publicKeyHex,
metadata: event.metadata,
));
} else if (event is UTXOReceivedEvent) {
sender.tell(UTXOReceivedResponse(
walletId: event.walletId,
txid: event.txid,
vout: event.vout,
success: true,
));
} else if (event is TransactionImportedEvent) {
sender.tell(TransactionRecordedResponse(
walletId: event.walletId,
txid: event.txid,
success: true,
));
} else if (event is UTXOReservedEvent) {
sender.tell(UTXOReservedResponse(
walletId: event.walletId,
utxoKey: '${event.txid}:${event.vout}',
reservedByTxId: event.reservedByTxId,
success: true,
));
} else if (event is DeferredTransactionCancelledEvent) {
sender.tell(DeferredSpendCancelledResponse(
walletId: event.walletId,
txid: event.txid,
success: true,
releasedUtxoKeys: [for (final r in event.releasedInputs) r.utxoKey],
));
} else if (event is DeferredSpendReclaimedEvent) {
sender.tell(DeferredSpendReclaimedResponse(
walletId: event.walletId,
txid: event.txid,
reclaimTxid: event.reclaimTxid,
success: true,
reclaimedUtxoKeys: event.reclaimedUtxoKeys,
));
}
}
// The recording is journaled (or was already, bead libspiffy-viy):
// a caller that must not act before it (BenfordCoordinatorActor
// broadcasts only after this, bead libspiffy-ypp) waits for this.
if (command is RecordOutgoingTransactionCommand) {
// The amount comes off the event this command journaled, not off
// the command: it is what the wallet recorded, and it is absent
// when nothing was journaled because the transaction was recorded
// already (bead libspiffy-viy). An absence, never a zero (bead
// libspiffy-5ml6).
final recorded = events.whereType<TransactionRecordedEvent>().firstOrNull;
sender.tell(TransactionRecordedResponse(
walletId: command.walletId,
txid: command.txid,
paymentAmount: recorded == null ? null : BigInt.tryParse(recorded.paymentAmount),
success: true,
));
}
// Answered also when nothing was journaled (already watched or owned).
if (command is AddWatchAddressCommand) {
sender.tell(WatchAddressAddedResponse(
walletId: command.walletId,
address: command.address,
success: true,
journaled: events.isNotEmpty,
));
}
}
}
// The WalletCreatedEvent is journaled, so the key material written in
// _handleCreateWallet is now committed: stop tracking it for rollback.
if (command is CreateWalletCommand) {
_keyMaterialAwaitingPersist.remove(command.commandId);
}
}