-
Notifications
You must be signed in to change notification settings - Fork 77
Expand file tree
/
Copy pathbbqr.dart
More file actions
121 lines (102 loc) · 3.45 KB
/
Copy pathbbqr.dart
File metadata and controls
121 lines (102 loc) · 3.45 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
import 'dart:convert';
import 'package:bb_mobile/core/bbqr/bbqr_options.dart';
import 'package:bb_mobile/core/errors/bull_exception.dart';
import 'package:bb_mobile/core/utils/bitcoin_tx.dart';
import 'package:bb_mobile/core/utils/logger.dart';
import 'package:bull_sdk/bdk.dart' as bdk;
import 'package:convert/convert.dart';
import 'package:bull_sdk/bbqr.dart' as bbqr;
enum TxFormat { psbt, hex }
class ScannedTransaction {
final TxFormat format;
final String data;
final BitcoinTx tx;
ScannedTransaction({
required this.format,
required this.data,
required this.tx,
});
}
class Bbqr {
final Map<int, String> parts = {};
BbqrOptions? options;
Bbqr();
bool get isScanningBbqr => parts.isNotEmpty && options != null;
Future<(ScannedTransaction?, Bbqr)> scanTransaction(String payload) async {
if (!BbqrOptions.isValid(payload)) {
try {
final tx = await BitcoinTx.fromBytes(hex.decode(payload));
return (
ScannedTransaction(format: TxFormat.hex, data: payload, tx: tx),
this,
);
} catch (e) {
log.severe(error: e, trace: StackTrace.current);
return (null, this);
}
} else {
final scannedOptions = BbqrOptions.decode(payload);
if (options != null && scannedOptions.total != options!.total) {
// reset another state.bbqr
// and expect the next scan to be a new BBQR
parts.clear();
return (null, this);
}
options = scannedOptions;
parts[options!.share] = payload;
if (options!.total == parts.length) {
final bbqrParts = parts.values.toList();
final bbqrJoiner = await bbqr.Joined.tryFromParts(parts: bbqrParts);
try {
final tx = await BitcoinTx.fromBytes(bbqrJoiner.data);
return (
ScannedTransaction(
format: TxFormat.hex,
data: hex.encode(bbqrJoiner.data),
tx: tx,
),
this,
);
} catch (_) {}
try {
final psbtBase64 = base64.encode(bbqrJoiner.data);
final tx = await BitcoinTx.fromPsbt(psbtBase64);
return (
ScannedTransaction(format: TxFormat.psbt, data: psbtBase64, tx: tx),
this,
);
} catch (_) {}
throw FailedToParseBbqr();
} else {
return (null, this);
}
}
}
static Future<List<String>> splitPsbt(String psbt) async {
// check if the PSBT is valid, will throw if not
final parsedPsbt = bdk.Psbt(psbtBase64: psbt);
final validPstb = parsedPsbt.serialize();
final psbtBytes = base64.decode(validPstb);
// The more we split the easier it is to scan the QR code.
var minSplitNumber = BigInt.from(psbtBytes.length ~/ 1000);
if (minSplitNumber < BigInt.from(1)) minSplitNumber = BigInt.from(1);
final defaultOptions = await bbqr.SplitOptions.default_();
final bbqrOptions = bbqr.SplitOptions(
minVersion: defaultOptions.minVersion,
maxVersion: defaultOptions.maxVersion,
encoding: defaultOptions.encoding,
maxSplitNumber: defaultOptions.maxSplitNumber,
minSplitNumber: minSplitNumber,
);
final split = await bbqr.Split.tryFromData(
bytes: psbtBytes,
fileType: bbqr.FileType.psbt,
options: bbqrOptions,
);
return split.parts;
}
}
class FailedToParseBbqr extends BullException {
FailedToParseBbqr()
: super('The scanned transaction is neither a PSBT nor a hex string');
}