Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,11 @@ class GetTransactionsUsecase {
}
final orderBuckets = <String, List<int>>{};
for (var index = 0; index < orders.length; index++) {
final txId = orders[index].transactionId;
if (txId != null) orderBuckets.putIfAbsent(txId, () => []).add(index);
final order = orders[index];
// The payjoin is broadcast before the payout txid is reported.
for (final txId in {order.transactionId, order.payjoin?.txid}) {
if (txId != null) orderBuckets.putIfAbsent(txId, () => []).add(index);
}
}
final consumedPayjoinIndices = <int>{};
final consumedOrderIndices = <int>{};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import 'dart:collection';
import 'dart:typed_data';

import 'package:bb_mobile/core/exchange/domain/repositories/exchange_order_repository.dart';
import 'package:bb_mobile/core/exchange/domain/entity/order.dart';
import 'package:bb_mobile/core/settings/data/settings_repository.dart';
import 'package:bb_mobile/core/settings/domain/settings_entity.dart';
import 'package:bb_mobile/core/swaps/data/repository/boltz_swap_repository.dart';
import 'package:bb_mobile/core/wallet/domain/entities/transaction_output.dart';
import 'package:bb_mobile/core/wallet/domain/repositories/wallet_transaction_repository.dart';
import 'package:bb_mobile/core/wallet/domain/entities/wallet_transaction.dart';
import 'package:bb_mobile/core/wallet/domain/entities/wallet.dart';
Expand Down Expand Up @@ -211,6 +213,50 @@ void main() {
expect(transactions.single.orderSwap?.localId, 'receive-order');
});

test('binds a buy the exchange still knows only by its payjoin txid', () async {
// The exchange broadcasts the payjoin before it reports that transaction as
// the order's payout, so the order carries no payout txid yet while the
// wallet already sees the transaction. Both describe the same event and the
// amounts agree; the list must show one row, not "Bitcoin" plus "Buy
// Bitcoin".
final payout = _walletTransaction(
txId: 'payjoin-txid',
walletId: 'w1',
isIncoming: true,
amountSat: 175906,
address: 'bc1qmine',
);
when(
() => walletTransactionRepository.getWalletTransactions(
walletId: any(named: 'walletId'),
sync: any(named: 'sync'),
environment: any(named: 'environment'),
),
).thenAnswer((_) async => [payout]);
when(
() => payjoinSessions.list(any()),
).thenAnswer((_) async => const Ok([]));
orders.add(
_buyOrder(
txId: null,
address: 'bc1qmine',
payoutAmountBtc: 0.00175906,
payjoinTxid: 'payjoin-txid',
orderStatus: OrderStatus.inProgress,
),
);

final transactions = await usecase.execute();

expect(
transactions,
hasLength(1),
reason: 'the order must not be listed again beside its own payjoin',
);
expect(transactions.single.order?.orderId, 'buy-order');
expect(transactions.single.walletTransaction?.txId, 'payjoin-txid');
});

test('keeps one canonical row for an internal chain swap', () async {
final payin = _walletTransaction(
txId: 'payin-tx',
Expand Down Expand Up @@ -390,6 +436,8 @@ WalletTransaction _walletTransaction({
required String txId,
required String walletId,
required bool isIncoming,
int amountSat = 1000,
String? address,
}) {
return WalletTransaction(
walletId: walletId,
Expand All @@ -399,15 +447,53 @@ WalletTransaction _walletTransaction({
: WalletTransactionDirection.outgoing,
status: WalletTransactionStatus.confirmed,
txId: txId,
amountSat: 1000,
amountSat: amountSat,
feeSat: 1,
vsize: 100,
inputs: const [],
outputs: const [],
outputs: [
if (address != null)
TransactionOutput.bitcoin(
txId: txId,
vout: 0,
isOwn: isIncoming,
scriptPubkey: Uint8List(0),
address: address,
),
],
isRbf: false,
);
}

Order _buyOrder({
required String? txId,
required String address,
required double payoutAmountBtc,
String? payjoinTxid,
OrderStatus orderStatus = OrderStatus.completed,
}) => Order.buy(
orderId: 'buy-order',
orderType: OrderType.buy,
message: OrderMessage(code: '', message: ''),
orderNumber: 1,
payinAmount: 100,
payinCurrency: 'CAD',
payoutAmount: payoutAmountBtc,
payoutCurrency: 'BTC',
payinMethod: OrderPaymentMethod.cadBalance,
payoutMethod: OrderPaymentMethod.bitcoin,
orderStatus: orderStatus,
payinStatus: OrderPayinStatus.completed,
payoutStatus: OrderPayoutStatus.completed,
createdAt: DateTime.utc(2026),
bitcoinAddress: address,
bitcoinTransactionId: txId,
payjoinDetails: payjoinTxid == null
? null
: OrderPayjoinDetails(txid: payjoinTxid),
isTestnet: false,
);

OrderSwapRecord _receiveOrderSwap() => OrderSwapRecord(
localId: 'receive-order',
requestId: 'receive-request',
Expand Down
Loading