Skip to content

bug: wallet.transactions() may throw UniFfi::UnexpectedEnumCase due to ChainPosition converter offset accounting #46

@reez

Description

@reez

Problem

A user reported wallet.transactions() crashing with:

UniFfi::UnexpectedEnumCase at FfiConverterChainPosition.read(...).

Calling wallet.transactions() on a wallet with transaction history can throw while decoding CanonicalTx.chainPosition.

Root cause

In generated Dart bindings, FfiConverterChainPosition (non-flat enum) returned absolute offsets from variant read()/write(), while parent decoders expect relative byte counts.

This causes offset drift when decoding sequences/records from non-zero buffer offsets, eventually corrupting enum tag parsing.

Notes

This appears to be an upstream uniffi-dart codegen issue?

Error

Future<List<Map<String, dynamic>>> getTransactions() async {
    print('[TX] Starting getTransactions()');

    try {
      final results = <Map<String, dynamic>>[];

      print('[TX] Fetching canonical transactions from wallet...');
      for (final c in wallet.transactions()) { // Throws error
        try {
          final txid = c.transaction.computeTxid();
          print('[TX] OK tx: $txid');
        } catch (e) {
          print('[TX] Failed decoding canonical tx: $e');
        }
      }
      // print('[TX] Canonical tx count: ${canonical.length}');

      List<CanonicalTx> canonical = [];

      for (final c in canonical) {
        print('[TX] Processing canonical tx...');

        final txid = c.transaction.computeTxid();
        print('[TX] Computed txid: $txid');

        print('[TX] Fetching TxDetails for txid...');
        final details = wallet.txDetails(txid);

        if (details == null) {
          print('[TX][WARN] txDetails returned NULL for $txid');
          continue;
        }

        print('[TX] Converting TxDetails to JSON...');
        final json = details.toJson();

        results.add(json);
        print('[TX] Added tx $txid to results');
      }

      print('[TX] getTransactions() completed. Total: ${results.length}');
      return results;
    } catch (e, st) {
      print('[TX][ERROR] Failed to fetch transactions');
      print('[TX][ERROR] $e');
      print('[TX][STACKTRACE]\n$st');
      throw Exception('Failed to fetch transactions: $e');
    }
  }


I/flutter (28298): [TX] Starting getTransactions()
I/flutter (28298): [TX] Fetching canonical transactions from wallet...
I/flutter (28298): [TX][ERROR] Failed to fetch transactions
I/flutter (28298): [TX][ERROR] UniFfi::UnexpectedEnumCase
I/flutter (28298): [TX][STACKTRACE]
I/flutter (28298): #0 FfiConverterChainPosition.read (package:bdk_dart/bdk.dart:5194:9)
I/flutter (28298): #1 FfiConverterCanonicalTx.read (package:bdk_dart/bdk.dart:330:60)
I/flutter (28298): #2 FfiConverterSequenceCanonicalTx.read (package:bdk_dart/bdk.dart:23632:43)
I/flutter (28298): #3 FfiConverterSequenceCanonicalTx.lift (package:bdk_dart/bdk.dart:23624:44)
I/flutter (28298): #4 rustCallWithLifter (package:bdk_dart/bdk.dart:22780:18)
I/flutter (28298): #5 Wallet.transactions (package:bdk_dart/bdk.dart:22526:12)
I/flutter (28298): #6 WalletService.getTransactions (package:flutter_wallet/services/wallet_service.dart:752:30)
I/flutter (28298): #7 WalletPageState._syncWallet (package:flutter_wallet/wallet_pages/wallet_page.dart:340:31)
I/flutter (28298): <asynchronous suspension>
I/flutter (28298): ❌ ERROR DURING SYNC: Exception: Failed to fetch transactions: UniFfi::UnexpectedEnumCase
I/flutter (28298): #0 WalletService.getTransactions (package:flutter_wallet/services/wallet_service.dart:791:7)
I/flutter (28298): #1 WalletPageState._syncWallet (package:flutter_wallet/wallet_pages/wallet_page.dart:340:31)
I/flutter (28298): <asynchronous suspension>
I/flutter (28298): E/flutter (28298): [ERROR:flutter/runtime/dart_vm_initializer.cc(40)] Unhandled Exception: Exception: Sync Error: Exception: Failed to fetch transactions: UniFfi::UnexpectedEnumCase
E/flutter (28298): #0 WalletPageState._syncWallet (package:flutter_wallet/wallet_pages/wallet_page.dart:424:7)
E/flutter (28298): <asynchronous suspension>

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions