mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2025-06-28 12:29:51 +00:00
feat(ur): support BBQR for coldcard qr
This commit is contained in:
parent
eeaa8b434d
commit
43f4dfa6f0
16 changed files with 110 additions and 35 deletions
|
@ -1,6 +1,7 @@
|
|||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:bbqrdart/bbqrdart.dart';
|
||||
import 'package:cw_bitcoin/electrum_wallet.dart';
|
||||
import 'package:grpc/grpc.dart';
|
||||
import 'package:cw_bitcoin/exceptions.dart';
|
||||
|
@ -183,7 +184,7 @@ class PendingBitcoinTransaction with PendingTransaction {
|
|||
bool shouldCommitUR() => isViewOnly;
|
||||
|
||||
@override
|
||||
Future<String?> commitUR() {
|
||||
Future<Map<String, String>> commitUR() {
|
||||
var sourceBytes = unsignedPsbt!;
|
||||
var cborEncoder = CBOREncoder();
|
||||
cborEncoder.encodeBytes(sourceBytes);
|
||||
|
@ -194,6 +195,19 @@ class PendingBitcoinTransaction with PendingTransaction {
|
|||
while (!encoded.isComplete) {
|
||||
values.add(encoded.nextPart());
|
||||
}
|
||||
return Future.value(values.join("\n"));
|
||||
|
||||
final bbqrObj = BBQRPsbt.fromUint8List(sourceBytes);
|
||||
List<String> bbqr = [
|
||||
bbqrObj.asString(),
|
||||
];
|
||||
while (!bbqrObj.isDone) {
|
||||
bbqrObj.next();
|
||||
bbqr.add(bbqrObj.asString());
|
||||
}
|
||||
|
||||
return Future.value({
|
||||
"PSBT (bcur)": values.join("\n"),
|
||||
"PSBT (bbqr)": bbqr.join("\n"),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,6 +46,15 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.11.0"
|
||||
bbqrdart:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: b0f3f72911221bafed0ab77359fccd64bc79d524
|
||||
resolved-ref: b0f3f72911221bafed0ab77359fccd64bc79d524
|
||||
url: "https://github.com/mrcyjanek/bbqrdart"
|
||||
source: git
|
||||
version: "1.0.0"
|
||||
bech32:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -1191,5 +1200,5 @@ packages:
|
|||
source: hosted
|
||||
version: "2.2.2"
|
||||
sdks:
|
||||
dart: ">=3.6.0 <4.0.0"
|
||||
dart: ">=3.6.2 <4.0.0"
|
||||
flutter: ">=3.27.0"
|
||||
|
|
|
@ -62,6 +62,10 @@ dependencies:
|
|||
git:
|
||||
url: https://github.com/bukata-sa/bc-ur-dart
|
||||
ref: 5738f70d0ec3d50977ac3dd01fed62939600238b
|
||||
bbqrdart:
|
||||
git:
|
||||
url: https://github.com/mrcyjanek/bbqrdart
|
||||
ref: b0f3f72911221bafed0ab77359fccd64bc79d524
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
|
|
@ -86,7 +86,7 @@ class PendingBitcoinCashTransaction with PendingTransaction {
|
|||
isReplaced: false,
|
||||
);
|
||||
@override
|
||||
Future<String?> commitUR() {
|
||||
Future<Map<String, String>> commitUR() {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,5 +17,5 @@ mixin PendingTransaction {
|
|||
bool shouldCommitUR() => false;
|
||||
|
||||
Future<void> commit();
|
||||
Future<String?> commitUR();
|
||||
Future<Map<String, String>> commitUR();
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ class DecredPendingTransaction with PendingTransaction {
|
|||
}
|
||||
|
||||
@override
|
||||
Future<String?> commitUR() {
|
||||
Future<Map<String, String>> commitUR() {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,7 +52,7 @@ class PendingEVMChainTransaction with PendingTransaction {
|
|||
}
|
||||
|
||||
@override
|
||||
Future<String?> commitUR() {
|
||||
Future<Map<String, String>> commitUR() {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,7 +65,7 @@ class PendingMoneroTransaction with PendingTransaction {
|
|||
}
|
||||
|
||||
@override
|
||||
Future<String?> commitUR() async {
|
||||
Future<Map<String, String>> commitUR() async {
|
||||
try {
|
||||
final ret = await monero_transaction_history.commitTransactionFromPointerAddress(
|
||||
address: pendingTransactionDescription.pointerAddress,
|
||||
|
@ -75,7 +75,10 @@ class PendingMoneroTransaction with PendingTransaction {
|
|||
await Future.delayed(const Duration(milliseconds: 250));
|
||||
await wallet.fetchTransactions();
|
||||
}());
|
||||
return ret;
|
||||
if (ret == null) return {};
|
||||
return {
|
||||
"xmr-txsigned": ret,
|
||||
};
|
||||
} catch (e) {
|
||||
final message = e.toString();
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ class PendingNanoTransaction with PendingTransaction {
|
|||
}
|
||||
|
||||
@override
|
||||
Future<String?> commitUR() {
|
||||
Future<Map<String, String>> commitUR() {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ class PendingSolanaTransaction with PendingTransaction {
|
|||
String get id => '';
|
||||
|
||||
@override
|
||||
Future<String?> commitUR() {
|
||||
Future<Map<String, String>> commitUR() {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ class PendingTronTransaction with PendingTransaction {
|
|||
String get id => '';
|
||||
|
||||
@override
|
||||
Future<String?> commitUR() {
|
||||
Future<Map<String, String>> commitUR() {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,7 +52,7 @@ class PendingWowneroTransaction with PendingTransaction {
|
|||
}
|
||||
|
||||
@override
|
||||
Future<String?> commitUR() {
|
||||
Future<Map<String, String>> commitUR() {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,7 +46,7 @@ class PendingZanoTransaction with PendingTransaction {
|
|||
}
|
||||
|
||||
@override
|
||||
Future<String?> commitUR() {
|
||||
Future<Map<String, String>> commitUR() {
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -971,7 +971,7 @@ Future<void> setup({
|
|||
|
||||
getIt.registerFactory(() => AnimatedURModel(getIt.get<AppStore>()));
|
||||
|
||||
getIt.registerFactoryParam<AnimatedURPage, String, void>((String urQr, _) =>
|
||||
getIt.registerFactoryParam<AnimatedURPage, Map<String, String>, void>((Map<String, String> urQr, _) =>
|
||||
AnimatedURPage(getIt.get<AnimatedURModel>(), urQr: urQr));
|
||||
|
||||
getIt.registerFactoryParam<ContactViewModel, ContactRecord?, void>(
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:bbqrdart/bbqrdart.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/entities/qr_scanner.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
|
@ -20,22 +21,21 @@ import 'package:flutter/services.dart';
|
|||
|
||||
class AnimatedURPage extends BasePage {
|
||||
final bool isAll;
|
||||
AnimatedURPage(this.animatedURmodel, {required String urQr, this.isAll = false}) {
|
||||
if (urQr == "export-outputs") {
|
||||
this.urQr = monero!.exportOutputsUR(animatedURmodel.wallet, false);
|
||||
} else if (urQr == "export-outputs-all") {
|
||||
this.urQr = monero!.exportOutputsUR(animatedURmodel.wallet, true);
|
||||
} else {
|
||||
this.urQr = urQr;
|
||||
}
|
||||
}
|
||||
AnimatedURPage(this.animatedURmodel, {
|
||||
required this.urQr,
|
||||
this.isAll = false,
|
||||
});
|
||||
|
||||
late String urQr;
|
||||
late Map<String, String> urQr;
|
||||
|
||||
final AnimatedURModel animatedURmodel;
|
||||
|
||||
String get urQrType {
|
||||
final first = urQr.trim().split("\n")[0];
|
||||
if (urQr.values.first.trim().substring(0, 2) == BBQR.header) {
|
||||
return BBQR.header;
|
||||
}
|
||||
if (urQr.isEmpty) return "unknown";
|
||||
final first = urQr.values.first.trim().split("\n")[0];
|
||||
return first.split('/')[0];
|
||||
}
|
||||
|
||||
|
@ -47,10 +47,10 @@ class AnimatedURPage extends BasePage {
|
|||
Padding(
|
||||
padding: const EdgeInsets.only(top: 64.0),
|
||||
child: URQR(
|
||||
frames: urQr.trim().split("\n"),
|
||||
urqr: urQr,
|
||||
),
|
||||
),
|
||||
if (["ur:xmr-txunsigned", "ur:xmr-output", "ur:psbt"].contains(urQrType)) ...{
|
||||
if (["ur:xmr-txunsigned", "ur:xmr-output", "ur:psbt", BBQR.header].contains(urQrType)) ...{
|
||||
SizedBox(height: 32),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
|
@ -65,6 +65,7 @@ class AnimatedURPage extends BasePage {
|
|||
),
|
||||
),
|
||||
},
|
||||
|
||||
if (urQrType == "ur:xmr-output" && !isAll) ...{
|
||||
SizedBox(height: 32),
|
||||
Padding(
|
||||
|
@ -88,7 +89,13 @@ class AnimatedURPage extends BasePage {
|
|||
Navigator.of(context).pushReplacement(
|
||||
MaterialPageRoute(
|
||||
builder: (context) {
|
||||
return AnimatedURPage(animatedURmodel, urQr: "export-outputs-all", isAll: true);
|
||||
return AnimatedURPage(
|
||||
animatedURmodel,
|
||||
urQr: {
|
||||
"export-outputs-all": "export-outputs-all",
|
||||
},
|
||||
isAll: true,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
@ -136,9 +143,9 @@ class AnimatedURPage extends BasePage {
|
|||
}
|
||||
|
||||
class URQR extends StatefulWidget {
|
||||
URQR({super.key, required this.frames});
|
||||
URQR({super.key, required this.urqr});
|
||||
|
||||
List<String> frames;
|
||||
final Map<String, String> urqr;
|
||||
|
||||
@override
|
||||
// ignore: library_private_types_in_public_api
|
||||
|
@ -172,6 +179,24 @@ class _URQRState extends State<URQR> {
|
|||
super.dispose();
|
||||
}
|
||||
|
||||
late String selected = (widget.urqr.isEmpty) ? "unknown" : widget.urqr.keys.first;
|
||||
int selectedInt = 0;
|
||||
|
||||
List<String> get frames {
|
||||
return widget.urqr[selected]?.split("\n") ?? [];
|
||||
}
|
||||
|
||||
late String nextLabel = widget.urqr.keys.toList()[(selectedInt + 1) % widget.urqr.length] ;
|
||||
|
||||
void next() {
|
||||
final keys = widget.urqr.keys.toList();
|
||||
selectedInt++;
|
||||
setState(() {
|
||||
nextLabel = keys[(selectedInt + 1) % keys.length];
|
||||
selected = keys[(selectedInt + 1) % keys.length];
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
|
@ -180,19 +205,35 @@ class _URQRState extends State<URQR> {
|
|||
children: [
|
||||
Center(
|
||||
child: QrImage(
|
||||
data: widget.frames[frame % widget.frames.length],
|
||||
data: frames[frame % frames.length],
|
||||
version: -1,
|
||||
size: 400,
|
||||
),
|
||||
),
|
||||
if (widget.urqr.values.length > 1)
|
||||
SizedBox(
|
||||
width: double.maxFinite,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
||||
child: SizedBox(
|
||||
width: double.maxFinite,
|
||||
child: PrimaryButton(
|
||||
onPressed: next,
|
||||
text: nextLabel,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
textColor: Theme.of(context).colorScheme.onPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (FeatureFlag.hasDevOptions) ...{
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Clipboard.setData(ClipboardData(text: """Current frame (${frame % widget.frames.length}): ${widget.frames[frame % widget.frames.length]},
|
||||
Clipboard.setData(ClipboardData(text: """Current frame (${frame % frames.length}): ${frames[frame % frames.length]},
|
||||
All frames:
|
||||
- ${widget.frames.join("\n - ")}"""));
|
||||
- ${frames.join("\n - ")}"""));
|
||||
},
|
||||
child: Text(widget.frames[frame % widget.frames.length]),
|
||||
child: Text(frames[frame % frames.length]),
|
||||
),
|
||||
}
|
||||
],
|
||||
|
|
|
@ -134,6 +134,10 @@ dependencies:
|
|||
git:
|
||||
url: https://github.com/bukata-sa/bc-ur-dart
|
||||
ref: 5738f70d0ec3d50977ac3dd01fed62939600238b
|
||||
bbqrdart:
|
||||
git:
|
||||
url: https://github.com/mrcyjanek/bbqrdart
|
||||
ref: b0f3f72911221bafed0ab77359fccd64bc79d524
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue