Skip to content

7 · Validate the BEEF

Bob has been handed a BEEF. Before he counts it as paid, his wallet checks it without trusting Alice or a server:

  • each merkle proof in the BEEF against the block headers Bob’s wallet synced itself;
  • that the outputs pay the invoice’s addresses;
  • that the amount matches the invoice.

If it’s valid, libspiffy broadcasts it through ARC (Arcade, here).

bin/first_payment.dart (step 7)
// Step 7: Bob validates the BEEF he was handed, which also broadcasts it.
try {
final verdict = await coordinator.ask(ValidateBEEFCommand(
walletId: 'bob',
beefHex: hexEncode(payment.beefBytes),
invoiceId: invoice.invoiceId,
));
print('Bob: valid=${verdict.valid} broadcasted=${verdict.broadcasted} '
'networkStatus=${verdict.networkStatus}');
} on CoordinatorFailure catch (e) {
print('Bob refused the payment: ${e.message}');
rethrow;
}
Bob: valid=true broadcasted=true networkStatus=SEEN_ON_NETWORK
[bob] invoice d2fb82ed-424b-4477-9a65-fc8e9394947a paid: 100000 sats in c5013d51…

ValidateBEEFCommand takes the BEEF hex-encoded. Its reply is a BEEFValidationResultEvent. An invalid BEEF is a failed request, so ask throws CoordinatorFailure with the reason, and the try/catch above handles it. When the BEEF is valid, the reply carries:

  • broadcasted and networkStatus: what ARC said when the payment was sent.
  • broadcastError: why the broadcast failed, when it did.

If a proof refers to a block whose header Bob’s wallet doesn’t hold yet, the BEEF can’t be checked yet either. ask then throws, and the failure’s event (e.event, a BEEFValidationResultEvent) has awaitingHeader set to true. The wallet keeps the payment and validates it once the header arrives. That later verdict carries no request id, so follow it with coordinator.on<BEEFValidationResultEvent>(walletId: 'bob').

The line invoice … paid comes from an InvoicePaidEvent, which libspiffy emits once ARC reports the network holds the payment. You print it from the listener you add in step 8.

Next: settle up and react to events →