diff --git a/cw_bitcoin/lib/bitcoin_wallet.dart b/cw_bitcoin/lib/bitcoin_wallet.dart index 1c9ad8220..ac59197f6 100644 --- a/cw_bitcoin/lib/bitcoin_wallet.dart +++ b/cw_bitcoin/lib/bitcoin_wallet.dart @@ -24,6 +24,7 @@ import 'package:cw_core/encryption_file_utils.dart'; import 'package:cw_core/payjoin_session.dart'; import 'package:cw_core/pending_transaction.dart'; import 'package:cw_core/unspent_coins_info.dart'; +import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_keys_file.dart'; import 'package:flutter/foundation.dart'; @@ -32,6 +33,9 @@ import 'package:ledger_bitcoin/ledger_bitcoin.dart'; import 'package:ledger_bitcoin/psbt.dart'; import 'package:ledger_flutter_plus/ledger_flutter_plus.dart'; import 'package:mobx/mobx.dart'; +import 'package:ur/cbor_lite.dart'; +import 'package:ur/ur.dart'; +import 'package:ur/ur_decoder.dart'; part 'bitcoin_wallet.g.dart'; @@ -373,7 +377,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { masterFingerprint: Uint8List(0)); if (tx.shouldCommitUR()) { - tx.unsignedPsbt = transaction.serialize(); + tx.unsignedPsbt = transaction.asPsbtV0(); return tx; } @@ -436,6 +440,24 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store { return base64Encode(psbt.asPsbtV0()); } + Future commitPsbtUR(List urCodes) async { + final ur = URDecoder(); + for (final inp in urCodes) { + ur.receivePart(inp); + } + final result = (ur.result as UR); + final cbor = result.cbor; + final cborDecoder = CBORDecoder(cbor); + final out = cborDecoder.decodeBytes(); + final bytes = out.$1; + final base64psbt = base64Encode(bytes); + final psbt = PsbtV2()..deserializeV0(base64Decode(base64psbt)); + + // psbt.finalize(); + final finalized = base64Encode(psbt.serialize()); + await commitPsbt(finalized); + } + @override Future signMessage(String message, {String? address = null}) async { if (walletInfo.isHardwareWallet) { diff --git a/lib/bitcoin/cw_bitcoin.dart b/lib/bitcoin/cw_bitcoin.dart index 11434b64b..263fe7e84 100644 --- a/lib/bitcoin/cw_bitcoin.dart +++ b/lib/bitcoin/cw_bitcoin.dart @@ -723,6 +723,12 @@ class CWBitcoin extends Bitcoin { } } + @override + Future commitPsbtUR(Object wallet, List urCodes) { + final _wallet = wallet as BitcoinWalletBase; + return _wallet.commitPsbtUR(urCodes); + } + @override String getPayjoinEndpoint(Object wallet) { final _wallet = wallet as ElectrumWallet; diff --git a/lib/src/screens/ur/animated_ur_page.dart b/lib/src/screens/ur/animated_ur_page.dart index a4ef3517f..5f63b4ac9 100644 --- a/lib/src/screens/ur/animated_ur_page.dart +++ b/lib/src/screens/ur/animated_ur_page.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/entities/qr_scanner.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/monero/monero.dart'; @@ -7,9 +8,12 @@ import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/screens/receive/widgets/qr_image.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; import 'package:cake_wallet/src/widgets/primary_button.dart'; +import 'package:cake_wallet/utils/clipboard_util.dart'; +import 'package:cake_wallet/utils/feature_flag.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/view_model/animated_ur_model.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; // ur:xmr-txunsigned - unsigned transaction // should show a scanner afterwards. @@ -46,8 +50,8 @@ class AnimatedURPage extends BasePage { frames: urQr.trim().split("\n"), ), ), + if (["ur:xmr-txunsigned", "ur:xmr-output", "ur:psbt"].contains(urQrType)) ...{ SizedBox(height: 32), - if (urQrType == "ur:xmr-txunsigned" || urQrType == "ur:xmr-output") Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0), child: SizedBox( @@ -60,8 +64,10 @@ class AnimatedURPage extends BasePage { ), ), ), - SizedBox(height: 32), - if (urQrType == "ur:xmr-output" && !isAll) Padding( + }, + if (urQrType == "ur:xmr-output" && !isAll) ...{ + SizedBox(height: 32), + Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0), child: SizedBox( width: double.maxFinite, @@ -73,6 +79,7 @@ class AnimatedURPage extends BasePage { ), ), ), + }, ], ); } @@ -106,6 +113,10 @@ class AnimatedURPage extends BasePage { Navigator.of(context).pop(true); } break; + case "ur:psbt": // psbt + final ur = await presentQRScanner(context); + if (ur == null) return; + await bitcoin!.commitPsbtUR(animatedURmodel.wallet, ur.trim().split("\n")); default: throw UnimplementedError("unable to handle UR: ${urQrType}"); } @@ -168,10 +179,21 @@ class _URQRState extends State { children: [ Center( child: QrImage( - data: widget.frames[frame % widget.frames.length], version: -1, + data: widget.frames[frame % widget.frames.length], + version: -1, size: 400, ), ), + if (FeatureFlag.hasDevOptions) ...{ + TextButton( + onPressed: () { + Clipboard.setData(ClipboardData(text: """Current frame (${frame % widget.frames.length}): ${widget.frames[frame % widget.frames.length]}, +All frames: + - ${widget.frames.join("\n - ")}""")); + }, + child: Text(widget.frames[frame % widget.frames.length]), + ), + } ], ); } diff --git a/tool/configure.dart b/tool/configure.dart index 42f137375..05568e146 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -245,6 +245,7 @@ abstract class Bitcoin { bool getMwebEnabled(Object wallet); String? getUnusedMwebAddress(Object wallet); String? getUnusedSegwitAddress(Object wallet); + Future commitPsbtUR(Object wallet, List urCodes); void updatePayjoinState(Object wallet, bool state); String getPayjoinEndpoint(Object wallet);