dev
dart 121 lines 3.81 KB
Raw
1 import 'dart:async';
2 import 'dart:io';
3 import 'dart:isolate';
4
5 import 'package:cw_bitcoin/payjoin/manager.dart';
6 import 'package:cw_bitcoin/payjoin/payjoin_session_errors.dart';
7 import 'package:cw_core/utils/print_verbose.dart';
8 import 'package:cw_core/utils/proxy_wrapper.dart';
9 import 'package:cw_core/utils/tor/abstract.dart';
10 import 'package:payjoin_flutter/common.dart';
11 import 'package:payjoin_flutter/send.dart';
12 import 'package:payjoin_flutter/src/generated/frb_generated.dart' as pj;
13 import 'package:payjoin_flutter/src/generated/api/send/error.dart' as pj_error;
14 import 'package:payjoin_flutter/uri.dart' as pj_uri;
15
16 enum PayjoinSenderRequestTypes {
17 requestPosted,
18 psbtToSign;
19 }
20
21 class PayjoinSenderWorker {
22 final SendPort sendPort;
23 final pendingRequests = <String, Completer<dynamic>>{};
24 final String pjUrl;
25
26 PayjoinSenderWorker._(this.sendPort, this.pjUrl);
27
28 static Future<void> run(List<Object> args) async {
29 await pj.core.init();
30 CakeTor.instance = await CakeTorInstance.getInstance();
31
32 final sendPort = args[0] as SendPort;
33 final senderJson = args[1] as String;
34 final pjUrl = args[2] as String;
35
36 final sender = Sender.fromJson(json: senderJson);
37 final worker = PayjoinSenderWorker._(sendPort, pjUrl);
38
39 try {
40 final proposalPsbt = await worker.runSender(sender);
41 sendPort.send({
42 'type': PayjoinSenderRequestTypes.psbtToSign,
43 'psbt': proposalPsbt,
44 });
45 } catch (e) {
46 sendPort.send(e);
47 }
48 }
49
50 final client = ProxyWrapper().getHttpIOClient();
51
52 /// Run a payjoin sender (V2 protocol first, fallback to V1).
53 Future<String> runSender(Sender sender) async {
54 try {
55 return await _runSenderV2(sender);
56 } catch (e) {
57 printV(e);
58 if (e is pj_error.FfiCreateRequestError) {
59 return await _runSenderV1(sender);
60 } else if (e is HttpException) {
61 printV(e);
62 throw Exception(PayjoinSessionError.recoverable(e.toString()));
63 } else {
64 throw Exception(PayjoinSessionError.unrecoverable(e.toString()));
65 }
66 }
67 }
68
69 /// Attempt to send payjoin using the V2 of the protocol.
70 Future<String> _runSenderV2(Sender sender) async {
71 try {
72 final postRequest = await sender.extractV2(
73 ohttpProxyUrl: await pj_uri.Url.fromStr(PayjoinManager.randomOhttpRelayUrl()),
74 );
75
76 final postResult = await _postRequest(postRequest.$1);
77 final getContext = await postRequest.$2.processResponse(response: postResult);
78
79 sendPort.send({'type': PayjoinSenderRequestTypes.requestPosted, "pj": pjUrl});
80
81 while (true) {
82 printV('Polling V2 Proposal Request (${pjUrl})');
83
84 final getRequest = await getContext.extractReq(
85 ohttpRelay: await PayjoinManager.randomOhttpRelayUrl(),
86 );
87 final getRes = await _postRequest(getRequest.$1);
88 final proposalPsbt = await getContext.processResponse(
89 response: getRes,
90 ohttpCtx: getRequest.$2,
91 );
92 printV("$proposalPsbt");
93 if (proposalPsbt != null) return proposalPsbt;
94 sleep(Duration(seconds: 2));
95 }
96 } catch (e) {
97 rethrow;
98 }
99 }
100
101 /// Attempt to send payjoin using the V1 of the protocol.
102 Future<String> _runSenderV1(Sender sender) async {
103 try {
104 final postRequest = await sender.extractV1();
105 final response = await _postRequest(postRequest.$1);
106
107 sendPort.send({'type': PayjoinSenderRequestTypes.requestPosted});
108
109 return await postRequest.$2.processResponse(response: response);
110 } catch (e, stack) {
111 throw PayjoinSessionError.unrecoverable('Send V1 payjoin error: $e, $stack');
112 }
113 }
114
115 Future<List<int>> _postRequest(Request req) async {
116 final httpRequest = await client.post(Uri.parse(req.url.asString()),
117 headers: {'Content-Type': req.contentType}, body: req.body);
118
119 return httpRequest.bodyBytes;
120 }
121 }