dev
dart 127 lines 4.68 KB
Raw
1 import 'dart:convert';
2 import 'dart:typed_data';
3
4 import 'package:bitcoin_base/bitcoin_base.dart';
5 import 'package:convert/convert.dart';
6 import 'package:cw_core/output_info.dart';
7 import 'package:cw_core/utils/print_verbose.dart';
8 import 'package:ledger_bitcoin/psbt.dart';
9
10 class PSBTTransactionBuild {
11 final PsbtV2 psbt = PsbtV2();
12
13 PSBTTransactionBuild(
14 {required List<PSBTReadyUtxoWithAddress> inputs,
15 required List<BitcoinBaseOutput> outputs,
16 required List<OutputInfo> cwOutputs,
17 bool enableRBF = true,
18 int locktime = 0}) {
19 psbt.setGlobalTxVersion(2);
20 psbt.setGlobalFallbackLocktime(locktime);
21 psbt.setGlobalInputCount(inputs.length);
22 psbt.setGlobalOutputCount(outputs.length);
23
24 for (var i = 0; i < inputs.length; i++) {
25 final input = inputs[i];
26
27 printV(input.utxo.isP2tr());
28 printV(input.utxo.isSegwit());
29 printV(input.utxo.isP2shSegwit());
30
31 psbt.setInputPreviousTxId(
32 i, Uint8List.fromList(hex.decode(input.utxo.txHash).reversed.toList()));
33 psbt.setInputOutputIndex(i, input.utxo.vout);
34 psbt.setInputSequence(i, enableRBF ? 0xfffffffd : 0xffffffff);
35
36 if (input.utxo.isSegwit()) {
37 setInputSegwit(i, input);
38 } else if (input.utxo.isP2shSegwit()) {
39 setInputP2shSegwit(i, input);
40 } else if (input.utxo.isP2tr()) {
41 // ToDo: (Konsti) Handle Taproot Inputs
42 } else {
43 setInputP2pkh(i, input);
44 }
45 }
46
47 for (var i = 0; i < outputs.length; i++) {
48 final output = outputs[i];
49
50 if (output is BitcoinOutput) {
51 psbt.setOutputScript(i, Uint8List.fromList(output.address.toScriptPubKey().toBytes()));
52 psbt.setOutputAmount(i, output.value.toInt());
53 if (cwOutputs.isNotEmpty) {
54 try {
55 final cwOutput = cwOutputs
56 .where((e) => [e.address, e.extractedAddress]
57 .map((e) => e?.toLowerCase())
58 .contains(output.address.toAddress().toLowerCase()))
59 .firstOrNull;
60 if (cwOutput != null &&
61 cwOutput.extra.containsKey('bip353_name') &&
62 cwOutput.extra.containsKey('bip353_proof')) {
63 final bip353Name = utf8.encode(cwOutput.extra['bip353_name'] as String);
64 final bip353Proof = base64.decode(cwOutput.extra['bip353_proof'] as String);
65
66 if (bip353Name.length > 255) {
67 printV('BIP353 name is too long, skipping');
68 continue;
69 }
70 final proof = Uint8List.fromList([
71 bip353Name.length,
72 ...bip353Name,
73 ...bip353Proof,
74 ]);
75
76 psbt.setOutputDNSSECProof(i, proof);
77 }
78 } catch (e) {
79 printV('Error setting DNSSEC proof: $e');
80 }
81 }
82 }
83 }
84 }
85
86 void setInputP2pkh(int i, PSBTReadyUtxoWithAddress input) {
87 psbt.setInputNonWitnessUtxo(i, Uint8List.fromList(hex.decode(input.rawTx)));
88 psbt.setInputBip32Derivation(i, Uint8List.fromList(hex.decode(input.ownerPublicKey)),
89 input.ownerMasterFingerprint, BIPPath.fromString(input.ownerDerivationPath).toPathArray());
90 }
91
92 void setInputSegwit(int i, PSBTReadyUtxoWithAddress input) {
93 psbt.setInputNonWitnessUtxo(i, Uint8List.fromList(hex.decode(input.rawTx)));
94 psbt.setInputBip32Derivation(i, Uint8List.fromList(hex.decode(input.ownerPublicKey)),
95 input.ownerMasterFingerprint, BIPPath.fromString(input.ownerDerivationPath).toPathArray());
96
97 psbt.setInputWitnessUtxo(i, Uint8List.fromList(bigIntToUint64LE(input.utxo.value)),
98 Uint8List.fromList(input.ownerDetails.address.toScriptPubKey().toBytes()));
99 }
100
101 void setInputP2shSegwit(int i, PSBTReadyUtxoWithAddress input) {
102 psbt.setInputNonWitnessUtxo(i, Uint8List.fromList(hex.decode(input.rawTx)));
103 psbt.setInputBip32Derivation(i, Uint8List.fromList(hex.decode(input.ownerPublicKey)),
104 input.ownerMasterFingerprint, BIPPath.fromString(input.ownerDerivationPath).toPathArray());
105
106 psbt.setInputRedeemScript(
107 i, Uint8List.fromList(input.ownerDetails.address.toScriptPubKey().toBytes()));
108 psbt.setInputWitnessUtxo(i, Uint8List.fromList(bigIntToUint64LE(input.utxo.value)),
109 Uint8List.fromList(input.ownerDetails.address.toScriptPubKey().toBytes()));
110 }
111 }
112
113 class PSBTReadyUtxoWithAddress extends UtxoWithAddress {
114 final String rawTx;
115 final String ownerDerivationPath;
116 final Uint8List ownerMasterFingerprint;
117 final String ownerPublicKey;
118
119 PSBTReadyUtxoWithAddress({
120 required super.utxo,
121 required this.rawTx,
122 required super.ownerDetails,
123 required this.ownerDerivationPath,
124 required this.ownerMasterFingerprint,
125 required this.ownerPublicKey,
126 });
127 }