1717/// mobile wallet, and starts a mobile sender session.
1818/// 3. payjoin-cli picks up the request, processes it, and posts a proposal
1919/// back to the directory.
20- /// 4. The mobile sender polls, picks up the proposal, signs and broadcasts
21- /// the final tx, reaching PayjoinStatus.completed.
20+ /// 4. The mobile sender's automatic poll loop picks up the proposal, signs
21+ /// and broadcasts the final tx, reaching PayjoinStatus.completed.
2222///
2323/// Prerequisites:
24- /// - Mobile wallet funded with testnet3 tBTC (TEST_ALICE_MNEMONIC in .env )
24+ /// - Mobile wallet funded with testnet3 tBTC (TEST_ALICE_MNEMONIC)
2525/// - payjoin-cli receiver already running (managed by the shell script)
2626///
27- /// Environment variables:
28- /// TEST_ALICE_MNEMONIC Mobile wallet mnemonic (needs testnet3 tBTC)
29- /// PJ_BIP21_URI BIP21 pjURI from payjoin-cli (set by script)
27+ /// Environment variables (passed via --dart-define / --dart-define-from-file):
28+ /// TEST_ALICE_MNEMONIC Mobile wallet mnemonic (needs testnet3 tBTC)
29+ /// PJ_BIP21_URI BIP21 pjURI from payjoin-cli (set by script)
30+ /// PAYJOIN_CLI_SEND_AMOUNT_SAT Amount in sat (default: 2000)
3031library ;
3132
3233import 'dart:async' ;
3334import 'dart:io' ;
3435
35- import 'package:bb_mobile/core/blockchain/data/datasources/bdk_bitcoin_blockchain_datasource.dart' ;
36- import 'package:bb_mobile/core/blockchain/domain/ports/electrum_server_port.dart' ;
3736import 'package:bb_mobile/core/fees/domain/fees_entity.dart' ;
38- import 'package:bb_mobile/core/payjoin/data/datasources/local_payjoin_datasource.dart' ;
39- import 'package:bb_mobile/core/payjoin/data/datasources/pdk_payjoin_datasource.dart' ;
4037import 'package:bb_mobile/core/payjoin/domain/entity/payjoin.dart' ;
4138import 'package:bb_mobile/core/payjoin/domain/repositories/payjoin_repository.dart' ;
4239import 'package:bb_mobile/core/payjoin/domain/usecases/send_with_payjoin_usecase.dart' ;
4340import 'package:bb_mobile/core/seed/data/repository/seed_repository.dart' ;
4441import 'package:bb_mobile/core/settings/domain/settings_entity.dart' ;
4542import 'package:bb_mobile/core/utils/constants.dart' ;
46- import 'package:bb_mobile/core/wallet/data/repositories/bitcoin_wallet_repository.dart' ;
4743import 'package:bb_mobile/core/wallet/data/repositories/wallet_address_repository.dart' ;
4844import 'package:bb_mobile/core/wallet/data/repositories/wallet_repository.dart' ;
4945import 'package:bb_mobile/core/wallet/domain/entities/wallet.dart' ;
5046import 'package:bb_mobile/features/send/domain/usecases/prepare_bitcoin_send_usecase.dart' ;
5147import 'package:bb_mobile/features/settings/domain/usecases/set_environment_usecase.dart' ;
5248import 'package:bb_mobile/locator.dart' ;
5349import 'package:bb_mobile/main.dart' ;
54- import 'package:dio/dio.dart' ;
5550import 'package:flutter/material.dart' ;
56- import 'package:flutter_dotenv/flutter_dotenv.dart' ;
57- import 'package:payjoin_flutter/send.dart' ;
5851import 'package:integration_test/integration_test.dart' ;
5952import 'package:test/test.dart' ;
6053
61- // Compile-time defines passed via --dart-define (required for device-based tests
62- // where Platform.environment is unavailable, e.g. Android).
6354const _dartDefines = < String , String > {
6455 'PJ_BIP21_URI' : String .fromEnvironment ('PJ_BIP21_URI' ),
6556 'TEST_ALICE_MNEMONIC' : String .fromEnvironment ('TEST_ALICE_MNEMONIC' ),
66- 'TEST_BOB_MNEMONIC' : String .fromEnvironment ('TEST_BOB_MNEMONIC' ),
6757 'PAYJOIN_CLI_SEND_AMOUNT_SAT' :
6858 String .fromEnvironment ('PAYJOIN_CLI_SEND_AMOUNT_SAT' ),
6959};
@@ -72,17 +62,14 @@ String _env(String key, {String? fallback}) {
7262 final dartVal = _dartDefines[key];
7363 final val = (dartVal != null && dartVal.isNotEmpty)
7464 ? dartVal
75- : Platform .environment[key] ?? dotenv.env[key] ;
65+ : Platform .environment[key];
7666 if (val != null && val.isNotEmpty) return val;
7767 if (fallback != null ) return fallback;
7868 throw Exception ('Required environment variable $key is not set' );
7969}
8070
8171Future <void > main ({bool isInitialized = false }) async {
8272 IntegrationTestWidgetsFlutterBinding .ensureInitialized ();
83- // NOTE: Bull.init() and locator<> calls are deferred to setUpAll() so that
84- // the flutter-test VM "loading" pre-check can call main() on the Linux host
85- // without trying to dlopen libark_wallet.so (which only exists on Android).
8673
8774 late String mobileWalletMnemonic;
8875 late String pjBip21Uri;
@@ -146,9 +133,6 @@ Future<void> main({bool isInitialized = false}) async {
146133 debugPrint ('[integration] Using pjURI: $pjBip21Uri ' );
147134 expect (pjBip21Uri, contains ('pj=' ));
148135
149- final parsedUri = Uri .parse (pjBip21Uri);
150- final recipientAddress = parsedUri.path;
151-
152136 final senderCompletedCompleter = Completer <bool >();
153137 final payjoinSub = payjoinRepository.payjoinStream.listen ((payjoin) {
154138 debugPrint (
@@ -161,24 +145,10 @@ Future<void> main({bool isInitialized = false}) async {
161145 }
162146 });
163147
164- final localDatasource = locator <LocalPayjoinDatasource >();
165- final bitcoinWalletRepository = locator <BitcoinWalletRepository >();
166- final blockchain = locator <BdkBitcoinBlockchainDatasource >();
167- final dio = Dio ();
168- const electrumServer = ElectrumServer (
169- url: ApiServiceConstants .publicElectrumTestUrlTwo,
170- priority: 1 ,
171- retry: 5 ,
172- timeout: 5 ,
173- stopGap: 20 ,
174- validateDomain: true ,
175- isCustom: false ,
176- );
177-
178148 try {
179149 final preparedSend = await prepareBitcoinSendUsecase.execute (
180150 walletId: mobileWallet.id,
181- address: recipientAddress ,
151+ address: Uri . parse (pjBip21Uri).path ,
182152 amountSat: amountSat,
183153 networkFee: const NetworkFee .relative (networkFeesSatPerVb),
184154 ignoreUnspendableInputs: false ,
@@ -195,59 +165,25 @@ Future<void> main({bool isInitialized = false}) async {
195165 debugPrint ('[integration] Mobile sender created: ${sender .id }' );
196166 expect (sender.status, PayjoinStatus .requested);
197167
198- // --- Step 1: post the original PSBT to the directory ---
199- final senderModel = await localDatasource.fetchSender (sender.id);
200- expect (senderModel, isNotNull, reason: 'Sender model not found in DB' );
201- final payjoinSender = Sender .fromJson (json: senderModel! .sender);
202-
203- debugPrint ('[integration] Posting payjoin request to directory...' );
204- final getContext = await PdkPayjoinDatasource .request (
205- sender: payjoinSender,
206- dio: dio,
168+ // Sender's background poll loop posts the original PSBT, picks up the
169+ // CLI's proposal, signs it, and broadcasts — all driven by the
170+ // PdkPayjoinDatasource session loop. We just wait for completion.
171+ final completed = await senderCompletedCompleter.future.timeout (
172+ Duration (seconds: PayjoinConstants .directoryPollingInterval * 20 ),
173+ onTimeout: () => false ,
207174 );
208- debugPrint ('[integration] Request posted, polling for CLI proposal...' );
209-
210- // --- Step 2: poll until the CLI receiver posts a proposal ---
211- String ? proposalPsbt;
212- for (int i = 0 ; i < 18 ; i++ ) {
213- proposalPsbt = await PdkPayjoinDatasource .getProposalPsbt (
214- context: getContext,
215- dio: dio,
216- );
217- if (proposalPsbt != null ) break ;
218- debugPrint (
219- '[integration] No proposal yet, retrying (${i + 1 }/18)...' );
220- await Future .delayed (
221- const Duration (seconds: PayjoinConstants .directoryPollingInterval),
222- );
223- }
224175 expect (
225- proposalPsbt,
226- isNotNull,
227- reason: 'CLI did not post a payjoin proposal in time' ,
228- );
229- debugPrint ('[integration] Received CLI proposal, signing...' );
230-
231- // --- Step 3: sign the proposal PSBT and broadcast ---
232- final signedProposalPsbt = await bitcoinWalletRepository.signPsbt (
233- proposalPsbt! ,
234- walletId: mobileWallet.id,
235- );
236- debugPrint ('[integration] Proposal signed, broadcasting...' );
237-
238- final txId = await blockchain.broadcastPsbt (
239- signedProposalPsbt,
240- electrumServer: electrumServer,
176+ completed,
177+ isTrue,
178+ reason: 'Mobile sender did not reach completed status — '
179+ 'CLI receiver may not have posted a proposal in time' ,
241180 );
242- debugPrint ('[integration] Payjoin tx broadcast: $txId ' );
243- expect (txId, isNotEmpty, reason: 'Broadcast returned empty txId' );
244181 } finally {
245182 await payjoinSub.cancel ();
246- dio.close ();
247183 }
248184 },
249185 timeout: const Timeout (
250- Duration (seconds: PayjoinConstants .directoryPollingInterval * 20 ),
186+ Duration (seconds: PayjoinConstants .directoryPollingInterval * 25 ),
251187 ),
252188 );
253189}
0 commit comments