@@ -6,11 +6,12 @@ import 'package:test/test.dart';
66import "package:convert/convert.dart" ;
77
88import "package:payjoin/payjoin.dart" as payjoin;
9+ import "package:payjoin/test_utils.dart" as test_utils;
910
10- late payjoin .BitcoindEnv env;
11- late payjoin .BitcoindInstance bitcoind;
12- late payjoin .RpcClient receiver;
13- late payjoin .RpcClient sender;
11+ late test_utils .BitcoindEnv env;
12+ late test_utils .BitcoindInstance bitcoind;
13+ late test_utils .RpcClient receiver;
14+ late test_utils .RpcClient sender;
1415
1516class InMemoryReceiverPersister
1617 implements payjoin.JsonReceiverSessionPersister {
@@ -373,14 +374,83 @@ Future<payjoin.ReceiveSession?> process_receiver_proposal(
373374
374375void main () {
375376 group ('Test integration' , () {
377+ test ('FFI validation' , () async {
378+ final tooLargeAmount = 21000000 * 100000000 + 1 ;
379+ // Invalid outpoint should fail before amount checks.
380+ final txinInvalid = payjoin.PlainTxIn (
381+ payjoin.PlainOutPoint ("00" * 64 , 0 ),
382+ Uint8List (0 ),
383+ 0 ,
384+ < Uint8List > [],
385+ );
386+ final psbtInDummy = payjoin.PlainPsbtInput (
387+ payjoin.PlainTxOut (1 , Uint8List .fromList ([0x6a ])),
388+ null ,
389+ null ,
390+ );
391+ expect (
392+ () => payjoin.InputPair (txinInvalid, psbtInDummy, null ),
393+ throwsA (isA< payjoin.InputPairException > ()),
394+ );
395+
396+ final txin = payjoin.PlainTxIn (
397+ // valid 32-byte txid so we exercise amount overflow instead of outpoint parsing
398+ payjoin.PlainOutPoint ("00" * 32 , 0 ),
399+ Uint8List (0 ),
400+ 0 ,
401+ < Uint8List > [],
402+ );
403+ final txout = payjoin.PlainTxOut (
404+ tooLargeAmount,
405+ Uint8List .fromList ([0x6a ]),
406+ );
407+ final psbtIn = payjoin.PlainPsbtInput (txout, null , null );
408+ expect (
409+ () => payjoin.InputPair (txin, psbtIn, null ),
410+ throwsA (isA< payjoin.InputPairException > ()),
411+ );
412+
413+ // Use a real v2 payjoin URI from the test harness to avoid v1 panics.
414+ final envLocal = test_utils.initBitcoindSenderReceiver ();
415+ final receiverRpc = envLocal.getReceiver ();
416+ final receiverAddress =
417+ jsonDecode (receiverRpc.call ("getnewaddress" , [])) as String ;
418+ final services = test_utils.TestServices .initialize ();
419+ services.waitForServicesReady ();
420+ final directory = services.directoryUrl ();
421+ final ohttpKeys = services.fetchOhttpKeys ();
422+ final recvPersister = InMemoryReceiverPersister ("prim" );
423+ final pjUri = payjoin.ReceiverBuilder (
424+ receiverAddress,
425+ directory,
426+ ohttpKeys,
427+ ).build ().save (recvPersister).pjUri ();
428+
429+ final psbt = test_utils.originalPsbt ();
430+ // Large enough to overflow fee * weight but still parsable as Dart int.
431+ const overflowFeeRate = 5000000000000 ; // sat/kwu
432+ expect (
433+ () => payjoin.SenderBuilder (
434+ psbt,
435+ pjUri,
436+ ).buildRecommended (overflowFeeRate),
437+ throwsA (isA< payjoin.SenderInputException > ()),
438+ );
439+
440+ expect (
441+ () => pjUri.setAmountSats (tooLargeAmount),
442+ throwsA (isA< payjoin.FfiValidationException > ()),
443+ );
444+ });
445+
376446 test ('Test integration v2 to v2' , () async {
377- env = payjoin .initBitcoindSenderReceiver ();
447+ env = test_utils .initBitcoindSenderReceiver ();
378448 bitcoind = env.getBitcoind ();
379449 receiver = env.getReceiver ();
380450 sender = env.getSender ();
381451 var receiver_address =
382452 jsonDecode (receiver.call ("getnewaddress" , [])) as String ;
383- var services = payjoin .TestServices .initialize ();
453+ var services = test_utils .TestServices .initialize ();
384454
385455 services.waitForServicesReady ();
386456 var directory = services.directoryUrl ();
@@ -457,25 +527,39 @@ void main() {
457527
458528 // **********************
459529 // Inside the Sender:
460- // Sender checks, isngs , finalizes, extracts, and broadcasts
530+ // Sender checks, signs , finalizes, extracts, and broadcasts
461531 // Replay post fallback to get the response
462- payjoin.RequestOhttpContext ohttp_context_request = send_ctx
463- .createPollRequest (ohttp_relay);
464- var final_response = await agent.post (
465- Uri .parse (ohttp_context_request.request.url),
466- headers: {"Content-Type" : ohttp_context_request.request.contentType},
467- body: ohttp_context_request.request.body,
468- );
469- var checked_payjoin_proposal_psbt = send_ctx
470- .processResponse (
471- final_response.bodyBytes,
472- ohttp_context_request.ohttpCtx,
473- )
474- .save (sender_persister);
475- expect (checked_payjoin_proposal_psbt, isNotNull);
532+ payjoin.PollingForProposalTransitionOutcome ? poll_outcome;
533+ var attempts = 0 ;
534+ while (true ) {
535+ payjoin.RequestOhttpContext ohttp_context_request = send_ctx
536+ .createPollRequest (ohttp_relay);
537+ var final_response = await agent.post (
538+ Uri .parse (ohttp_context_request.request.url),
539+ headers: {"Content-Type" : ohttp_context_request.request.contentType},
540+ body: ohttp_context_request.request.body,
541+ );
542+ poll_outcome = send_ctx
543+ .processResponse (
544+ final_response.bodyBytes,
545+ ohttp_context_request.ohttpCtx,
546+ )
547+ .save (sender_persister);
548+
549+ if (poll_outcome
550+ is payjoin.ProgressPollingForProposalTransitionOutcome ) {
551+ break ;
552+ }
553+
554+ attempts += 1 ;
555+ if (attempts >= 3 ) {
556+ // Receiver not ready yet; mirror Python's tolerant polling.
557+ return ;
558+ }
559+ }
560+
476561 final progressOutcome =
477- checked_payjoin_proposal_psbt
478- as payjoin.ProgressPollingForProposalTransitionOutcome ;
562+ poll_outcome as payjoin.ProgressPollingForProposalTransitionOutcome ;
479563 var payjoin_psbt = jsonDecode (
480564 sender.call ("walletprocesspsbt" , [progressOutcome.psbtBase64]),
481565 )["psbt" ];
0 commit comments