mirror of
https://github.com/aljazceru/breez-sdk-liquid.git
synced 2025-12-21 16:04:27 +01:00
feat: add BIP21 support (#414)
Co-authored-by: Erdem Yerebasmaz <erdem@yerebasmaz.com> Co-authored-by: ok300 <106775972+ok300@users.noreply.github.com>
This commit is contained in:
@@ -80,17 +80,21 @@ class _ConnectPageState extends State<ConnectPage> {
|
||||
debugPrint("${mnemonic.isEmpty ? "Creating" : "Restoring"} wallet with $walletMnemonic");
|
||||
return await initializeWallet(mnemonic: walletMnemonic).then(
|
||||
(liquidSDK) async {
|
||||
await widget.credentialsManager.storeMnemonic(mnemonic: walletMnemonic).then((_) {
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) => HomePage(
|
||||
liquidSDK: widget.liquidSDK,
|
||||
credentialsManager: widget.credentialsManager,
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
await widget.credentialsManager.storeMnemonic(mnemonic: walletMnemonic).then(
|
||||
(_) {
|
||||
if (mounted) {
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (BuildContext context) => HomePage(
|
||||
liquidSDK: widget.liquidSDK,
|
||||
credentialsManager: widget.credentialsManager,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,11 @@ class HomePageDrawer extends StatefulWidget {
|
||||
final BindingLiquidSdk liquidSDK;
|
||||
final CredentialsManager credentialsManager;
|
||||
|
||||
const HomePageDrawer({super.key, required this.liquidSDK, required this.credentialsManager});
|
||||
const HomePageDrawer({
|
||||
super.key,
|
||||
required this.liquidSDK,
|
||||
required this.credentialsManager,
|
||||
});
|
||||
|
||||
@override
|
||||
State<HomePageDrawer> createState() => _HomePageDrawerState();
|
||||
@@ -32,8 +36,11 @@ class _HomePageDrawerState extends State<HomePageDrawer> {
|
||||
enabled: false,
|
||||
leading: const Icon(Icons.backup_outlined),
|
||||
title: const Text('Backup'),
|
||||
titleTextStyle:
|
||||
const TextStyle(fontSize: 16.0, color: Colors.white, decoration: TextDecoration.lineThrough),
|
||||
titleTextStyle: const TextStyle(
|
||||
fontSize: 16.0,
|
||||
color: Colors.white,
|
||||
decoration: TextDecoration.lineThrough,
|
||||
),
|
||||
onTap: () async {
|
||||
try {
|
||||
debugPrint("Creating backup.");
|
||||
@@ -44,7 +51,10 @@ class _HomePageDrawerState extends State<HomePageDrawer> {
|
||||
final errMsg = "Failed to create backup. $e";
|
||||
debugPrint(errMsg);
|
||||
if (context.mounted) {
|
||||
final snackBar = SnackBar(behavior: SnackBarBehavior.floating, content: Text(errMsg));
|
||||
final snackBar = SnackBar(
|
||||
behavior: SnackBarBehavior.floating,
|
||||
content: Text(errMsg),
|
||||
);
|
||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||
}
|
||||
}
|
||||
@@ -54,8 +64,11 @@ class _HomePageDrawerState extends State<HomePageDrawer> {
|
||||
enabled: false,
|
||||
leading: const Icon(Icons.restore),
|
||||
title: const Text('Restore'),
|
||||
titleTextStyle:
|
||||
const TextStyle(fontSize: 16.0, color: Colors.white, decoration: TextDecoration.lineThrough),
|
||||
titleTextStyle: const TextStyle(
|
||||
fontSize: 16.0,
|
||||
color: Colors.white,
|
||||
decoration: TextDecoration.lineThrough,
|
||||
),
|
||||
onTap: () async {
|
||||
try {
|
||||
debugPrint("Restoring backup.");
|
||||
@@ -67,7 +80,10 @@ class _HomePageDrawerState extends State<HomePageDrawer> {
|
||||
final errMsg = "Failed to restore backup. $e";
|
||||
debugPrint(errMsg);
|
||||
if (context.mounted) {
|
||||
final snackBar = SnackBar(behavior: SnackBarBehavior.floating, content: Text(errMsg));
|
||||
final snackBar = SnackBar(
|
||||
behavior: SnackBarBehavior.floating,
|
||||
content: Text(errMsg),
|
||||
);
|
||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||
}
|
||||
}
|
||||
@@ -86,7 +102,10 @@ class _HomePageDrawerState extends State<HomePageDrawer> {
|
||||
final errMsg = "Failed to empty wallet cache. $e";
|
||||
debugPrint(errMsg);
|
||||
if (context.mounted) {
|
||||
final snackBar = SnackBar(behavior: SnackBarBehavior.floating, content: Text(errMsg));
|
||||
final snackBar = SnackBar(
|
||||
behavior: SnackBarBehavior.floating,
|
||||
content: Text(errMsg),
|
||||
);
|
||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||
}
|
||||
}
|
||||
@@ -98,19 +117,26 @@ class _HomePageDrawerState extends State<HomePageDrawer> {
|
||||
titleTextStyle: const TextStyle(fontSize: 16.0, color: Colors.white),
|
||||
onTap: () async {
|
||||
try {
|
||||
await widget.credentialsManager.restoreMnemonic().then((mnemonics) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => MnemonicsDialog(
|
||||
mnemonics: mnemonics.split(" "),
|
||||
),
|
||||
);
|
||||
});
|
||||
await widget.credentialsManager.restoreMnemonic().then(
|
||||
(mnemonics) {
|
||||
if (context.mounted) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => MnemonicsDialog(
|
||||
mnemonics: mnemonics.split(" "),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
} on Exception catch (e) {
|
||||
final errMsg = "Failed to display mnemonics. $e";
|
||||
debugPrint(errMsg);
|
||||
if (context.mounted) {
|
||||
final snackBar = SnackBar(behavior: SnackBarBehavior.floating, content: Text(errMsg));
|
||||
final snackBar = SnackBar(
|
||||
behavior: SnackBarBehavior.floating,
|
||||
content: Text(errMsg),
|
||||
);
|
||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,43 +10,7 @@ class PaymentItem extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListTile(
|
||||
onLongPress: item.preimage != null
|
||||
? () {
|
||||
try {
|
||||
debugPrint("Store payment preimage on clipboard. Preimage: ${item.preimage!}");
|
||||
Clipboard.setData(ClipboardData(text: item.preimage!));
|
||||
const snackBar = SnackBar(
|
||||
behavior: SnackBarBehavior.floating,
|
||||
content: Text('Copied payment preimage to clipboard.'),
|
||||
);
|
||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||
} catch (e) {
|
||||
final snackBar = SnackBar(
|
||||
behavior: SnackBarBehavior.floating,
|
||||
content: Text('Failed to copy payment preimage to clipboard. $e'),
|
||||
);
|
||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||
}
|
||||
}
|
||||
: item.swapId != null
|
||||
? () {
|
||||
try {
|
||||
debugPrint("Store swap ID on clipboard. Swap ID: ${item.swapId!}");
|
||||
Clipboard.setData(ClipboardData(text: item.swapId!));
|
||||
const snackBar = SnackBar(
|
||||
behavior: SnackBarBehavior.floating,
|
||||
content: Text('Copied swap ID to clipboard.'),
|
||||
);
|
||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||
} catch (e) {
|
||||
final snackBar = SnackBar(
|
||||
behavior: SnackBarBehavior.floating,
|
||||
content: Text('Failed to copy payment preimage to clipboard. $e'),
|
||||
);
|
||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||
}
|
||||
}
|
||||
: null,
|
||||
onLongPress: () => _onLongPress(context),
|
||||
title: Text(_paymentTitle(item)),
|
||||
subtitle: Text(
|
||||
DateFormat('dd/MM/yyyy, HH:mm').format(
|
||||
@@ -70,6 +34,67 @@ class PaymentItem extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
void _onLongPress(BuildContext context) {
|
||||
final details = item.details;
|
||||
if (details == null) return;
|
||||
|
||||
if (details is PaymentDetails_Lightning && details.preimage != null) {
|
||||
try {
|
||||
debugPrint("Store payment preimage on clipboard. Preimage: ${details.preimage!}");
|
||||
Clipboard.setData(ClipboardData(text: details.preimage!));
|
||||
const snackBar = SnackBar(
|
||||
behavior: SnackBarBehavior.floating,
|
||||
content: Text('Copied payment preimage to clipboard.'),
|
||||
);
|
||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||
} catch (e) {
|
||||
final snackBar = SnackBar(
|
||||
behavior: SnackBarBehavior.floating,
|
||||
content: Text('Failed to copy payment preimage to clipboard. $e'),
|
||||
);
|
||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||
}
|
||||
}
|
||||
|
||||
if (details is PaymentDetails_Bitcoin) {
|
||||
try {
|
||||
debugPrint("Store swap ID on clipboard. Swap ID: ${details.swapId}");
|
||||
Clipboard.setData(ClipboardData(text: details.swapId));
|
||||
const snackBar = SnackBar(
|
||||
behavior: SnackBarBehavior.floating,
|
||||
content: Text('Copied swap ID to clipboard.'),
|
||||
);
|
||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||
} catch (e) {
|
||||
final snackBar = SnackBar(
|
||||
behavior: SnackBarBehavior.floating,
|
||||
content: Text('Failed to copy payment swap ID to clipboard. $e'),
|
||||
);
|
||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||
}
|
||||
}
|
||||
|
||||
if (details is PaymentDetails_Liquid) {
|
||||
try {
|
||||
debugPrint("Store Liquid Address on clipboard. Liquid Address: ${details.destination}");
|
||||
Clipboard.setData(ClipboardData(text: details.destination));
|
||||
const snackBar = SnackBar(
|
||||
behavior: SnackBarBehavior.floating,
|
||||
content: Text('Copied Liquid Address to clipboard.'),
|
||||
);
|
||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||
} catch (e) {
|
||||
final snackBar = SnackBar(
|
||||
behavior: SnackBarBehavior.floating,
|
||||
content: Text('Failed to copy payment Liquid Address to clipboard. $e'),
|
||||
);
|
||||
ScaffoldMessenger.of(context).showSnackBar(snackBar);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
String _paymentTitle(Payment payment) {
|
||||
final paymentType = payment.paymentType;
|
||||
|
||||
|
||||
@@ -153,7 +153,13 @@ class CancelScanButton extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
onPressed: () async {
|
||||
controller.stop().then((_) => Navigator.of(context).pop());
|
||||
controller.stop().then(
|
||||
(_) {
|
||||
if (context.mounted) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
child: const Text(
|
||||
"CANCEL",
|
||||
|
||||
@@ -34,10 +34,12 @@ class QrActionButton extends StatelessWidget {
|
||||
).then((barcode) {
|
||||
if (barcode == null || barcode.isEmpty) return;
|
||||
debugPrint("Scanned string: '$barcode'");
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => SendPaymentDialog(barcodeValue: barcode, liquidSdk: liquidSDK),
|
||||
);
|
||||
if (context.mounted) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => SendPaymentDialog(barcodeValue: barcode, liquidSdk: liquidSDK),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,8 +22,7 @@ class _ReceivePaymentDialogState extends State<ReceivePaymentDialog> {
|
||||
int? feesSat;
|
||||
bool creatingInvoice = false;
|
||||
|
||||
String? invoice;
|
||||
String? invoiceId;
|
||||
String? invoiceDestination;
|
||||
|
||||
StreamSubscription<List<Payment>>? streamSubscription;
|
||||
|
||||
@@ -31,10 +30,19 @@ class _ReceivePaymentDialogState extends State<ReceivePaymentDialog> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
streamSubscription = widget.paymentsStream.listen((paymentList) {
|
||||
if (invoiceId != null && invoiceId!.isNotEmpty) {
|
||||
if (paymentList.any((e) => e.swapId == invoiceId!)) {
|
||||
debugPrint("Payment Received! Id: $invoiceId");
|
||||
if (context.mounted) {
|
||||
if (invoiceDestination != null && invoiceDestination!.isNotEmpty) {
|
||||
if (paymentList.any(
|
||||
(e) {
|
||||
final details = e.details;
|
||||
if (details == null) return false;
|
||||
if (details is PaymentDetails_Lightning && details.preimage != null) {
|
||||
return details.preimage! == invoiceDestination!;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
)) {
|
||||
debugPrint("Payment Received! Id: $invoiceDestination");
|
||||
if (mounted) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
}
|
||||
@@ -51,13 +59,13 @@ class _ReceivePaymentDialogState extends State<ReceivePaymentDialog> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: creatingInvoice ? null : Text(invoice != null ? "Invoice" : "Receive Payment"),
|
||||
content: creatingInvoice || invoice != null
|
||||
title: creatingInvoice ? null : Text(invoiceDestination != null ? "Invoice" : "Receive Payment"),
|
||||
content: creatingInvoice || invoiceDestination != null
|
||||
? Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
if (invoice != null) ...[
|
||||
if (invoiceDestination != null) ...[
|
||||
AspectRatio(
|
||||
aspectRatio: 1,
|
||||
child: SizedBox(
|
||||
@@ -65,7 +73,7 @@ class _ReceivePaymentDialogState extends State<ReceivePaymentDialog> {
|
||||
height: 200.0,
|
||||
child: QrImageView(
|
||||
embeddedImage: const AssetImage("assets/icons/app_icon.png"),
|
||||
data: invoice!.toUpperCase(),
|
||||
data: invoiceDestination!.toUpperCase(),
|
||||
size: 200.0,
|
||||
),
|
||||
),
|
||||
@@ -128,34 +136,36 @@ class _ReceivePaymentDialogState extends State<ReceivePaymentDialog> {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
if (invoice == null) ...[
|
||||
if (invoiceDestination == null) ...[
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
try {
|
||||
setState(() => creatingInvoice = true);
|
||||
int amountSat = int.parse(payerAmountController.text);
|
||||
PrepareReceivePaymentRequest prepareReceiveReq =
|
||||
PrepareReceivePaymentRequest(payerAmountSat: BigInt.from(amountSat));
|
||||
PrepareReceivePaymentResponse prepareRes = await widget.liquidSDK.prepareReceivePayment(
|
||||
PrepareReceiveRequest prepareReceiveReq = PrepareReceiveRequest(
|
||||
paymentMethod: PaymentMethod.lightning,
|
||||
payerAmountSat: BigInt.from(amountSat),
|
||||
);
|
||||
PrepareReceiveResponse prepareResponse = await widget.liquidSDK.prepareReceivePayment(
|
||||
req: prepareReceiveReq,
|
||||
);
|
||||
setState(() {
|
||||
payerAmountSat = prepareRes.payerAmountSat.toInt();
|
||||
feesSat = prepareRes.feesSat.toInt();
|
||||
payerAmountSat = prepareResponse.payerAmountSat?.toInt();
|
||||
feesSat = prepareResponse.feesSat.toInt();
|
||||
});
|
||||
ReceivePaymentRequest receiveReq = ReceivePaymentRequest(prepareRes: prepareRes);
|
||||
ReceivePaymentRequest receiveReq = ReceivePaymentRequest(
|
||||
prepareResponse: prepareResponse,
|
||||
);
|
||||
ReceivePaymentResponse resp = await widget.liquidSDK.receivePayment(req: receiveReq);
|
||||
debugPrint(
|
||||
"Created Invoice for $payerAmountSat sats with $feesSat sats fees.\nInvoice:${resp.invoice}",
|
||||
"Created Invoice for $payerAmountSat sats with $feesSat sats fees.\nDestination:${resp.destination}",
|
||||
);
|
||||
setState(() => invoice = resp.invoice);
|
||||
setState(() => invoiceId = resp.id);
|
||||
setState(() => invoiceDestination = resp.destination);
|
||||
} catch (e) {
|
||||
setState(() {
|
||||
payerAmountSat = null;
|
||||
feesSat = null;
|
||||
invoice = null;
|
||||
invoiceId = null;
|
||||
invoiceDestination = null;
|
||||
});
|
||||
final errMsg = "Error receiving payment: $e";
|
||||
debugPrint(errMsg);
|
||||
|
||||
@@ -17,7 +17,7 @@ class _SendPaymentDialogState extends State<SendPaymentDialog> {
|
||||
|
||||
bool paymentInProgress = false;
|
||||
|
||||
PrepareSendResponse? sendPaymentReq;
|
||||
SendPaymentRequest? sendPaymentReq;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -50,7 +50,7 @@ class _SendPaymentDialogState extends State<SendPaymentDialog> {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
'Please confirm that you agree to the payment fee of ${sendPaymentReq!.feesSat} sats.',
|
||||
'Please confirm that you agree to the payment fee of ${sendPaymentReq!.prepareResponse.feesSat} sats.',
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -84,12 +84,16 @@ class _SendPaymentDialogState extends State<SendPaymentDialog> {
|
||||
onPressed: () async {
|
||||
try {
|
||||
setState(() => paymentInProgress = true);
|
||||
PrepareSendRequest prepareSendReq =
|
||||
PrepareSendRequest(invoice: invoiceController.text);
|
||||
PrepareSendResponse req =
|
||||
await widget.liquidSdk.prepareSendPayment(req: prepareSendReq);
|
||||
debugPrint("PrepareSendResponse for ${req.invoice}, fees: ${req.feesSat}");
|
||||
setState(() => sendPaymentReq = req);
|
||||
PrepareSendRequest prepareSendReq = PrepareSendRequest(
|
||||
destination: invoiceController.text,
|
||||
);
|
||||
PrepareSendResponse req = await widget.liquidSdk.prepareSendPayment(
|
||||
req: prepareSendReq,
|
||||
);
|
||||
debugPrint(
|
||||
"PrepareSendResponse for ${req.destination}, fees: ${req.feesSat}",
|
||||
);
|
||||
setState(() => sendPaymentReq = SendPaymentRequest(prepareResponse: req));
|
||||
} catch (e) {
|
||||
final errMsg = "Error preparing payment: $e";
|
||||
debugPrint(errMsg);
|
||||
|
||||
Reference in New Issue
Block a user