diff --git a/.github/workflows/pr_test_build.yml b/.github/workflows/pr_test_build.yml index 99a45287f..af6c947f5 100644 --- a/.github/workflows/pr_test_build.yml +++ b/.github/workflows/pr_test_build.yml @@ -158,6 +158,9 @@ jobs: echo "const walletConnectProjectId = '${{ secrets.WALLET_CONNECT_PROJECT_ID }}';" >> lib/.secrets.g.dart echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> lib/.secrets.g.dart echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart + echo "const breezApiKey = '${{ secrets.BREEZ_API_KEY }}';" >> cw_lightning/lib/.secrets.g.dart + echo "const greenlightCert = '${{ secrets.GREENLIGHT_CERTIFICATE }}';" >> cw_lightning/lib/.secrets.g.dart + echo "const greenlightKey = '${{ secrets.GREENLIGHT_KEY }}';" >> cw_lightning/lib/.secrets.g.dart echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart echo "const testCakePayApiKey = '${{ secrets.TEST_CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart echo "const cakePayApiKey = '${{ secrets.CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart diff --git a/.gitignore b/.gitignore index 77441e66f..f4d8b2543 100644 --- a/.gitignore +++ b/.gitignore @@ -128,6 +128,7 @@ cw_haven/android/.externalNativeBuild/ cw_haven/android/.cxx/ lib/bitcoin/bitcoin.dart +lib/lightning/lightning.dart lib/monero/monero.dart lib/haven/haven.dart lib/ethereum/ethereum.dart diff --git a/android/app/build.gradle b/android/app/build.gradle index 5e27aeb9e..263c47105 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -23,6 +23,7 @@ if (flutterVersionName == null) { apply plugin: 'com.android.application' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" +apply plugin: 'kotlinx-serialization' def keystoreProperties = new Properties() def keystorePropertiesFile = rootProject.file('key.properties') diff --git a/android/app/src/main/AndroidManifestBase.xml b/android/app/src/main/AndroidManifestBase.xml index b03c8a925..fa15e7b70 100644 --- a/android/app/src/main/AndroidManifestBase.xml +++ b/android/app/src/main/AndroidManifestBase.xml @@ -9,6 +9,8 @@ + + @@ -69,6 +71,9 @@ + + + @@ -115,6 +120,24 @@ android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/provider_paths" /> + + + + + + + + diff --git a/android/build.gradle b/android/build.gradle index aa9f5005d..6a56a8f70 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -9,6 +9,7 @@ buildscript { classpath 'com.android.tools.build:gradle:7.3.0' classpath 'com.google.gms:google-services:4.3.8' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" } } diff --git a/assets/images/lightning_logo.png b/assets/images/lightning_logo.png new file mode 100644 index 000000000..af38b4ccb Binary files /dev/null and b/assets/images/lightning_logo.png differ diff --git a/assets/images/lightning_logo.svg b/assets/images/lightning_logo.svg new file mode 100644 index 000000000..6fbc51ef4 --- /dev/null +++ b/assets/images/lightning_logo.svg @@ -0,0 +1,10 @@ + + + + + + + \ No newline at end of file diff --git a/assets/images/warning.png b/assets/images/warning.png new file mode 100644 index 000000000..179eb4b5a Binary files /dev/null and b/assets/images/warning.png differ diff --git a/configure_cake_wallet.sh b/configure_cake_wallet.sh index 0539221a3..36f1c2de4 100755 --- a/configure_cake_wallet.sh +++ b/configure_cake_wallet.sh @@ -31,5 +31,4 @@ source ./app_env.sh cakewallet ./app_config.sh cd ../.. && flutter pub get #flutter packages pub run tool/generate_localization.dart -./model_generator.sh -#cd macos && pod install \ No newline at end of file +./model_generator.sh \ No newline at end of file diff --git a/cw_bitcoin/lib/bitcoin_amount_format.dart b/cw_bitcoin/lib/bitcoin_amount_format.dart index d5a42d984..7069c8396 100644 --- a/cw_bitcoin/lib/bitcoin_amount_format.dart +++ b/cw_bitcoin/lib/bitcoin_amount_format.dart @@ -3,12 +3,13 @@ import 'package:cw_core/crypto_amount_format.dart'; const bitcoinAmountLength = 8; const bitcoinAmountDivider = 100000000; +const lightningAmountDivider = 1; final bitcoinAmountFormat = NumberFormat() ..maximumFractionDigits = bitcoinAmountLength ..minimumFractionDigits = 1; -String bitcoinAmountToString({required int amount}) => bitcoinAmountFormat.format( - cryptoAmountToDouble(amount: amount, divider: bitcoinAmountDivider)); +String bitcoinAmountToString({required int amount}) => + bitcoinAmountFormat.format(cryptoAmountToDouble(amount: amount, divider: bitcoinAmountDivider)); double bitcoinAmountToDouble({required int amount}) => cryptoAmountToDouble(amount: amount, divider: bitcoinAmountDivider); @@ -24,3 +25,9 @@ int stringDoubleToBitcoinAmount(String amount) { return result; } + +String bitcoinAmountToLightningString({required int amount}) { + String formattedAmount = bitcoinAmountFormat + .format(cryptoAmountToDouble(amount: amount, divider: lightningAmountDivider)); + return formattedAmount.substring(0, formattedAmount.length - 2); +} diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index db42e2356..e2cc75a85 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -73,6 +73,7 @@ abstract class ElectrumWalletBase getAccountHDWallet(currency, networkType, seedBytes, xpub, walletInfo.derivationInfo), syncStatus = NotConnectedSyncStatus(), _password = password, + _mnemonic = mnemonic, _feeRates = [], _isTransactionUpdating = false, isEnabledAutoGenerateSubaddress = true, @@ -91,7 +92,6 @@ abstract class ElectrumWalletBase this.unspentCoinsInfo = unspentCoinsInfo, this.network = _getNetwork(networkType, currency), this.isTestnet = networkType == bitcoin.testnet, - this._mnemonic = mnemonic, super(walletInfo) { this.electrumClient = electrumClient ?? ElectrumClient(); this.walletInfo = walletInfo; @@ -1138,7 +1138,7 @@ abstract class ElectrumWalletBase } @override - Future close() async { + Future close({bool? switchingToSameWalletType}) async { try { await electrumClient.close(); } catch (_) {} diff --git a/cw_bitcoin/lib/litecoin_wallet.dart b/cw_bitcoin/lib/litecoin_wallet.dart index 209ddc774..f79143b2b 100644 --- a/cw_bitcoin/lib/litecoin_wallet.dart +++ b/cw_bitcoin/lib/litecoin_wallet.dart @@ -33,15 +33,16 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store { Map? initialRegularAddressIndex, Map? initialChangeAddressIndex, }) : super( - mnemonic: mnemonic, - password: password, - walletInfo: walletInfo, - unspentCoinsInfo: unspentCoinsInfo, - networkType: litecoinNetwork, - initialAddresses: initialAddresses, - initialBalance: initialBalance, - seedBytes: seedBytes, - currency: CryptoCurrency.ltc) { + mnemonic: mnemonic, + password: password, + walletInfo: walletInfo, + unspentCoinsInfo: unspentCoinsInfo, + networkType: litecoinNetwork, + initialAddresses: initialAddresses, + initialBalance: initialBalance, + seedBytes: seedBytes, + currency: CryptoCurrency.ltc, + ) { walletAddresses = LitecoinWalletAddresses( walletInfo, initialAddresses: initialAddresses, diff --git a/cw_bitcoin/pubspec.yaml b/cw_bitcoin/pubspec.yaml index 66c5729e8..5159ac991 100644 --- a/cw_bitcoin/pubspec.yaml +++ b/cw_bitcoin/pubspec.yaml @@ -27,7 +27,7 @@ dependencies: git: url: https://github.com/cake-tech/bitbox-flutter.git ref: Add-Support-For-OP-Return-data - rxdart: ^0.27.5 + rxdart: ^0.28.0 cryptography: ^2.0.5 bitcoin_base: git: @@ -53,7 +53,7 @@ dev_dependencies: build_runner: ^2.4.7 build_resolvers: ^2.0.9 mobx_codegen: ^2.0.7 - hive_generator: ^1.1.3 + hive_generator: ^2.0.1 dependency_overrides: watcher: ^1.1.0 diff --git a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart index 51bd3612d..a674b1cc6 100644 --- a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart +++ b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart @@ -35,15 +35,16 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store { Map? initialRegularAddressIndex, Map? initialChangeAddressIndex, }) : super( - mnemonic: mnemonic, - password: password, - walletInfo: walletInfo, - unspentCoinsInfo: unspentCoinsInfo, - networkType: bitcoin.bitcoin, - initialAddresses: initialAddresses, - initialBalance: initialBalance, - seedBytes: seedBytes, - currency: CryptoCurrency.bch) { + mnemonic: mnemonic, + password: password, + walletInfo: walletInfo, + unspentCoinsInfo: unspentCoinsInfo, + networkType: bitcoin.bitcoin, + initialAddresses: initialAddresses, + initialBalance: initialBalance, + seedBytes: seedBytes, + currency: CryptoCurrency.bch, + ) { walletAddresses = BitcoinCashWalletAddresses( walletInfo, initialAddresses: initialAddresses, diff --git a/cw_bitcoin_cash/pubspec.yaml b/cw_bitcoin_cash/pubspec.yaml index a0ce889c1..abb3b5aef 100644 --- a/cw_bitcoin_cash/pubspec.yaml +++ b/cw_bitcoin_cash/pubspec.yaml @@ -43,7 +43,7 @@ dev_dependencies: sdk: flutter build_runner: ^2.4.7 mobx_codegen: ^2.0.7 - hive_generator: ^1.1.3 + hive_generator: ^2.0.1 dependency_overrides: watcher: ^1.1.0 diff --git a/cw_core/lib/amount_converter.dart b/cw_core/lib/amount_converter.dart index 1c5456b07..ca93349ad 100644 --- a/cw_core/lib/amount_converter.dart +++ b/cw_core/lib/amount_converter.dart @@ -6,6 +6,7 @@ class AmountConverter { static const _moneroAmountDivider = 1000000000000; static const _wowneroAmountLength = 11; static const _wowneroAmountDivider = 100000000000; + static const _ethereumAmountDivider = 1000000000000000000; static const _bitcoinAmountDivider = 100000000; static const _bitcoinAmountLength = 8; static final _bitcoinAmountFormat = NumberFormat() @@ -18,6 +19,30 @@ class AmountConverter { ..maximumFractionDigits = _wowneroAmountLength ..minimumFractionDigits = 1; + static int amountStringToInt(CryptoCurrency cryptoCurrency, String amount) { + switch (cryptoCurrency) { + case CryptoCurrency.xmr: + return _moneroParseAmount(amount); + case CryptoCurrency.xhv: + case CryptoCurrency.xag: + case CryptoCurrency.xau: + case CryptoCurrency.xaud: + case CryptoCurrency.xbtc: + case CryptoCurrency.xcad: + case CryptoCurrency.xchf: + case CryptoCurrency.xcny: + case CryptoCurrency.xeur: + case CryptoCurrency.xgbp: + case CryptoCurrency.xjpy: + case CryptoCurrency.xnok: + case CryptoCurrency.xnzd: + case CryptoCurrency.xusd: + return _moneroParseAmount(amount); + default: + return 0; + } + } + static String amountIntToString(CryptoCurrency cryptoCurrency, int amount) { switch (cryptoCurrency) { case CryptoCurrency.xmr: @@ -28,6 +53,8 @@ class AmountConverter { case CryptoCurrency.bch: case CryptoCurrency.ltc: return _bitcoinAmountToString(amount); + case CryptoCurrency.btcln: + return _lightningAmountToString(amount); case CryptoCurrency.xhv: case CryptoCurrency.xag: case CryptoCurrency.xau: @@ -57,6 +84,11 @@ class AmountConverter { static String _bitcoinAmountToString(int amount) => _bitcoinAmountFormat .format(cryptoAmountToDouble(amount: amount, divider: _bitcoinAmountDivider)); + static String _lightningAmountToString(int amount) { + String formattedAmount = _bitcoinAmountFormat.format(amount); + return formattedAmount.substring(0, formattedAmount.length - 2); + } + static String _wowneroAmountToString(int amount) => _wowneroAmountFormat .format(cryptoAmountToDouble(amount: amount, divider: _wowneroAmountDivider)); } diff --git a/cw_core/lib/crypto_currency.dart b/cw_core/lib/crypto_currency.dart index 6881393ed..a738175c2 100644 --- a/cw_core/lib/crypto_currency.dart +++ b/cw_core/lib/crypto_currency.dart @@ -103,6 +103,7 @@ class CryptoCurrency extends EnumerableItem with Serializable implemen CryptoCurrency.kaspa, CryptoCurrency.digibyte, CryptoCurrency.usdtSol, + CryptoCurrency.btcln, CryptoCurrency.usdcTrc20, CryptoCurrency.tbtc, CryptoCurrency.wow, @@ -190,7 +191,7 @@ class CryptoCurrency extends EnumerableItem with Serializable implemen static const scrt = CryptoCurrency(title: 'SCRT', fullName: 'Secret Network', raw: 59, name: 'scrt', iconPath: 'assets/images/scrt_icon.png', decimals: 6); static const uni = CryptoCurrency(title: 'UNI', tag: 'ETH', fullName: 'Uniswap', raw: 60, name: 'uni', iconPath: 'assets/images/uni_icon.png', decimals: 18); static const stx = CryptoCurrency(title: 'STX', fullName: 'Stacks', raw: 61, name: 'stx', iconPath: 'assets/images/stx_icon.png', decimals: 8); - static const btcln = CryptoCurrency(title: 'BTC', tag: 'LN', fullName: 'Bitcoin Lightning Network', raw: 62, name: 'btcln', iconPath: 'assets/images/btc.png', decimals: 8); + static const btcln = CryptoCurrency(title: 'sats', tag: 'LN', fullName: 'Bitcoin Lightning Network', raw: 62, name: 'sats', iconPath: 'assets/images/lightning_logo.png', decimals: 8); static const shib = CryptoCurrency(title: 'SHIB', tag: 'ETH', fullName: 'Shiba Inu', raw: 63, name: 'shib', iconPath: 'assets/images/shib_icon.png', decimals: 18); static const aave = CryptoCurrency(title: 'AAVE', tag: 'ETH', fullName: 'Aave', raw: 64, name: 'aave', iconPath: 'assets/images/aave_icon.png', decimals: 18); static const arb = CryptoCurrency(title: 'ARB', fullName: 'Arbitrum', raw: 65, name: 'arb', iconPath: 'assets/images/arb_icon.png', decimals: 18); @@ -224,7 +225,6 @@ class CryptoCurrency extends EnumerableItem with Serializable implemen static const tbtc = CryptoCurrency(title: 'tBTC', fullName: 'Testnet Bitcoin', raw: 93, name: 'tbtc', iconPath: 'assets/images/tbtc.png', decimals: 8); static const wow = CryptoCurrency(title: 'WOW', fullName: 'Wownero', raw: 94, name: 'wow', iconPath: 'assets/images/wownero_icon.png', decimals: 11); - static final Map _rawCurrencyMap = [...all, ...havenCurrencies].fold>({}, (acc, item) { acc.addAll({item.raw: item}); diff --git a/cw_core/lib/currency_for_wallet_type.dart b/cw_core/lib/currency_for_wallet_type.dart index 630078949..9c147a657 100644 --- a/cw_core/lib/currency_for_wallet_type.dart +++ b/cw_core/lib/currency_for_wallet_type.dart @@ -8,6 +8,8 @@ CryptoCurrency currencyForWalletType(WalletType type, {bool? isTestnet}) { return CryptoCurrency.tbtc; } return CryptoCurrency.btc; + case WalletType.lightning: + return CryptoCurrency.btcln; case WalletType.monero: return CryptoCurrency.xmr; case WalletType.litecoin: diff --git a/cw_core/lib/wallet_base.dart b/cw_core/lib/wallet_base.dart index a616b0bfd..c352d32f9 100644 --- a/cw_core/lib/wallet_base.dart +++ b/cw_core/lib/wallet_base.dart @@ -80,7 +80,7 @@ abstract class WalletBase rescan({required int height}); - void close(); + void close({bool? switchingToSameWalletType}); Future changePassword(String password); diff --git a/cw_core/lib/wallet_type.dart b/cw_core/lib/wallet_type.dart index e3957b4e7..0d9e68b3b 100644 --- a/cw_core/lib/wallet_type.dart +++ b/cw_core/lib/wallet_type.dart @@ -7,6 +7,7 @@ part 'wallet_type.g.dart'; const walletTypes = [ WalletType.monero, WalletType.bitcoin, + WalletType.lightning, WalletType.litecoin, WalletType.haven, WalletType.ethereum, @@ -16,6 +17,7 @@ const walletTypes = [ WalletType.polygon, WalletType.solana, WalletType.tron, + WalletType.wownero, ]; @HiveType(typeId: WALLET_TYPE_TYPE_ID) @@ -58,6 +60,9 @@ enum WalletType { @HiveField(12) wownero, + + @HiveField(13) + lightning, } int serializeToInt(WalletType type) { @@ -86,6 +91,8 @@ int serializeToInt(WalletType type) { return 10; case WalletType.wownero: return 11; + case WalletType.lightning: + return 12; case WalletType.none: return -1; } @@ -117,6 +124,8 @@ WalletType deserializeFromInt(int raw) { return WalletType.tron; case 11: return WalletType.wownero; + case 12: + return WalletType.lightning; default: throw Exception('Unexpected token: $raw for WalletType deserializeFromInt'); } @@ -142,6 +151,8 @@ String walletTypeToString(WalletType type) { return 'Banano'; case WalletType.polygon: return 'Polygon'; + case WalletType.lightning: + return 'Lightning'; case WalletType.solana: return 'Solana'; case WalletType.tron: @@ -173,6 +184,8 @@ String walletTypeToDisplayName(WalletType type) { return 'Banano (BAN)'; case WalletType.polygon: return 'Polygon (MATIC)'; + case WalletType.lightning: + return 'Bitcoin (Lightning)'; case WalletType.solana: return 'Solana (SOL)'; case WalletType.tron: @@ -207,6 +220,8 @@ CryptoCurrency walletTypeToCryptoCurrency(WalletType type, {bool isTestnet = fal return CryptoCurrency.banano; case WalletType.polygon: return CryptoCurrency.maticpoly; + case WalletType.lightning: + return CryptoCurrency.btcln; case WalletType.solana: return CryptoCurrency.sol; case WalletType.tron: diff --git a/cw_evm/lib/evm_chain_wallet.dart b/cw_evm/lib/evm_chain_wallet.dart index 2adb54746..4dfa662cf 100644 --- a/cw_evm/lib/evm_chain_wallet.dart +++ b/cw_evm/lib/evm_chain_wallet.dart @@ -191,7 +191,7 @@ abstract class EVMChainWalletBase } @override - void close() { + void close({bool? switchingToSameWalletType}) { _client.stop(); _transactionsUpdateTimer?.cancel(); } diff --git a/cw_evm/pubspec.yaml b/cw_evm/pubspec.yaml index e4b29b676..4cf43b676 100644 --- a/cw_evm/pubspec.yaml +++ b/cw_evm/pubspec.yaml @@ -43,7 +43,7 @@ dev_dependencies: sdk: flutter build_runner: ^2.4.7 mobx_codegen: ^2.0.7 - hive_generator: ^1.1.3 + hive_generator: ^2.0.1 flutter_lints: ^2.0.0 flutter: diff --git a/cw_haven/lib/haven_wallet.dart b/cw_haven/lib/haven_wallet.dart index e639be4b9..ec96200f5 100644 --- a/cw_haven/lib/haven_wallet.dart +++ b/cw_haven/lib/haven_wallet.dart @@ -106,7 +106,7 @@ abstract class HavenWalletBase Future? updateBalance() => null; @override - void close() { + void close({bool? switchingToSameWalletType}) { _listener?.stop(); _onAccountChangeReaction?.reaction.dispose(); _autoSaveTimer?.cancel(); diff --git a/cw_haven/pubspec.yaml b/cw_haven/pubspec.yaml index d868c986d..290e1f618 100644 --- a/cw_haven/pubspec.yaml +++ b/cw_haven/pubspec.yaml @@ -27,7 +27,7 @@ dev_dependencies: build_runner: ^2.4.7 mobx_codegen: ^2.0.7 build_resolvers: ^2.0.9 - hive_generator: ^1.1.3 + hive_generator: ^2.0.1 dependency_overrides: watcher: ^1.1.0 diff --git a/cw_lightning/.gitignore b/cw_lightning/.gitignore new file mode 100644 index 000000000..1985397a2 --- /dev/null +++ b/cw_lightning/.gitignore @@ -0,0 +1,74 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +build/ + +# Android related +**/android/**/gradle-wrapper.jar +**/android/.gradle +**/android/captures/ +**/android/gradlew +**/android/gradlew.bat +**/android/local.properties +**/android/**/GeneratedPluginRegistrant.java + +# iOS/XCode related +**/ios/**/*.mode1v3 +**/ios/**/*.mode2v3 +**/ios/**/*.moved-aside +**/ios/**/*.pbxuser +**/ios/**/*.perspectivev3 +**/ios/**/*sync/ +**/ios/**/.sconsign.dblite +**/ios/**/.tags* +**/ios/**/.vagrant/ +**/ios/**/DerivedData/ +**/ios/**/Icon? +**/ios/**/Pods/ +**/ios/**/.symlinks/ +**/ios/**/profile +**/ios/**/xcuserdata +**/ios/.generated/ +**/ios/Flutter/App.framework +**/ios/Flutter/Flutter.framework +**/ios/Flutter/Flutter.podspec +**/ios/Flutter/Generated.xcconfig +**/ios/Flutter/app.flx +**/ios/Flutter/app.zip +**/ios/Flutter/flutter_assets/ +**/ios/Flutter/flutter_export_environment.sh +**/ios/ServiceDefinitions.json +**/ios/Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!**/ios/**/default.mode1v3 +!**/ios/**/default.mode2v3 +!**/ios/**/default.pbxuser +!**/ios/**/default.perspectivev3 diff --git a/cw_lightning/.metadata b/cw_lightning/.metadata new file mode 100644 index 000000000..4ce247fd6 --- /dev/null +++ b/cw_lightning/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: b1395592de68cc8ac4522094ae59956dd21a91db + channel: stable + +project_type: package diff --git a/cw_lightning/CHANGELOG.md b/cw_lightning/CHANGELOG.md new file mode 100644 index 000000000..ac071598e --- /dev/null +++ b/cw_lightning/CHANGELOG.md @@ -0,0 +1,3 @@ +## [0.0.1] - TODO: Add release date. + +* TODO: Describe initial release. diff --git a/cw_lightning/LICENSE b/cw_lightning/LICENSE new file mode 100644 index 000000000..ba75c69f7 --- /dev/null +++ b/cw_lightning/LICENSE @@ -0,0 +1 @@ +TODO: Add your license here. diff --git a/cw_lightning/README.md b/cw_lightning/README.md new file mode 100644 index 000000000..af26c1780 --- /dev/null +++ b/cw_lightning/README.md @@ -0,0 +1,14 @@ +# cw_bitcoin + +A new Flutter package project. + +## Getting Started + +This project is a starting point for a Dart +[package](https://flutter.dev/developing-packages/), +a library module containing code that can be shared easily across +multiple Flutter or Dart projects. + +For help getting started with Flutter, view our +[online documentation](https://flutter.dev/docs), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/cw_lightning/lib/cw_lightning.dart b/cw_lightning/lib/cw_lightning.dart new file mode 100644 index 000000000..200190a18 --- /dev/null +++ b/cw_lightning/lib/cw_lightning.dart @@ -0,0 +1,7 @@ +library cw_lightning; + +/// A Calculator. +class Calculator { + /// Returns [value] plus 1. + int addOne(int value) => value + 1; +} diff --git a/cw_lightning/lib/lightning_balance.dart b/cw_lightning/lib/lightning_balance.dart new file mode 100644 index 000000000..3ce0ddc97 --- /dev/null +++ b/cw_lightning/lib/lightning_balance.dart @@ -0,0 +1,45 @@ +import 'dart:convert'; +import 'package:cw_bitcoin/bitcoin_amount_format.dart'; +import 'package:cw_bitcoin/electrum_balance.dart'; +import 'package:cw_core/balance.dart'; + +class LightningBalance extends ElectrumBalance { + LightningBalance({required this.confirmed, required this.unconfirmed, required this.frozen}) + : super( + confirmed: confirmed, + unconfirmed: unconfirmed, + frozen: frozen, + ); + + static LightningBalance? fromJSON(String? jsonSource) { + if (jsonSource == null) { + return null; + } + + final decoded = json.decode(jsonSource) as Map; + + return LightningBalance( + confirmed: decoded['confirmed'] as int? ?? 0, + unconfirmed: decoded['unconfirmed'] as int? ?? 0, + frozen: decoded['frozen'] as int? ?? 0); + } + + final int confirmed; + final int unconfirmed; + final int frozen; + + @override + String get formattedAvailableBalance => bitcoinAmountToLightningString(amount: confirmed); + + @override + String get formattedAdditionalBalance => bitcoinAmountToLightningString(amount: unconfirmed); + + @override + String get formattedUnAvailableBalance { + final frozenFormatted = bitcoinAmountToLightningString(amount: frozen); + return frozenFormatted == '0.0' ? '' : frozenFormatted; + } + + String toJSON() => + json.encode({'confirmed': confirmed, 'unconfirmed': unconfirmed, 'frozen': frozen}); +} diff --git a/cw_lightning/lib/lightning_receive_page_option.dart b/cw_lightning/lib/lightning_receive_page_option.dart new file mode 100644 index 000000000..fd2917a14 --- /dev/null +++ b/cw_lightning/lib/lightning_receive_page_option.dart @@ -0,0 +1,19 @@ +import 'package:cw_core/receive_page_option.dart'; + +class LightningReceivePageOption implements ReceivePageOption { + static const lightningOnchain = LightningReceivePageOption._('lightningOnchain'); + static const lightningInvoice = LightningReceivePageOption._('lightningInvoice'); + + const LightningReceivePageOption._(this.value); + + final String value; + + String toString() { + return value; + } + + static const all = [ + LightningReceivePageOption.lightningInvoice, + LightningReceivePageOption.lightningOnchain + ]; +} diff --git a/cw_lightning/lib/lightning_transaction_info.dart b/cw_lightning/lib/lightning_transaction_info.dart new file mode 100644 index 000000000..20e0f22bc --- /dev/null +++ b/cw_lightning/lib/lightning_transaction_info.dart @@ -0,0 +1,57 @@ +import 'package:cw_bitcoin/bitcoin_amount_format.dart'; +import 'package:cw_bitcoin/electrum_transaction_info.dart'; +import 'package:cw_core/transaction_direction.dart'; +import 'package:cw_core/format_amount.dart'; +import 'package:cw_core/wallet_type.dart'; + +class LightningTransactionInfo extends ElectrumTransactionInfo { + LightningTransactionInfo({ + required String id, + required int amount, + int? fee, + required TransactionDirection direction, + required bool isPending, + required DateTime date, + }) : super( + WalletType.lightning, + amount: amount, + fee: fee, + direction: direction, + date: date, + isPending: isPending, + id: id, + confirmations: 0, + height: 0, + ) {} + + @override + String amountFormatted() => + '${formatAmount(bitcoinAmountToLightningString(amount: amount))} ${walletTypeToCryptoCurrency(type).title}'; + + @override + String? feeFormatted() => fee != null + ? '${formatAmount(bitcoinAmountToLightningString(amount: fee!))} ${walletTypeToCryptoCurrency(type).title}' + : ''; + + factory LightningTransactionInfo.fromJson(Map data, WalletType type) { + return LightningTransactionInfo( + id: data['id'] as String, + amount: data['amount'] as int, + fee: data['fee'] as int, + direction: parseTransactionDirectionFromInt(data['direction'] as int), + date: DateTime.fromMillisecondsSinceEpoch(data['date'] as int), + isPending: data['isPending'] as bool, + ); + } + + Map toJson() { + final m = {}; + m['id'] = id; + m['amount'] = amount; + m['direction'] = direction.index; + m['date'] = date.millisecondsSinceEpoch; + m['isPending'] = isPending; + m['fee'] = fee; + return m; + } +} diff --git a/cw_lightning/lib/lightning_wallet.dart b/cw_lightning/lib/lightning_wallet.dart new file mode 100644 index 000000000..93eb28a9a --- /dev/null +++ b/cw_lightning/lib/lightning_wallet.dart @@ -0,0 +1,422 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:bitbox/bitbox.dart'; +import 'package:bitcoin_base/bitcoin_base.dart'; +import 'package:breez_sdk/breez_sdk.dart'; +import 'package:breez_sdk/bridge_generated.dart'; +import 'package:cw_bitcoin/bitcoin_mnemonic.dart'; +import 'package:cw_bitcoin/electrum_wallet_snapshot.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/node.dart'; +import 'package:cw_core/pathForWallet.dart'; +import 'package:cw_core/pending_transaction.dart'; +import 'package:cw_core/sync_status.dart'; +import 'package:cw_core/transaction_direction.dart'; +import 'package:cw_core/unspent_coins_info.dart'; +import 'package:cw_lightning/lightning_balance.dart'; +import 'package:cw_lightning/lightning_transaction_info.dart'; +import 'package:hive/hive.dart'; +import 'package:mobx/mobx.dart'; +import 'package:flutter/foundation.dart'; +import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_bitcoin/bitcoin_address_record.dart'; +import 'package:cw_bitcoin/bitcoin_wallet_addresses.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:cw_lightning/.secrets.g.dart' as secrets; +import 'package:cw_bitcoin/electrum_wallet.dart'; +import 'package:bip39/bip39.dart' as bip39; + +part 'lightning_wallet.g.dart'; + +class LightningWallet = LightningWalletBase with _$LightningWallet; + +abstract class LightningWalletBase extends ElectrumWallet with Store { + bool _isTransactionUpdating; + + @override + @observable + SyncStatus syncStatus; + + LightningWalletBase({ + required String mnemonic, + required String password, + required WalletInfo walletInfo, + required Box unspentCoinsInfo, + required Uint8List seedBytes, + String? addressPageType, + List? initialAddresses, + LightningBalance? initialBalance, + Map? initialRegularAddressIndex, + Map? initialChangeAddressIndex, + }) : _isTransactionUpdating = false, + syncStatus = NotConnectedSyncStatus(), + _balance = ObservableMap(), + mnemonic = mnemonic, + super( + password: password, + walletInfo: walletInfo, + unspentCoinsInfo: unspentCoinsInfo, + networkType: bitcoin.bitcoin, + initialAddresses: initialAddresses, + initialBalance: initialBalance, + seedBytes: seedBytes, + currency: CryptoCurrency.btcln, + ) { + _balance[CryptoCurrency.btcln] = + initialBalance ?? LightningBalance(confirmed: 0, unconfirmed: 0, frozen: 0); + String derivationPath = walletInfo.derivationInfo!.derivationPath!; + String sideDerivationPath = derivationPath.substring(0, derivationPath.length - 1) + "1"; + final hd = bitcoin.HDWallet.fromSeed(seedBytes, network: networkType); + walletAddresses = BitcoinWalletAddresses( + walletInfo, + initialAddresses: initialAddresses, + initialRegularAddressIndex: initialRegularAddressIndex, + initialChangeAddressIndex: initialChangeAddressIndex, + mainHd: hd.derivePath(derivationPath), + sideHd: hd.derivePath(sideDerivationPath), + network: network, + ); + + // initialize breez: + try { + setupBreez(seedBytes); + } catch (e) { + print("Error initializing Breez: $e"); + } + + autorun((_) { + this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress; + }); + } + + late final ObservableMap _balance; + StreamSubscription>? _paymentsSub; + StreamSubscription? _nodeStateSub; + StreamSubscription? _logStream; + + @override + @computed + ObservableMap get balance => _balance; + + static Future create( + {required String mnemonic, + required String password, + required WalletInfo walletInfo, + required Box unspentCoinsInfo, + String? addressPageType, + List? initialAddresses, + LightningBalance? initialBalance, + Map? initialRegularAddressIndex, + Map? initialChangeAddressIndex}) async { + late final Uint8List seedBytes; + // electrum: + if (validateMnemonic(mnemonic)) { + seedBytes = await mnemonicToSeedBytes(mnemonic); + // bip39: + } else if (bip39.validateMnemonic(mnemonic)) { + seedBytes = await bip39.mnemonicToSeed(mnemonic); + } else { + throw Exception("Invalid mnemonic!"); + } + return LightningWallet( + mnemonic: mnemonic, + password: password, + walletInfo: walletInfo, + unspentCoinsInfo: unspentCoinsInfo, + initialAddresses: initialAddresses, + initialBalance: initialBalance, + seedBytes: seedBytes, + initialRegularAddressIndex: initialRegularAddressIndex, + initialChangeAddressIndex: initialChangeAddressIndex, + addressPageType: addressPageType, + ); + } + + static Future open({ + required String name, + required WalletInfo walletInfo, + required Box unspentCoinsInfo, + required String password, + }) async { + final snp = + await ElectrumWalletSnapshot.load(name, walletInfo.type, password, BitcoinNetwork.mainnet); + + print("OPENING!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + return LightningWallet( + mnemonic: snp.mnemonic!, + password: password, + walletInfo: walletInfo, + unspentCoinsInfo: unspentCoinsInfo, + initialAddresses: snp.addresses, + initialBalance: LightningBalance( + confirmed: snp.balance.confirmed, + unconfirmed: snp.balance.unconfirmed, + frozen: snp.balance.frozen, + ), + seedBytes: await Mnemonic.toSeed(snp.mnemonic!), + initialRegularAddressIndex: snp.regularAddressIndex, + initialChangeAddressIndex: snp.changeAddressIndex, + addressPageType: snp.addressPageType, + ); + } + + Future _handleNodeState(NodeState? nodeState) async { + if (nodeState == null) return; + _balance[CryptoCurrency.btcln] = LightningBalance( + confirmed: nodeState.maxPayableMsat ~/ 1000, + unconfirmed: nodeState.maxReceivableMsat ~/ 1000, + frozen: 0, + ); + } + + Future _handlePayments(List payments) async { + _isTransactionUpdating = true; + final txs = convertToTxInfo(payments); + transactionHistory.addMany(txs); + _isTransactionUpdating = false; + } + + @override + Future renameWalletFiles(String newWalletName) async { + await stopBreez(true); + await super.renameWalletFiles(newWalletName); + await setupBreez(await Mnemonic.toSeed(mnemonic)); + } + + void _logSdkEntries(LogEntry entry) { + switch (entry.level) { + case "ERROR": + case "WARN": + case "INFO": + // case "DEBUG": + // case "TRACE": + print("BREEZ:${entry.level}: ${entry.line}"); + break; + } + } + + Future setupBreez(Uint8List seedBytes) async { + final sdk = await BreezSDK(); + _logStream?.cancel(); + _logStream = sdk.logStream.listen(_logSdkEntries); + + try { + if (!(await sdk.isInitialized())) { + sdk.initialize(); + } + } catch (e) { + print("Error initializing Breez: $e"); + return; + } + + GreenlightCredentials greenlightCredentials = GreenlightCredentials( + developerKey: base64.decode(secrets.greenlightKey), + developerCert: base64.decode(secrets.greenlightCert), + ); + + NodeConfig breezNodeConfig = NodeConfig.greenlight( + config: GreenlightNodeConfig( + partnerCredentials: greenlightCredentials, + inviteCode: null, + ), + ); + Config breezConfig = await sdk.defaultConfig( + envType: EnvironmentType.Production, + apiKey: secrets.breezApiKey, + nodeConfig: breezNodeConfig, + ); + + String workingDir = await pathForWalletDir(name: walletInfo.name, type: type); + workingDir = "$workingDir/breez/"; + + new Directory(workingDir).createSync(recursive: true); + breezConfig = breezConfig.copyWith(workingDir: workingDir); + + // disconnect if already connected + try { + if (await sdk.isInitialized()) { + await sdk.disconnect(); + } + } catch (e, s) { + print("ERROR disconnecting from Breez: $e\n$s"); + } + + try { + await sdk.connect( + req: ConnectRequest( + config: breezConfig, + seed: seedBytes, + ), + ); + } catch (e, s) { + print("Error connecting to Breez: $e\n$s"); + } + + await _nodeStateSub?.cancel(); + _nodeStateSub = sdk.nodeStateStream.listen((event) { + _handleNodeState(event); + }); + await _handleNodeState(await sdk.nodeInfo()); + + await _paymentsSub?.cancel(); + _paymentsSub = sdk.paymentsStream.listen((List payments) { + _handlePayments(payments); + }); + await _handlePayments(await sdk.listPayments(req: ListPaymentsRequest())); + + print("initialized breez: ${(await sdk.isInitialized())}"); + } + + Future stopBreez(bool disconnect) async { + if (disconnect) { + final sdk = await BreezSDK(); + if (await sdk.isInitialized()) { + await sdk.disconnect(); + } + } + await _nodeStateSub?.cancel(); + await _paymentsSub?.cancel(); + } + + @action + @override + Future startSync() async { + try { + syncStatus = AttemptingSyncStatus(); + await updateTransactions(); + syncStatus = SyncedSyncStatus(); + } catch (e) { + print(e); + syncStatus = FailedSyncStatus(); + rethrow; + } + } + + @override + Future changePassword(String password) { + throw UnimplementedError("changePassword"); + } + + @action + @override + Future connectToNode({required Node node}) async { + try { + syncStatus = ConnectingSyncStatus(); + await updateTransactions(); + syncStatus = ConnectedSyncStatus(); + } catch (e) { + print(e); + syncStatus = FailedSyncStatus(); + } + } + + @override + Future createTransaction(Object credentials) async { + throw UnimplementedError("createTransaction"); + } + + Future updateTransactions() async { + try { + if (_isTransactionUpdating) { + return false; + } + + _isTransactionUpdating = true; + final transactions = await fetchTransactions(); + transactionHistory.addMany(transactions); + await transactionHistory.save(); + _isTransactionUpdating = false; + return true; + } catch (_) { + _isTransactionUpdating = false; + return false; + } + } + + Map convertToTxInfo(List payments) { + Map transactions = {}; + + for (Payment tx in payments) { + if (tx.paymentType == PaymentType.ClosedChannel) { + continue; + } + bool isSend = tx.paymentType == PaymentType.Sent; + transactions[tx.id] = LightningTransactionInfo( + isPending: false, + id: tx.id, + amount: tx.amountMsat ~/ 1000, + fee: tx.feeMsat ~/ 1000, + date: DateTime.fromMillisecondsSinceEpoch(tx.paymentTime * 1000), + direction: isSend ? TransactionDirection.outgoing : TransactionDirection.incoming, + ); + } + return transactions; + } + + @override + Future> fetchTransactions() async { + final sdk = await BreezSDK(); + + final payments = await sdk.listPayments(req: ListPaymentsRequest()); + final transactions = convertToTxInfo(payments); + + return transactions; + } + + @override + Future rescan({ + required int height, + int? chainTip, + ScanData? scanData, + bool? doSingleScan, + bool? usingElectrs, + }) async { + updateTransactions(); + } + + Future init() async { + await walletAddresses.init(); + await transactionHistory.init(); + await save(); + } + + String toJSON() => json.encode({ + 'mnemonic': mnemonic, + 'account_index': walletAddresses.currentReceiveAddressIndexByType, + 'change_address_index': walletAddresses.currentChangeAddressIndexByType, + 'addresses': walletAddresses.allAddresses.map((addr) => addr.toJSON()).toList(), + 'address_page_type': walletInfo.addressPageType == null + ? SegwitAddresType.p2wpkh.toString() + : walletInfo.addressPageType.toString(), + 'balance': balance[currency]?.toJSON(), + 'network_type': network == BitcoinNetwork.testnet ? 'testnet' : 'mainnet', + }); + + Future updateBalance() async { + // balance is updated automatically + } + + @override + String mnemonic; + + @override + String get seed => mnemonic; + + Future makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type); + + @override + Future close({bool? switchingToSameWalletType}) async { + try { + await electrumClient.close(); + } catch (_) {} + try { + bool shouldDisconnect = switchingToSameWalletType == null || !switchingToSameWalletType; + await stopBreez(shouldDisconnect); + } catch (e, s) { + print("Error stopping breez: $e\n$s"); + } + } +} diff --git a/cw_lightning/lib/lightning_wallet_service.dart b/cw_lightning/lib/lightning_wallet_service.dart new file mode 100644 index 000000000..54910d283 --- /dev/null +++ b/cw_lightning/lib/lightning_wallet_service.dart @@ -0,0 +1,134 @@ +import 'dart:io'; +import 'package:bitcoin_base/bitcoin_base.dart'; +import 'package:cw_bitcoin/bitcoin_mnemonic.dart'; +import 'package:cw_bitcoin/mnemonic_is_incorrect_exception.dart'; +import 'package:cw_bitcoin/bitcoin_wallet_creation_credentials.dart'; +import 'package:cw_core/unspent_coins_info.dart'; +import 'package:cw_core/wallet_base.dart'; +import 'package:cw_core/wallet_service.dart'; +import 'package:cw_core/pathForWallet.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:cw_lightning/lightning_wallet.dart'; +import 'package:hive/hive.dart'; +import 'package:collection/collection.dart'; +import 'package:bip39/bip39.dart' as bip39; + +class LightningWalletService extends WalletService< + BitcoinNewWalletCredentials, + BitcoinRestoreWalletFromSeedCredentials, + BitcoinRestoreWalletFromWIFCredentials, + BitcoinRestoreWalletFromHardware> { + LightningWalletService(this.walletInfoSource, this.unspentCoinsInfoSource); + + final Box walletInfoSource; + final Box unspentCoinsInfoSource; + + @override + WalletType getType() => WalletType.lightning; + + @override + Future create(BitcoinNewWalletCredentials credentials, {bool? isTestnet}) async { + final wallet = await LightningWalletBase.create( + mnemonic: bip39.generateMnemonic(), + password: credentials.password!, + walletInfo: credentials.walletInfo!, + unspentCoinsInfo: unspentCoinsInfoSource, + ); + await wallet.save(); + await wallet.init(); + return wallet; + } + + @override + Future isWalletExit(String name) async => + File(await pathForWallet(name: name, type: getType())).existsSync(); + + @override + Future openWallet(String name, String password) async { + final walletInfo = walletInfoSource.values + .firstWhereOrNull((info) => info.id == WalletBase.idFor(name, getType()))!; + try { + final wallet = await LightningWalletBase.open( + password: password, + name: name, + walletInfo: walletInfo, + unspentCoinsInfo: unspentCoinsInfoSource, + ); + await wallet.init(); + saveBackup(name); + return wallet; + } catch (_) { + await restoreWalletFilesFromBackup(name); + final wallet = await LightningWalletBase.open( + password: password, + name: name, + walletInfo: walletInfo, + unspentCoinsInfo: unspentCoinsInfoSource, + ); + await wallet.init(); + return wallet; + } + } + + @override + Future restoreFromHardwareWallet(BitcoinRestoreWalletFromHardware credentials, + {bool? isTestnet}) { + throw UnimplementedError( + "Restoring a Lightning wallet from a hardware wallet is not yet supported!"); + } + + @override + Future remove(String wallet) async { + File(await pathForWalletDir(name: wallet, type: getType())).delete(recursive: true); + final walletInfo = walletInfoSource.values + .firstWhereOrNull((info) => info.id == WalletBase.idFor(wallet, getType()))!; + await walletInfoSource.delete(walletInfo.key); + } + + @override + Future rename(String currentName, String password, String newName) async { + final currentWalletInfo = walletInfoSource.values + .firstWhereOrNull((info) => info.id == WalletBase.idFor(currentName, getType()))!; + final currentWallet = await LightningWalletBase.open( + password: password, + name: currentName, + walletInfo: currentWalletInfo, + unspentCoinsInfo: unspentCoinsInfoSource); + + await currentWallet.renameWalletFiles(newName); + await saveBackup(newName); + + final newWalletInfo = currentWalletInfo; + newWalletInfo.id = WalletBase.idFor(newName, getType()); + newWalletInfo.name = newName; + + await walletInfoSource.put(currentWalletInfo.key, newWalletInfo); + } + + @override + Future restoreFromKeys(BitcoinRestoreWalletFromWIFCredentials credentials, + {bool? isTestnet}) async => + throw UnimplementedError(); + + @override + Future restoreFromSeed(BitcoinRestoreWalletFromSeedCredentials credentials, + {bool? isTestnet}) async { + if (!bip39.validateMnemonic(credentials.mnemonic) && !validateMnemonic(credentials.mnemonic)) { + throw BitcoinMnemonicIsIncorrectException(); + } + + final network = isTestnet == true ? BitcoinNetwork.testnet : BitcoinNetwork.mainnet; + credentials.walletInfo?.network = network.value; + + final wallet = await LightningWalletBase.create( + password: credentials.password!, + mnemonic: credentials.mnemonic, + walletInfo: credentials.walletInfo!, + unspentCoinsInfo: unspentCoinsInfoSource, + ); + await wallet.save(); + await wallet.init(); + return wallet; + } +} diff --git a/cw_lightning/pubspec.lock b/cw_lightning/pubspec.lock new file mode 100644 index 000000000..4136218f0 --- /dev/null +++ b/cw_lightning/pubspec.lock @@ -0,0 +1,920 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: ae92f5d747aee634b87f89d9946000c2de774be1d6ac3e58268224348cd0101a + url: "https://pub.dev" + source: hosted + version: "61.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: ea3d8652bda62982addfd92fdc2d0214e5f82e43325104990d4f4c4a2a313562 + url: "https://pub.dev" + source: hosted + version: "5.13.0" + archive: + dependency: transitive + description: + name: archive + sha256: "20071638cbe4e5964a427cfa0e86dce55d060bc7d82d56f3554095d7239a8765" + url: "https://pub.dev" + source: hosted + version: "3.4.2" + args: + dependency: transitive + description: + name: args + sha256: "139d809800a412ebb26a3892da228b2d0ba36f0ef5d9a82166e5e52ec8d61611" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + asn1lib: + dependency: transitive + description: + name: asn1lib + sha256: ab96a1cb3beeccf8145c52e449233fe68364c9641623acd3adad66f8184f1039 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + bech32: + dependency: transitive + description: + path: "." + ref: "cake-0.2.2" + resolved-ref: "05755063b593aa6cca0a4820a318e0ce17de6192" + url: "https://github.com/cake-tech/bech32.git" + source: git + version: "0.2.2" + bip32: + dependency: transitive + description: + name: bip32 + sha256: "54787cd7a111e9d37394aabbf53d1fc5e2e0e0af2cd01c459147a97c0e3f8a97" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + bip39: + dependency: transitive + description: + name: bip39 + sha256: de1ee27ebe7d96b84bb3a04a4132a0a3007dcdd5ad27dd14aa87a29d97c45edc + url: "https://pub.dev" + source: hosted + version: "1.0.6" + bitbox: + dependency: "direct main" + description: + path: "." + ref: Add-Support-For-OP-Return-data + resolved-ref: "57b78afb85bd2c30d3cdb9f7884f3878a62be442" + url: "https://github.com/cake-tech/bitbox-flutter.git" + source: git + version: "1.0.1" + bitcoin_base: + dependency: "direct main" + description: + path: "." + ref: cake-update-v2 + resolved-ref: "3fd81d238b990bb767fc7a4fdd5053a22a142e2e" + url: "https://github.com/cake-tech/bitcoin_base.git" + source: git + version: "4.2.0" + bitcoin_flutter: + dependency: "direct main" + description: + path: "." + ref: cake-update-v4 + resolved-ref: e19ffb7e7977278a75b27e0479b3c6f4034223b3 + url: "https://github.com/cake-tech/bitcoin_flutter.git" + source: git + version: "2.1.0" + blockchain_utils: + dependency: transitive + description: + name: blockchain_utils + sha256: "38ef5f4a22441ac4370aed9071dc71c460acffc37c79b344533f67d15f24c13c" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + breez_sdk: + dependency: "direct main" + description: + path: "." + ref: "v0.4.0-rc2" + resolved-ref: "8762a59b1f823d3c37ee04b95bfe4eb88ea4eb6c" + url: "https://github.com/breez/breez-sdk-flutter.git" + source: git + version: "0.4.0-rc2" + bs58check: + dependency: transitive + description: + name: bs58check + sha256: c4a164d42b25c2f6bc88a8beccb9fc7d01440f3c60ba23663a20a70faf484ea9 + url: "https://pub.dev" + source: hosted + version: "1.0.2" + build: + dependency: transitive + description: + name: build + sha256: "3fbda25365741f8251b39f3917fb3c8e286a96fd068a5a242e11c2012d495777" + url: "https://pub.dev" + source: hosted + version: "2.3.1" + build_cli_annotations: + dependency: transitive + description: + name: build_cli_annotations + sha256: b59d2769769efd6c9ff6d4c4cede0be115a566afc591705c2040b707534b1172 + url: "https://pub.dev" + source: hosted + version: "2.1.0" + build_config: + dependency: transitive + description: + name: build_config + sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1 + url: "https://pub.dev" + source: hosted + version: "1.1.1" + build_daemon: + dependency: transitive + description: + name: build_daemon + sha256: "6bc5544ea6ce4428266e7ea680e945c68806c4aae2da0eb5e9ccf38df8d6acbf" + url: "https://pub.dev" + source: hosted + version: "3.1.0" + build_resolvers: + dependency: "direct dev" + description: + name: build_resolvers + sha256: "687cf90a3951affac1bd5f9ecb5e3e90b60487f3d9cdc359bb310f8876bb02a6" + url: "https://pub.dev" + source: hosted + version: "2.0.10" + build_runner: + dependency: "direct dev" + description: + name: build_runner + sha256: b0a8a7b8a76c493e85f1b84bffa0588859a06197863dba8c9036b15581fd9727 + url: "https://pub.dev" + source: hosted + version: "2.3.3" + build_runner_core: + dependency: transitive + description: + name: build_runner_core + sha256: "14febe0f5bac5ae474117a36099b4de6f1dbc52df6c5e55534b3da9591bf4292" + url: "https://pub.dev" + source: hosted + version: "7.2.7" + built_collection: + dependency: transitive + description: + name: built_collection + sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" + url: "https://pub.dev" + source: hosted + version: "5.1.1" + built_value: + dependency: transitive + description: + name: built_value + sha256: "169565c8ad06adb760c3645bf71f00bff161b00002cace266cad42c5d22a7725" + url: "https://pub.dev" + source: hosted + version: "8.4.3" + characters: + dependency: transitive + description: + name: characters + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + url: "https://pub.dev" + source: hosted + version: "1.3.0" + checked_yaml: + dependency: transitive + description: + name: checked_yaml + sha256: "3d1505d91afa809d177efd4eed5bb0eb65805097a1463abdd2add076effae311" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + clock: + dependency: transitive + description: + name: clock + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + url: "https://pub.dev" + source: hosted + version: "1.1.1" + code_builder: + dependency: transitive + description: + name: code_builder + sha256: "0d43dd1288fd145de1ecc9a3948ad4a6d5a82f0a14c4fdd0892260787d975cbe" + url: "https://pub.dev" + source: hosted + version: "4.4.0" + collection: + dependency: transitive + description: + name: collection + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + url: "https://pub.dev" + source: hosted + version: "1.18.0" + convert: + dependency: transitive + description: + name: convert + sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + url: "https://pub.dev" + source: hosted + version: "3.1.1" + crypto: + dependency: transitive + description: + name: crypto + sha256: aa274aa7774f8964e4f4f38cc994db7b6158dd36e9187aaceaddc994b35c6c67 + url: "https://pub.dev" + source: hosted + version: "3.0.2" + cryptography: + dependency: "direct main" + description: + name: cryptography + sha256: e0e37f79665cd5c86e8897f9abe1accfe813c0cc5299dab22256e22fddc1fef8 + url: "https://pub.dev" + source: hosted + version: "2.0.5" + cw_bitcoin: + dependency: "direct main" + description: + path: "../cw_bitcoin" + relative: true + source: path + version: "0.0.1" + cw_core: + dependency: "direct main" + description: + path: "../cw_core" + relative: true + source: path + version: "0.0.1" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: "7a03456c3490394c8e7665890333e91ae8a49be43542b616e414449ac358acd4" + url: "https://pub.dev" + source: hosted + version: "2.2.4" + encrypt: + dependency: transitive + description: + name: encrypt + sha256: "4fd4e4fdc21b9d7d4141823e1e6515cd94e7b8d84749504c232999fba25d9bbb" + url: "https://pub.dev" + source: hosted + version: "5.0.1" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + ffi: + dependency: transitive + description: + name: ffi + sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + file: + dependency: transitive + description: + name: file + sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" + url: "https://pub.dev" + source: hosted + version: "6.1.4" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_mobx: + dependency: "direct main" + description: + name: flutter_mobx + sha256: "0da4add0016387a7bf309a0d0c41d36c6b3ae25ed7a176409267f166509e723e" + url: "https://pub.dev" + source: hosted + version: "2.0.6+5" + flutter_rust_bridge: + dependency: transitive + description: + name: flutter_rust_bridge + sha256: "02720226035257ad0b571c1256f43df3e1556a499f6bcb004849a0faaa0e87f0" + url: "https://pub.dev" + source: hosted + version: "1.82.6" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + freezed: + dependency: transitive + description: + name: freezed + sha256: "57247f692f35f068cae297549a46a9a097100685c6780fe67177503eea5ed4e5" + url: "https://pub.dev" + source: hosted + version: "2.4.7" + freezed_annotation: + dependency: transitive + description: + name: freezed_annotation + sha256: c3fd9336eb55a38cc1bbd79ab17573113a8deccd0ecbbf926cca3c62803b5c2d + url: "https://pub.dev" + source: hosted + version: "2.4.1" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" + url: "https://pub.dev" + source: hosted + version: "3.2.0" + glob: + dependency: transitive + description: + name: glob + sha256: "4515b5b6ddb505ebdd242a5f2cc5d22d3d6a80013789debfbda7777f47ea308c" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + graphs: + dependency: transitive + description: + name: graphs + sha256: f9e130f3259f52d26f0cfc0e964513796dafed572fa52e45d2f8d6ca14db39b2 + url: "https://pub.dev" + source: hosted + version: "2.2.0" + hex: + dependency: transitive + description: + name: hex + sha256: "4e7cd54e4b59ba026432a6be2dd9d96e4c5205725194997193bf871703b82c4a" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + hive: + dependency: transitive + description: + name: hive + sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941" + url: "https://pub.dev" + source: hosted + version: "2.2.3" + hive_generator: + dependency: "direct dev" + description: + name: hive_generator + sha256: "06cb8f58ace74de61f63500564931f9505368f45f98958bd7a6c35ba24159db4" + url: "https://pub.dev" + source: hosted + version: "2.0.1" + http: + dependency: "direct main" + description: + name: http + sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" + url: "https://pub.dev" + source: hosted + version: "3.2.1" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + intl: + dependency: "direct main" + description: + name: intl + sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" + url: "https://pub.dev" + source: hosted + version: "0.18.1" + io: + dependency: transitive + description: + name: io + sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" + url: "https://pub.dev" + source: hosted + version: "1.0.4" + js: + dependency: transitive + description: + name: js + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.dev" + source: hosted + version: "0.6.7" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: c33da08e136c3df0190bd5bbe51ae1df4a7d96e7954d1d7249fea2968a72d317 + url: "https://pub.dev" + source: hosted + version: "4.8.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" + url: "https://pub.dev" + source: hosted + version: "10.0.0" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 + url: "https://pub.dev" + source: hosted + version: "2.0.1" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 + url: "https://pub.dev" + source: hosted + version: "2.0.1" + logging: + dependency: transitive + description: + name: logging + sha256: "04094f2eb032cbb06c6f6e8d3607edcfcb0455e2bb6cbc010cb01171dcb64e6d" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + matcher: + dependency: transitive + description: + name: matcher + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + url: "https://pub.dev" + source: hosted + version: "0.12.16+1" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + url: "https://pub.dev" + source: hosted + version: "0.8.0" + meta: + dependency: transitive + description: + name: meta + sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 + url: "https://pub.dev" + source: hosted + version: "1.11.0" + mime: + dependency: transitive + description: + name: mime + sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e + url: "https://pub.dev" + source: hosted + version: "1.0.4" + mobx: + dependency: "direct main" + description: + name: mobx + sha256: f1862bd92c6a903fab67338f27e2f731117c3cb9ea37cee1a487f9e4e0de314a + url: "https://pub.dev" + source: hosted + version: "2.1.3+1" + mobx_codegen: + dependency: "direct dev" + description: + name: mobx_codegen + sha256: "86122e410d8ea24dda0c69adb5c2a6ccadd5ce02ad46e144764e0d0184a06181" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + package_config: + dependency: transitive + description: + name: package_config + sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + path: + dependency: transitive + description: + name: path + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + url: "https://pub.dev" + source: hosted + version: "1.9.0" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa + url: "https://pub.dev" + source: hosted + version: "2.1.1" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: e595b98692943b4881b219f0a9e3945118d3c16bd7e2813f98ec6e532d905f72 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d" + url: "https://pub.dev" + source: hosted + version: "2.3.1" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" + url: "https://pub.dev" + source: hosted + version: "2.2.1" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750 + url: "https://pub.dev" + source: hosted + version: "5.4.0" + platform: + dependency: transitive + description: + name: platform + sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76" + url: "https://pub.dev" + source: hosted + version: "3.1.0" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: dbf0f707c78beedc9200146ad3cb0ab4d5da13c246336987be6940f026500d3a + url: "https://pub.dev" + source: hosted + version: "2.1.3" + pointycastle: + dependency: transitive + description: + name: pointycastle + sha256: db7306cf0249f838d1a24af52b5a5887c5bf7f31d8bb4e827d071dc0939ad346 + url: "https://pub.dev" + source: hosted + version: "3.6.2" + pool: + dependency: transitive + description: + name: pool + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + url: "https://pub.dev" + source: hosted + version: "1.5.1" + process: + dependency: transitive + description: + name: process + sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09" + url: "https://pub.dev" + source: hosted + version: "4.2.4" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "307de764d305289ff24ad257ad5c5793ce56d04947599ad68b3baa124105fc17" + url: "https://pub.dev" + source: hosted + version: "2.1.3" + pubspec_parse: + dependency: transitive + description: + name: pubspec_parse + sha256: "75f6614d6dde2dc68948dffbaa4fe5dae32cd700eb9fb763fe11dfb45a3c4d0a" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + puppeteer: + dependency: transitive + description: + name: puppeteer + sha256: "59e723cc5b69537159a7c34efd645dc08a6a1ac4647d7d7823606802c0f93cdb" + url: "https://pub.dev" + source: hosted + version: "3.2.0" + rxdart: + dependency: "direct main" + description: + name: rxdart + sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb" + url: "https://pub.dev" + source: hosted + version: "0.27.7" + shelf: + dependency: transitive + description: + name: shelf + sha256: c24a96135a2ccd62c64b69315a14adc5c3419df63b4d7c05832a346fdb73682c + url: "https://pub.dev" + source: hosted + version: "1.4.0" + shelf_static: + dependency: transitive + description: + name: shelf_static + sha256: a41d3f53c4adf0f57480578c1d61d90342cd617de7fc8077b1304643c2d85c1e + url: "https://pub.dev" + source: hosted + version: "1.1.2" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: a988c0e8d8ffbdb8a28aa7ec8e449c260f3deb808781fe1284d22c5bba7156e8 + url: "https://pub.dev" + source: hosted + version: "1.0.3" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + socks5_proxy: + dependency: transitive + description: + name: socks5_proxy + sha256: "1d21b5606169654bbf4cfb904e8e6ed897e9f763358709f87310c757096d909a" + url: "https://pub.dev" + source: hosted + version: "1.0.4" + source_gen: + dependency: transitive + description: + name: source_gen + sha256: "2d79738b6bbf38a43920e2b8d189e9a3ce6cc201f4b8fc76be5e4fe377b1c38d" + url: "https://pub.dev" + source: hosted + version: "1.2.6" + source_helper: + dependency: transitive + description: + name: source_helper + sha256: "3b67aade1d52416149c633ba1bb36df44d97c6b51830c2198e934e3fca87ca1f" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + source_span: + dependency: transitive + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + url: "https://pub.dev" + source: hosted + version: "1.11.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + url: "https://pub.dev" + source: hosted + version: "2.1.2" + stream_transform: + dependency: transitive + description: + name: stream_transform + sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test_api: + dependency: transitive + description: + name: test_api + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + url: "https://pub.dev" + source: hosted + version: "0.6.1" + timing: + dependency: transitive + description: + name: timing + sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + tuple: + dependency: transitive + description: + name: tuple + sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151 + url: "https://pub.dev" + source: hosted + version: "2.0.2" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: "26f87ade979c47a150c9eaab93ccd2bebe70a27dc0b4b29517f2904f04eb11a5" + url: "https://pub.dev" + source: hosted + version: "1.3.1" + unorm_dart: + dependency: "direct main" + description: + name: unorm_dart + sha256: "5b35bff83fce4d76467641438f9e867dc9bcfdb8c1694854f230579d68cd8f4b" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + uuid: + dependency: transitive + description: + name: uuid + sha256: "22c94e5ad1e75f9934b766b53c742572ee2677c56bc871d850a57dad0f82127f" + url: "https://pub.dev" + source: hosted + version: "4.2.2" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 + url: "https://pub.dev" + source: hosted + version: "13.0.0" + watcher: + dependency: transitive + description: + name: watcher + sha256: "6a7f46926b01ce81bfc339da6a7f20afbe7733eff9846f6d6a5466aa4c6667c0" + url: "https://pub.dev" + source: hosted + version: "1.0.2" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: ca49c0bc209c687b887f30527fb6a9d80040b072cc2990f34b9bec3e7663101b + url: "https://pub.dev" + source: hosted + version: "2.3.0" + win32: + dependency: transitive + description: + name: win32 + sha256: c9ebe7ee4ab0c2194e65d3a07d8c54c5d00bb001b76081c4a04cdb8448b59e46 + url: "https://pub.dev" + source: hosted + version: "3.1.3" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: bd512f03919aac5f1313eb8249f223bacf4927031bf60b02601f81f687689e86 + url: "https://pub.dev" + source: hosted + version: "0.2.0+3" + yaml: + dependency: transitive + description: + name: yaml + sha256: "23812a9b125b48d4007117254bca50abb6c712352927eece9e155207b1db2370" + url: "https://pub.dev" + source: hosted + version: "3.1.1" +sdks: + dart: ">=3.3.0 <4.0.0" + flutter: ">=3.19.0" diff --git a/cw_lightning/pubspec.yaml b/cw_lightning/pubspec.yaml new file mode 100644 index 000000000..39c5e23eb --- /dev/null +++ b/cw_lightning/pubspec.yaml @@ -0,0 +1,88 @@ +name: cw_lightning +description: A new Flutter package project. +version: 0.0.1 +publish_to: none +author: Cake Wallet +homepage: https://cakewallet.com + +environment: + sdk: ">=2.17.5 <3.0.0" + flutter: ">=1.20.0" + +dependencies: + flutter: + sdk: flutter + path_provider: ^2.0.11 + http: ^1.1.0 + mobx: ^2.0.7+4 + flutter_mobx: ^2.0.6+1 + intl: ^0.18.0 + cw_core: + path: ../cw_core + cw_bitcoin: + path: ../cw_bitcoin + bitcoin_flutter: + git: + url: https://github.com/cake-tech/bitcoin_flutter.git + ref: cake-update-v4 + bitbox: + git: + url: https://github.com/cake-tech/bitbox-flutter.git + ref: Add-Support-For-OP-Return-data + breez_sdk: + git: + url: https://github.com/breez/breez-sdk-flutter.git + ref: v0.4.3-rc1 + cryptography: ^2.0.5 + bitcoin_base: + git: + url: https://github.com/cake-tech/bitcoin_base + ref: cake-update-v3 + +dev_dependencies: + flutter_test: + sdk: flutter + build_runner: ^2.1.11 + build_resolvers: ^2.0.9 + mobx_codegen: ^2.0.7 + hive_generator: ^2.0.1 + +dependency_overrides: + watcher: ^1.1.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter. +flutter: + + # To add assets to your package, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # + # For details regarding assets in packages, see + # https://flutter.dev/assets-and-images/#from-packages + # + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware. + + # To add custom fonts to your package, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts in packages, see + # https://flutter.dev/custom-fonts/#from-packages diff --git a/cw_lightning/test/cw_lightning_test.dart b/cw_lightning/test/cw_lightning_test.dart new file mode 100644 index 000000000..58f02e00a --- /dev/null +++ b/cw_lightning/test/cw_lightning_test.dart @@ -0,0 +1,12 @@ +import 'package:flutter_test/flutter_test.dart'; + +import 'package:cw_lightning/cw_lightning.dart'; + +void main() { + test('adds one to input values', () { + final calculator = Calculator(); + expect(calculator.addOne(2), 3); + expect(calculator.addOne(-7), -6); + expect(calculator.addOne(0), 1); + }); +} diff --git a/cw_monero/lib/monero_wallet.dart b/cw_monero/lib/monero_wallet.dart index 244471c13..25cce3a8c 100644 --- a/cw_monero/lib/monero_wallet.dart +++ b/cw_monero/lib/monero_wallet.dart @@ -161,7 +161,7 @@ abstract class MoneroWalletBase extends WalletBase? updateBalance() => null; @override - void close() async { + void close({bool? switchingToSameWalletType}) { _listener?.stop(); _onAccountChangeReaction?.reaction.dispose(); _autoSaveTimer?.cancel(); diff --git a/cw_monero/pubspec.yaml b/cw_monero/pubspec.yaml index 53e50877f..46d6029fc 100644 --- a/cw_monero/pubspec.yaml +++ b/cw_monero/pubspec.yaml @@ -34,7 +34,7 @@ dev_dependencies: build_runner: ^2.4.7 build_resolvers: ^2.0.9 mobx_codegen: ^2.0.7 - hive_generator: ^1.1.3 + hive_generator: ^2.0.1 dependency_overrides: watcher: ^1.1.0 diff --git a/cw_nano/lib/nano_wallet.dart b/cw_nano/lib/nano_wallet.dart index 5efe3006d..50f2d999e 100644 --- a/cw_nano/lib/nano_wallet.dart +++ b/cw_nano/lib/nano_wallet.dart @@ -138,7 +138,7 @@ abstract class NanoWalletBase } @override - void close() { + void close({bool? switchingToSameWalletType}) { _client.stop(); _receiveTimer?.cancel(); } diff --git a/cw_nano/pubspec.yaml b/cw_nano/pubspec.yaml index 768c1bb4e..f65aac23b 100644 --- a/cw_nano/pubspec.yaml +++ b/cw_nano/pubspec.yaml @@ -34,7 +34,7 @@ dev_dependencies: sdk: flutter build_runner: ^2.4.7 mobx_codegen: ^2.0.7 - hive_generator: ^1.1.3 + hive_generator: ^2.0.1 dependency_overrides: watcher: ^1.1.0 diff --git a/cw_solana/lib/solana_wallet.dart b/cw_solana/lib/solana_wallet.dart index 401968698..5aa6032aa 100644 --- a/cw_solana/lib/solana_wallet.dart +++ b/cw_solana/lib/solana_wallet.dart @@ -165,7 +165,7 @@ abstract class SolanaWalletBase Future changePassword(String password) => throw UnimplementedError("changePassword"); @override - void close() { + void close({bool? switchingToSameWalletType}) { _client.stop(); _transactionsUpdateTimer?.cancel(); } diff --git a/cw_tron/lib/tron_wallet.dart b/cw_tron/lib/tron_wallet.dart index 96f92e450..efdf9571a 100644 --- a/cw_tron/lib/tron_wallet.dart +++ b/cw_tron/lib/tron_wallet.dart @@ -186,7 +186,7 @@ abstract class TronWalletBase } @override - void close() { + void close({bool? switchingToSameWalletType}) { _transactionsUpdateTimer?.cancel(); } diff --git a/ios/Runner/InfoBase.plist b/ios/Runner/InfoBase.plist index aec00022b..474b91eb1 100644 --- a/ios/Runner/InfoBase.plist +++ b/ios/Runner/InfoBase.plist @@ -60,6 +60,26 @@ bitcoin-wallet + + CFBundleTypeRole + Editor + CFBundleURLName + lightning + CFBundleURLSchemes + + lightning + + + + CFBundleTypeRole + Editor + CFBundleURLName + lightning-wallet + CFBundleURLSchemes + + lightning-wallet + + CFBundleTypeRole Editor diff --git a/lib/bitcoin_cash/cw_bitcoin_cash.dart b/lib/bitcoin_cash/cw_bitcoin_cash.dart index 6e169209f..ce1fb210d 100644 --- a/lib/bitcoin_cash/cw_bitcoin_cash.dart +++ b/lib/bitcoin_cash/cw_bitcoin_cash.dart @@ -36,4 +36,10 @@ class CWBitcoinCash extends BitcoinCash { @override TransactionPriority getBitcoinCashTransactionPrioritySlow() => BitcoinCashTransactionPriority.slow; + + @override + String getMnemonic(int? strength) => throw UnimplementedError(); + + @override + Uint8List getSeedFromMnemonic(String seed) => throw UnimplementedError(); } diff --git a/lib/core/address_validator.dart b/lib/core/address_validator.dart index 92a735481..483e9acfa 100644 --- a/lib/core/address_validator.dart +++ b/lib/core/address_validator.dart @@ -279,7 +279,8 @@ class AddressValidator extends TextValidator { '|([^0-9a-zA-Z]|^)((bc|tb)1q[ac-hj-np-z02-9]{40,80})([^0-9a-zA-Z]|\$)' //P2wshAddress type '|([^0-9a-zA-Z]|^)((bc|tb)1p([ac-hj-np-z02-9]{39}|[ac-hj-np-z02-9]{59}|[ac-hj-np-z02-9]{8,89}))([^0-9a-zA-Z]|\$)' //P2trAddress type '|${SilentPaymentAddress.regex.pattern}\$'; - + case CryptoCurrency.btcln: + return '(lnbc|LNBC)([0-9]{1,}[a-zA-Z0-9]+)([^0-9a-zA-Z]|\$)'; case CryptoCurrency.ltc: return '([^0-9a-zA-Z]|^)^L[a-zA-Z0-9]{26,33}([^0-9a-zA-Z]|\$)' '|([^0-9a-zA-Z]|^)[LM][a-km-zA-HJ-NP-Z1-9]{26,33}([^0-9a-zA-Z]|\$)' diff --git a/lib/core/seed_validator.dart b/lib/core/seed_validator.dart index 2d2a0c6ee..f4323572d 100644 --- a/lib/core/seed_validator.dart +++ b/lib/core/seed_validator.dart @@ -24,7 +24,7 @@ class SeedValidator extends Validator { static List getWordList({required WalletType type, required String language}) { switch (type) { case WalletType.bitcoin: - return getBitcoinWordList(language); + case WalletType.lightning: case WalletType.litecoin: return getBitcoinWordList(language); case WalletType.monero: diff --git a/lib/core/wallet_creation_service.dart b/lib/core/wallet_creation_service.dart index 2f3acb6c9..8ab817091 100644 --- a/lib/core/wallet_creation_service.dart +++ b/lib/core/wallet_creation_service.dart @@ -79,6 +79,7 @@ class WalletCreationService { case WalletType.polygon: case WalletType.solana: case WalletType.tron: + case WalletType.lightning: return true; case WalletType.monero: case WalletType.wownero: diff --git a/lib/di.dart b/lib/di.dart index 9136260e5..c8eab2c5d 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -1,3 +1,4 @@ +import 'package:breez_sdk/bridge_generated.dart'; import 'package:cake_wallet/.secrets.g.dart' as secrets; import 'package:cake_wallet/anonpay/anonpay_api.dart'; import 'package:cake_wallet/anonpay/anonpay_info_base.dart'; @@ -28,16 +29,24 @@ import 'package:cake_wallet/entities/contact.dart'; import 'package:cake_wallet/entities/contact_record.dart'; import 'package:cake_wallet/entities/exchange_api_mode.dart'; import 'package:cake_wallet/entities/parse_address_from_domain.dart'; +import 'package:cake_wallet/src/screens/cake_pay/cake_pay.dart'; +import 'package:cake_wallet/view_model/lightning_send_view_model.dart'; +import 'package:cake_wallet/view_model/link_view_model.dart'; +import 'package:cake_wallet/tron/tron.dart'; +import 'package:cake_wallet/src/screens/transaction_details/rbf_details_page.dart'; +import 'package:cw_core/nano_account.dart'; +import 'package:cw_core/receive_page_option.dart'; import 'package:cake_wallet/entities/qr_view_data.dart'; import 'package:cake_wallet/entities/template.dart'; import 'package:cake_wallet/entities/transaction_description.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; +import 'package:cake_wallet/lightning/lightning.dart'; +import 'package:cake_wallet/nano/nano.dart'; import 'package:cake_wallet/cake_pay/cake_pay_card.dart'; import 'package:cake_wallet/exchange/exchange_template.dart'; import 'package:cake_wallet/exchange/trade.dart'; import 'package:cake_wallet/haven/haven.dart'; import 'package:cake_wallet/monero/monero.dart'; -import 'package:cake_wallet/nano/nano.dart'; import 'package:cake_wallet/polygon/polygon.dart'; import 'package:cake_wallet/reactions/on_authentication_state_change.dart'; import 'package:cake_wallet/routes.dart'; @@ -77,12 +86,16 @@ import 'package:cake_wallet/src/screens/order_details/order_details_page.dart'; import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart'; import 'package:cake_wallet/src/screens/receive/anonpay_invoice_page.dart'; import 'package:cake_wallet/src/screens/receive/anonpay_receive_page.dart'; +import 'package:cake_wallet/src/screens/receive/lightning_invoice_page.dart'; +import 'package:cake_wallet/src/screens/receive/lightning_receive_page.dart'; +import 'package:cake_wallet/src/screens/restore/wallet_restore_choose_derivation.dart'; +import 'package:cake_wallet/src/screens/send/lightning_send_confirm_page.dart'; +import 'package:cake_wallet/src/screens/send/lightning_send_page.dart'; import 'package:cake_wallet/src/screens/receive/fullscreen_qr_page.dart'; import 'package:cake_wallet/src/screens/receive/receive_page.dart'; import 'package:cake_wallet/src/screens/rescan/rescan_page.dart'; import 'package:cake_wallet/src/screens/restore/restore_from_backup_page.dart'; import 'package:cake_wallet/src/screens/restore/restore_options_page.dart'; -import 'package:cake_wallet/src/screens/restore/wallet_restore_choose_derivation.dart'; import 'package:cake_wallet/src/screens/restore/wallet_restore_page.dart'; import 'package:cake_wallet/src/screens/seed/pre_seed_page.dart'; import 'package:cake_wallet/src/screens/seed/wallet_seed_page.dart'; @@ -122,13 +135,14 @@ import 'package:cake_wallet/view_model/anonpay_details_view_model.dart'; import 'package:cake_wallet/view_model/dashboard/home_settings_view_model.dart'; import 'package:cake_wallet/view_model/dashboard/nft_view_model.dart'; import 'package:cake_wallet/view_model/dashboard/receive_option_view_model.dart'; +import 'package:cake_wallet/view_model/lightning_invoice_page_view_model.dart'; +import 'package:cake_wallet/view_model/lightning_view_model.dart'; import 'package:cake_wallet/view_model/cake_pay/cake_pay_auth_view_model.dart'; import 'package:cake_wallet/view_model/cake_pay/cake_pay_buy_card_view_model.dart'; import 'package:cake_wallet/cake_pay/cake_pay_service.dart'; import 'package:cake_wallet/cake_pay/cake_pay_api.dart'; import 'package:cake_wallet/cake_pay/cake_pay_vendor.dart'; import 'package:cake_wallet/src/screens/cake_pay/auth/cake_pay_account_page.dart'; -import 'package:cake_wallet/src/screens/cake_pay/cake_pay.dart'; import 'package:cake_wallet/view_model/cake_pay/cake_pay_account_view_model.dart'; import 'package:cake_wallet/view_model/cake_pay/cake_pay_cards_list_view_model.dart'; import 'package:cake_wallet/view_model/cake_pay/cake_pay_purchase_view_model.dart'; @@ -136,6 +150,7 @@ import 'package:cake_wallet/view_model/nano_account_list/nano_account_edit_or_cr import 'package:cake_wallet/view_model/nano_account_list/nano_account_list_view_model.dart'; import 'package:cake_wallet/view_model/node_list/pow_node_list_view_model.dart'; import 'package:cake_wallet/view_model/seed_type_view_model.dart'; +import 'package:cake_wallet/view_model/send/output.dart'; import 'package:cake_wallet/view_model/set_up_2fa_viewmodel.dart'; import 'package:cake_wallet/view_model/restore/restore_from_qr_vm.dart'; import 'package:cake_wallet/view_model/settings/display_settings_view_model.dart'; @@ -148,13 +163,12 @@ import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_i import 'package:cake_wallet/view_model/wallet_list/wallet_edit_view_model.dart'; import 'package:cake_wallet/view_model/wallet_list/wallet_list_item.dart'; import 'package:cake_wallet/view_model/wallet_restore_choose_derivation_view_model.dart'; -import 'package:cw_core/nano_account.dart'; import 'package:cw_core/unspent_coins_info.dart'; +import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_service.dart'; import 'package:cw_core/transaction_info.dart'; import 'package:cw_core/node.dart'; import 'package:cake_wallet/src/screens/trade_details/trade_details_page.dart'; -import 'package:cake_wallet/src/screens/transaction_details/rbf_details_page.dart'; import 'package:cake_wallet/src/screens/transaction_details/transaction_details_page.dart'; import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_details_page.dart'; import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_list_page.dart'; @@ -174,7 +188,6 @@ import 'package:cake_wallet/store/templates/exchange_template_store.dart'; import 'package:cake_wallet/store/templates/send_template_store.dart'; import 'package:cake_wallet/store/wallet_list_store.dart'; import 'package:cake_wallet/store/yat/yat_store.dart'; -import 'package:cake_wallet/tron/tron.dart'; import 'package:cake_wallet/view_model/auth_view_model.dart'; import 'package:cake_wallet/view_model/backup_view_model.dart'; import 'package:cake_wallet/view_model/buy/buy_amount_view_model.dart'; @@ -188,7 +201,6 @@ import 'package:cake_wallet/view_model/edit_backup_password_view_model.dart'; import 'package:cake_wallet/view_model/exchange/exchange_trade_view_model.dart'; import 'package:cake_wallet/view_model/exchange/exchange_view_model.dart'; import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart'; -import 'package:cake_wallet/view_model/link_view_model.dart'; import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dart'; import 'package:cake_wallet/view_model/monero_account_list/monero_account_edit_or_create_view_model.dart'; import 'package:cake_wallet/view_model/monero_account_list/monero_account_list_view_model.dart'; @@ -217,8 +229,6 @@ import 'package:cake_wallet/view_model/wallet_restore_view_model.dart'; import 'package:cake_wallet/view_model/wallet_seed_view_model.dart'; import 'package:cake_wallet/wownero/wownero.dart'; import 'package:cw_core/crypto_currency.dart'; -import 'package:cw_core/receive_page_option.dart'; -import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; @@ -376,17 +386,19 @@ Future setup({ fiatConvertationStore: getIt.get())); getIt.registerFactory(() => DashboardViewModel( - balanceViewModel: getIt.get(), - appStore: getIt.get(), - tradesStore: getIt.get(), - tradeFilterStore: getIt.get(), - transactionFilterStore: getIt.get(), - settingsStore: settingsStore, - yatStore: getIt.get(), - ordersStore: getIt.get(), - anonpayTransactionsStore: getIt.get(), - sharedPreferences: getIt.get(), - keyService: getIt.get())); + balanceViewModel: getIt.get(), + appStore: getIt.get(), + tradesStore: getIt.get(), + tradeFilterStore: getIt.get(), + transactionFilterStore: getIt.get(), + settingsStore: settingsStore, + yatStore: getIt.get(), + ordersStore: getIt.get(), + anonpayTransactionsStore: getIt.get(), + sharedPreferences: getIt.get(), + keyService: getIt.get(), + lightningViewModel: getIt.get(), + )); getIt.registerFactory( () => AuthService( @@ -624,7 +636,6 @@ Future setup({ authService: getIt.get(), initialPaymentRequest: initialPaymentRequest, )); - getIt.registerFactory( () => SendTemplatePage(sendTemplateViewModel: getIt.get())); @@ -905,6 +916,8 @@ Future setup({ return nano!.createNanoWalletService(_walletInfoSource); case WalletType.polygon: return polygon!.createPolygonWalletService(_walletInfoSource); + case WalletType.lightning: + return lightning!.createLightningWalletService(_walletInfoSource, _unspentCoinsInfoSource); case WalletType.solana: return solana!.createSolanaWalletService(_walletInfoSource); case WalletType.tron: @@ -1192,5 +1205,62 @@ Future setup({ getIt.registerFactory(() => NFTViewModel(appStore, getIt.get())); getIt.registerFactory(() => TorPage(getIt.get())); + getIt.registerFactory( + () => LightningViewModel(), + ); + + getIt.registerFactory( + () => LightningSendViewModel( + settingsStore: getIt.get(), + fiatConversionStore: getIt.get(), + ), + ); + + getIt.registerFactoryParam((_, __) { + return LightningInvoicePageViewModel( + getIt.get(), + getIt.get().wallet!, + getIt.get(), + getIt.get(), + ); + }); + + getIt.registerFactoryParam((_, __) { + return LightningReceiveOnchainPage( + addressListViewModel: getIt.get(), + lightningViewModel: getIt.get(), + receiveOptionViewModel: + getIt.get(param1: lightning!.getOptionOnchain()), + ); + }); + + getIt.registerFactoryParam((_, __) { + return LightningInvoicePage( + lightningInvoicePageViewModel: getIt.get(), + receiveOptionViewModel: + getIt.get(param1: lightning!.getOptionInvoice()), + ); + }); + + getIt.registerFactory(() { + return LightningSendPage( + output: Output( + getIt.get().wallet!, + getIt.get(), + getIt.get(), + () => CryptoCurrency.btcln, + ), + authService: getIt.get(), + lightningSendViewModel: getIt.get(), + ); + }); + + getIt.registerFactoryParam((LNInvoice invoice, _) { + return LightningSendConfirmPage( + invoice: invoice, + lightningSendViewModel: getIt.get(), + ); + }); + _isSetupFinished = true; } diff --git a/lib/entities/main_actions.dart b/lib/entities/main_actions.dart index c1dd71cc9..21f07e05b 100644 --- a/lib/entities/main_actions.dart +++ b/lib/entities/main_actions.dart @@ -1,8 +1,10 @@ import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/lightning/lightning.dart'; import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; +import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; class MainActions { @@ -11,8 +13,7 @@ class MainActions { final bool Function(DashboardViewModel viewModel)? isEnabled; final bool Function(DashboardViewModel viewModel)? canShow; - final Future Function( - BuildContext context, DashboardViewModel viewModel) onTap; + final Future Function(BuildContext context, DashboardViewModel viewModel) onTap; MainActions._({ required this.name, @@ -55,6 +56,10 @@ class MainActions { name: (context) => S.of(context).receive, image: 'assets/images/received.png', onTap: (BuildContext context, DashboardViewModel viewModel) async { + if (viewModel.wallet.type == WalletType.lightning) { + Navigator.pushNamed(context, Routes.lightningInvoice); + return; + } Navigator.pushNamed(context, Routes.addressPage); }, ); @@ -75,6 +80,10 @@ class MainActions { name: (context) => S.of(context).send, image: 'assets/images/upload.png', onTap: (BuildContext context, DashboardViewModel viewModel) async { + if (viewModel.wallet.type == WalletType.lightning) { + Navigator.pushNamed(context, Routes.lightningSend); + return; + } Navigator.pushNamed(context, Routes.send); }, ); @@ -114,4 +123,4 @@ class MainActions { }, ); } -} \ No newline at end of file +} diff --git a/lib/entities/provider_types.dart b/lib/entities/provider_types.dart index da7bae4c1..fc4d48abe 100644 --- a/lib/entities/provider_types.dart +++ b/lib/entities/provider_types.dart @@ -78,6 +78,7 @@ class ProvidersHelper { ProviderType.moonpay, ]; case WalletType.none: + case WalletType.lightning: case WalletType.haven: return []; } @@ -113,6 +114,7 @@ class ProvidersHelper { case WalletType.monero: case WalletType.nano: case WalletType.banano: + case WalletType.lightning: case WalletType.none: case WalletType.haven: case WalletType.wownero: diff --git a/lib/exchange/provider/trocador_exchange_provider.dart b/lib/exchange/provider/trocador_exchange_provider.dart index 688bf15c9..68ab39afa 100644 --- a/lib/exchange/provider/trocador_exchange_provider.dart +++ b/lib/exchange/provider/trocador_exchange_provider.dart @@ -11,6 +11,14 @@ import 'package:cake_wallet/exchange/utils/currency_pairs_utils.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:http/http.dart'; +double lightningDoubleToBitcoinDouble({required double amount}) { + return amount / 100000000; +} + +double bitcoinDoubleToLightningDouble({required double amount}) { + return amount * 100000000; +} + class TrocadorExchangeProvider extends ExchangeProvider { TrocadorExchangeProvider({this.useTorOnly = false, this.providerStates = const {}}) : _lastUsedRateId = '', @@ -106,6 +114,14 @@ class TrocadorExchangeProvider extends ExchangeProvider { final coinJson = responseJSON.first as Map; + // trocador treats btcln as just bitcoin amounts: + if (from == CryptoCurrency.btcln) { + return Limits( + min: bitcoinDoubleToLightningDouble(amount: (coinJson['minimum'] as double)), + max: bitcoinDoubleToLightningDouble(amount: (coinJson['maximum'] as double)), + ); + } + return Limits( min: coinJson['minimum'] as double, max: coinJson['maximum'] as double, @@ -122,14 +138,20 @@ class TrocadorExchangeProvider extends ExchangeProvider { try { if (amount == 0) return 0.0; + double amt = amount; + + if (from == CryptoCurrency.btcln) { + amt = lightningDoubleToBitcoinDouble(amount: amount); + } + final params = { 'api_key': apiKey, 'ticker_from': _normalizeCurrency(from), 'ticker_to': _normalizeCurrency(to), 'network_from': _networkFor(from), 'network_to': _networkFor(to), - if (!isFixedRateMode) 'amount_from': amount.toString(), - if (isFixedRateMode) 'amount_to': amount.toString(), + if (!isFixedRateMode) 'amount_from': amt.toString(), + if (isFixedRateMode) 'amount_to': amt.toString(), 'payment': isFixedRateMode ? 'True' : 'False', 'min_kycrating': 'C', 'markup': markup, @@ -160,6 +182,14 @@ class TrocadorExchangeProvider extends ExchangeProvider { required bool isFixedRateMode, required bool isSendAll, }) async { + double fromAmt = double.parse(request.fromAmount); + double toAmt = double.parse(request.toAmount); + if (request.fromCurrency == CryptoCurrency.btcln) { + fromAmt = lightningDoubleToBitcoinDouble(amount: fromAmt); + } + if (request.toCurrency == CryptoCurrency.btcln) { + toAmt = lightningDoubleToBitcoinDouble(amount: toAmt); + } final params = { 'api_key': apiKey, 'ticker_from': _normalizeCurrency(request.fromCurrency), @@ -169,17 +199,22 @@ class TrocadorExchangeProvider extends ExchangeProvider { 'payment': isFixedRateMode ? 'True' : 'False', 'min_kycrating': 'C', 'markup': markup, - if (!isFixedRateMode) 'amount_from': request.fromAmount, - if (isFixedRateMode) 'amount_to': request.toAmount, + if (!isFixedRateMode) 'amount_from': fromAmt.toString(), + if (isFixedRateMode) 'amount_to': toAmt.toString(), 'address': request.toAddress, 'refund': request.refundAddress }; + double amt = double.tryParse(request.toAmount) ?? 0; + if (request.fromCurrency == CryptoCurrency.btcln) { + amt = lightningDoubleToBitcoinDouble(amount: amt); + } + if (isFixedRateMode) { await fetchRate( from: request.fromCurrency, to: request.toCurrency, - amount: double.tryParse(request.toAmount) ?? 0, + amount: amt, isFixedRateMode: true, isReceiveAmount: true, ); @@ -225,6 +260,13 @@ class TrocadorExchangeProvider extends ExchangeProvider { final providerId = responseJSON['id_provider'] as String; final providerName = responseJSON['provider'] as String; + String? responseAmount = responseJSON['amount_from']?.toString(); + if (request.fromCurrency == CryptoCurrency.btcln && responseAmount != null) { + responseAmount = + bitcoinDoubleToLightningDouble(amount: double.parse(responseAmount)).toString(); + } + responseAmount ??= fromAmt.toString(); + return Trade( id: id, from: request.fromCurrency, @@ -237,7 +279,7 @@ class TrocadorExchangeProvider extends ExchangeProvider { providerId: providerId, providerName: providerName, createdAt: DateTime.tryParse(date)?.toLocal(), - amount: responseJSON['amount_from']?.toString() ?? request.fromAmount, + amount: responseAmount, payoutAddress: payoutAddress, isSendAll: isSendAll); } @@ -290,6 +332,8 @@ class TrocadorExchangeProvider extends ExchangeProvider { return 'MATIC'; case CryptoCurrency.zec: return 'Mainnet'; + case CryptoCurrency.btcln: + return 'Lightning'; default: return currency.tag != null ? _normalizeTag(currency.tag!) : 'Mainnet'; } @@ -301,6 +345,8 @@ class TrocadorExchangeProvider extends ExchangeProvider { return 'zec'; case CryptoCurrency.usdcEPoly: return 'usdce'; + case CryptoCurrency.btcln: + return 'btc'; default: return currency.title.toLowerCase(); } diff --git a/lib/lightning/cw_lightning.dart b/lib/lightning/cw_lightning.dart new file mode 100644 index 000000000..74a2a0acd --- /dev/null +++ b/lib/lightning/cw_lightning.dart @@ -0,0 +1,69 @@ +part of 'lightning.dart'; + +class CWLightning extends Lightning { + + @override + String formatterLightningAmountToString({required int amount}) => + bitcoinAmountToString(amount: amount * 100000000); + + @override + double formatterLightningAmountToDouble({required int amount}) => + bitcoinAmountToDouble(amount: amount * 100000000); + + @override + int formatterStringDoubleToLightningAmount(String amount) => + stringDoubleToBitcoinAmount(amount * 100000000); + + WalletService createLightningWalletService( + Box walletInfoSource, Box unspentCoinSource) { + return LightningWalletService(walletInfoSource, unspentCoinSource); + } + + @override + List getLightningReceivePageOptions() => + LightningReceivePageOption.all; + + @override + ReceivePageOption getOptionInvoice() => LightningReceivePageOption.lightningInvoice; + + @override + ReceivePageOption getOptionOnchain() => LightningReceivePageOption.lightningOnchain; + + @override + String satsToLightningString(int sats) { + const bitcoinAmountLength = 8; + const bitcoinAmountDivider = 100000000; + const lightningAmountDivider = 1; + final bitcoinAmountFormat = NumberFormat() + ..maximumFractionDigits = bitcoinAmountLength + ..minimumFractionDigits = 1; + + String formattedAmount = bitcoinAmountFormat.format(sats); + return formattedAmount.substring(0, formattedAmount.length - 2); + } + + @override + String bitcoinAmountToLightningString({required int amount}) { + final bitcoinAmountFormat = NumberFormat() + ..maximumFractionDigits = bitcoinAmountLength + ..minimumFractionDigits = 1; + String formattedAmount = + bitcoinAmountFormat.format(cryptoAmountToDouble(amount: amount, divider: 1)); + return formattedAmount.substring(0, formattedAmount.length - 2); + } + + @override + int bitcoinAmountToLightningAmount({required int amount}) { + return amount * 100000000; + } + + @override + double bitcoinDoubleToLightningDouble({required double amount}) { + return amount * 100000000; + } + + @override + double lightningDoubleToBitcoinDouble({required double amount}) { + return amount / 100000000; + } +} diff --git a/lib/reactions/fiat_rate_update.dart b/lib/reactions/fiat_rate_update.dart index e46ef4b64..6a4e6fe01 100644 --- a/lib/reactions/fiat_rate_update.dart +++ b/lib/reactions/fiat_rate_update.dart @@ -30,46 +30,49 @@ Future startFiatRateUpdate( if (appStore.wallet!.type == WalletType.haven) { await updateHavenRate(fiatConversionStore); - } else { - fiatConversionStore.prices[appStore.wallet!.currency] = - await FiatConversionService.fetchPrice( - crypto: appStore.wallet!.currency, - fiat: settingsStore.fiatCurrency, - torOnly: settingsStore.fiatApiMode == FiatApiMode.torOnly); + return; } - Iterable? currencies; - if (appStore.wallet!.type == WalletType.ethereum) { - currencies = - ethereum!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled); + Iterable? currencies = []; + switch (appStore.wallet!.type) { + case WalletType.ethereum: + currencies = + ethereum!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled); + break; + case WalletType.polygon: + currencies = + polygon!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled); + break; + case WalletType.solana: + currencies = + solana!.getSPLTokenCurrencies(appStore.wallet!).where((element) => element.enabled); + break; + case WalletType.tron: + currencies = + tron!.getTronTokenCurrencies(appStore.wallet!).where((element) => element.enabled); + break; + case WalletType.lightning: + currencies = [CryptoCurrency.btc]; + break; + default: + currencies = [appStore.wallet!.currency]; + break; } - if (appStore.wallet!.type == WalletType.polygon) { - currencies = - polygon!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled); + for (final currency in currencies) { + () async { + fiatConversionStore.prices[currency] = await FiatConversionService.fetchPrice( + crypto: currency, + fiat: settingsStore.fiatCurrency, + torOnly: settingsStore.fiatApiMode == FiatApiMode.torOnly, + ); + }.call(); } - if (appStore.wallet!.type == WalletType.solana) { - currencies = - solana!.getSPLTokenCurrencies(appStore.wallet!).where((element) => element.enabled); - } - - if (appStore.wallet!.type == WalletType.tron) { - currencies = - tron!.getTronTokenCurrencies(appStore.wallet!).where((element) => element.enabled); - } - - - if (currencies != null) { - for (final currency in currencies) { - () async { - fiatConversionStore.prices[currency] = await FiatConversionService.fetchPrice( - crypto: currency, - fiat: settingsStore.fiatCurrency, - torOnly: settingsStore.fiatApiMode == FiatApiMode.torOnly); - }.call(); - } - } + // keep btcln price in sync with btc (since the fiat api only returns btc and not btcln) + // (btcln price is just the btc price divided by 100000000) + fiatConversionStore.prices[CryptoCurrency.btcln] = + (fiatConversionStore.prices[CryptoCurrency.btc] ?? 0) / 100000000; } catch (e) { print(e); } diff --git a/lib/reactions/on_current_wallet_change.dart b/lib/reactions/on_current_wallet_change.dart index a6ce2bae9..78ac925f5 100644 --- a/lib/reactions/on_current_wallet_change.dart +++ b/lib/reactions/on_current_wallet_change.dart @@ -73,7 +73,8 @@ void startCurrentWalletChangeReaction( if (wallet.type == WalletType.monero || wallet.type == WalletType.bitcoin || wallet.type == WalletType.litecoin || - wallet.type == WalletType.bitcoinCash) { + wallet.type == WalletType.bitcoinCash || + wallet.type == WalletType.lightning) { _setAutoGenerateSubaddressStatus(wallet, settingsStore); } diff --git a/lib/router.dart b/lib/router.dart index c09664cef..572da43fe 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -42,6 +42,9 @@ import 'package:cake_wallet/src/screens/new_wallet/new_wallet_page.dart'; import 'package:cake_wallet/src/screens/new_wallet/new_wallet_type_page.dart'; import 'package:cake_wallet/src/screens/nodes/node_create_or_edit_page.dart'; import 'package:cake_wallet/src/screens/nodes/pow_node_create_or_edit_page.dart'; +import 'package:cake_wallet/src/screens/receive/lightning_invoice_page.dart'; +import 'package:cake_wallet/src/screens/receive/lightning_receive_page.dart'; +import 'package:cake_wallet/src/screens/restore/sweeping_wallet_page.dart'; import 'package:cake_wallet/src/screens/order_details/order_details_page.dart'; import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart'; import 'package:cake_wallet/src/screens/receive/anonpay_invoice_page.dart'; @@ -53,6 +56,8 @@ import 'package:cake_wallet/src/screens/restore/restore_from_backup_page.dart'; import 'package:cake_wallet/src/screens/restore/restore_options_page.dart'; import 'package:cake_wallet/src/screens/restore/sweeping_wallet_page.dart'; import 'package:cake_wallet/src/screens/restore/wallet_restore_choose_derivation.dart'; +import 'package:cake_wallet/src/screens/send/lightning_send_confirm_page.dart'; +import 'package:cake_wallet/src/screens/send/lightning_send_page.dart'; import 'package:cake_wallet/src/screens/restore/wallet_restore_page.dart'; import 'package:cake_wallet/src/screens/seed/pre_seed_page.dart'; import 'package:cake_wallet/src/screens/seed/wallet_seed_page.dart'; @@ -651,6 +656,25 @@ Route createRoute(RouteSettings settings) { case Routes.torPage: return MaterialPageRoute(builder: (_) => getIt.get()); + case Routes.lightningSend: + return CupertinoPageRoute( + fullscreenDialog: true, builder: (_) => getIt.get()); + + case Routes.lightningSendConfirm: + return CupertinoPageRoute( + fullscreenDialog: true, + builder: (_) => getIt.get(param1: settings.arguments)); + + case Routes.lightningReceiveOnchain: + final args = settings.arguments as List; + return CupertinoPageRoute( + fullscreenDialog: true, + builder: (_) => getIt.get(param1: args)); + + case Routes.lightningInvoice: + return CupertinoPageRoute( + fullscreenDialog: true, builder: (_) => getIt.get()); + case Routes.connectDevices: final params = settings.arguments as ConnectDevicePageParams; return MaterialPageRoute( diff --git a/lib/routes.dart b/lib/routes.dart index 78a93bee7..6d5293397 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -101,5 +101,10 @@ class Routes { static const nftDetailsPage = '/nft_details_page'; static const importNFTPage = '/import_nft_page'; static const torPage = '/tor_page'; + static const lightningSend = '/lightning_send'; + static const lightningSendConfirm = '/lightning_send_confirm'; + static const lightningInvoice = '/lightning_invoice'; + static const lightningReceiveOnchain = '/lightning_receive_onchain'; + static const lightningSettings = '/lightning_settings'; static const connectDevices = '/device/connect'; } diff --git a/lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart b/lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart index 46e63af01..73406b006 100644 --- a/lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart +++ b/lib/src/screens/dashboard/desktop_widgets/desktop_wallet_selection_dropdown.dart @@ -30,6 +30,7 @@ class DesktopWalletSelectionDropDown extends StatefulWidget { class _DesktopWalletSelectionDropDownState extends State { final moneroIcon = Image.asset('assets/images/monero_logo.png', height: 24, width: 24); final bitcoinIcon = Image.asset('assets/images/bitcoin.png', height: 24, width: 24); + final lightningIcon = Image.asset('assets/images/lightning_logo.png', height: 24, width: 24); final tBitcoinIcon = Image.asset('assets/images/tbtc.png', height: 24, width: 24); final litecoinIcon = Image.asset('assets/images/litecoin_icon.png', height: 24, width: 24); final havenIcon = Image.asset('assets/images/haven_logo.png', height: 24, width: 24); @@ -150,6 +151,8 @@ class _DesktopWalletSelectionDropDownState extends State null, + title: S.of(context).warning, + subTitle: serviceMessage, + ), + ); + }), Observer( builder: (_) { if (dashboardViewModel.balanceViewModel.isShowCard && diff --git a/lib/src/screens/dashboard/widgets/menu_widget.dart b/lib/src/screens/dashboard/widgets/menu_widget.dart index 78d8abc95..489b8cbb1 100644 --- a/lib/src/screens/dashboard/widgets/menu_widget.dart +++ b/lib/src/screens/dashboard/widgets/menu_widget.dart @@ -28,6 +28,7 @@ class MenuWidgetState extends State { this.fromBottomEdge = 25, this.moneroIcon = Image.asset('assets/images/monero_menu.png'), this.bitcoinIcon = Image.asset('assets/images/bitcoin_menu.png'), + this.lightningIcon = Image.asset('assets/images/lightning_logo.png'), this.litecoinIcon = Image.asset('assets/images/litecoin_menu.png'), this.havenIcon = Image.asset('assets/images/haven_menu.png'), this.ethereumIcon = Image.asset('assets/images/eth_icon.png'), @@ -52,6 +53,7 @@ class MenuWidgetState extends State { Image moneroIcon; Image bitcoinIcon; + Image lightningIcon; Image litecoinIcon; Image havenIcon; Image ethereumIcon; @@ -103,6 +105,7 @@ class MenuWidgetState extends State { color: Theme.of(context).extension()!.iconColor); bitcoinIcon = Image.asset('assets/images/bitcoin_menu.png', color: Theme.of(context).extension()!.iconColor); + lightningIcon = Image.asset('assets/images/lightning_logo.png'); return Row( mainAxisSize: MainAxisSize.max, @@ -220,6 +223,8 @@ class MenuWidgetState extends State { return moneroIcon; case WalletType.bitcoin: return bitcoinIcon; + case WalletType.lightning: + return lightningIcon; case WalletType.litecoin: return litecoinIcon; case WalletType.haven: diff --git a/lib/src/screens/exchange/exchange_page.dart b/lib/src/screens/exchange/exchange_page.dart index 5c064df27..c99287b6f 100644 --- a/lib/src/screens/exchange/exchange_page.dart +++ b/lib/src/screens/exchange/exchange_page.dart @@ -638,6 +638,7 @@ class ExchangePage extends BasePage { initialIsAddressEditable: exchangeViewModel.isDepositAddressEnabled, isAmountEstimated: false, hasRefundAddress: true, + hasAddress: exchangeViewModel.hasAddress, isMoneroWallet: exchangeViewModel.isMoneroWallet, currencies: exchangeViewModel.depositCurrencies, onCurrencySelected: (currency) { diff --git a/lib/src/screens/exchange/widgets/exchange_card.dart b/lib/src/screens/exchange/widgets/exchange_card.dart index 760b0c137..094fd9aab 100644 --- a/lib/src/screens/exchange/widgets/exchange_card.dart +++ b/lib/src/screens/exchange/widgets/exchange_card.dart @@ -33,6 +33,7 @@ class ExchangeCard extends StatefulWidget { this.title = '', this.initialIsAddressEditable = true, this.hasRefundAddress = false, + this.hasAddress = true, this.isMoneroWallet = false, this.currencyButtonColor = Colors.transparent, this.addressButtonsColor = Colors.transparent, @@ -57,6 +58,7 @@ class ExchangeCard extends StatefulWidget { final bool initialIsAddressEditable; final bool isAmountEstimated; final bool hasRefundAddress; + final bool hasAddress; final bool isMoneroWallet; final Image imageArrow; final Color currencyButtonColor; @@ -272,9 +274,7 @@ class ExchangeCardState extends State { color: Theme.of(context) .extension()! .hintTextColor), - validator: _isAmountEditable - ? widget.currencyValueValidator - : null), + validator: _isAmountEditable ? widget.currencyValueValidator : null), ), ), if (widget.hasAllAmount) @@ -330,138 +330,142 @@ class ExchangeCardState extends State { : Offstage(), ])), ), - !_isAddressEditable && widget.hasRefundAddress - ? Padding( - padding: EdgeInsets.only(top: 20), - child: Text( - S.of(context).refund_address, - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - color: Theme.of(context).extension()!.hintTextColor), - )) - : Offstage(), - _isAddressEditable - ? FocusTraversalOrder( - order: NumericFocusOrder(2), - child: Padding( + if (widget.hasAddress) ...[ + !_isAddressEditable && widget.hasRefundAddress + ? Padding( padding: EdgeInsets.only(top: 20), - child: AddressTextField( - focusNode: widget.addressFocusNode, - controller: addressController, - onURIScanned: (uri) { - final paymentRequest = PaymentRequest.fromUri(uri); - addressController.text = paymentRequest.address; + child: Text( + S.of(context).refund_address, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + color: Theme.of(context).extension()!.hintTextColor), + )) + : Offstage(), + _isAddressEditable + ? FocusTraversalOrder( + order: NumericFocusOrder(2), + child: Padding( + padding: EdgeInsets.only(top: 20), + child: AddressTextField( + focusNode: widget.addressFocusNode, + controller: addressController, + onURIScanned: (uri) { + final paymentRequest = PaymentRequest.fromUri(uri); + addressController.text = paymentRequest.address; - if (amountController.text.isNotEmpty) { - _showAmountPopup(context, paymentRequest); - return; - } - widget.amountFocusNode?.requestFocus(); - amountController.text = paymentRequest.amount; - }, - placeholder: widget.hasRefundAddress ? S.of(context).refund_address : null, - options: [ - AddressTextFieldOption.paste, - AddressTextFieldOption.qrCode, - AddressTextFieldOption.addressBook, - ], - isBorderExist: false, - textStyle: - TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Colors.white), - hintStyle: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w600, - color: Theme.of(context).extension()!.hintTextColor), - buttonColor: widget.addressButtonsColor, - validator: widget.addressTextFieldValidator, - onPushPasteButton: widget.onPushPasteButton, - onPushAddressBookButton: widget.onPushAddressBookButton, - selectedCurrency: _selectedCurrency), - ), - ) - : Padding( - padding: EdgeInsets.only(top: 10), - child: Builder( - builder: (context) => Stack(children: [ - FocusTraversalOrder( - order: NumericFocusOrder(3), - child: BaseTextFormField( - controller: addressController, - borderColor: Colors.transparent, - suffixIcon: SizedBox(width: _isMoneroWallet ? 80 : 36), - textStyle: TextStyle( - fontSize: 16, fontWeight: FontWeight.w600, color: Colors.white), - validator: widget.addressTextFieldValidator), - ), - Positioned( - top: 2, - right: 0, - child: SizedBox( - width: _isMoneroWallet ? 80 : 36, - child: Row(children: [ - if (_isMoneroWallet) + if (amountController.text.isNotEmpty) { + _showAmountPopup(context, paymentRequest); + return; + } + widget.amountFocusNode?.requestFocus(); + amountController.text = paymentRequest.amount; + }, + placeholder: widget.hasRefundAddress ? S.of(context).refund_address : null, + options: [ + AddressTextFieldOption.paste, + AddressTextFieldOption.qrCode, + AddressTextFieldOption.addressBook, + ], + isBorderExist: false, + textStyle: TextStyle( + fontSize: 16, fontWeight: FontWeight.w600, color: Colors.white), + hintStyle: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Theme.of(context).extension()!.hintTextColor), + buttonColor: widget.addressButtonsColor, + validator: widget.addressTextFieldValidator, + onPushPasteButton: widget.onPushPasteButton, + onPushAddressBookButton: widget.onPushAddressBookButton, + selectedCurrency: _selectedCurrency), + ), + ) + : Padding( + padding: EdgeInsets.only(top: 10), + child: Builder( + builder: (context) => Stack(children: [ + FocusTraversalOrder( + order: NumericFocusOrder(3), + child: BaseTextFormField( + controller: addressController, + borderColor: Colors.transparent, + suffixIcon: SizedBox(width: _isMoneroWallet ? 80 : 36), + textStyle: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Colors.white), + validator: widget.addressTextFieldValidator), + ), + Positioned( + top: 2, + right: 0, + child: SizedBox( + width: _isMoneroWallet ? 80 : 36, + child: Row(children: [ + if (_isMoneroWallet) + Padding( + padding: EdgeInsets.only(left: 10), + child: Container( + width: 34, + height: 34, + padding: EdgeInsets.only(top: 0), + child: Semantics( + label: S.of(context).address_book, + child: InkWell( + onTap: () async { + final contact = + await Navigator.of(context).pushNamed( + Routes.pickerAddressBook, + arguments: widget.initialCurrency, + ); + + if (contact is ContactBase) { + setState(() => + addressController.text = contact.address); + widget.onPushAddressBookButton?.call(context); + } + }, + child: Container( + padding: EdgeInsets.all(8), + decoration: BoxDecoration( + color: widget.addressButtonsColor, + borderRadius: + BorderRadius.all(Radius.circular(6))), + child: Image.asset( + 'assets/images/open_book.png', + color: Theme.of(context) + .extension()! + .textFieldButtonIconColor, + )), + ), + )), + ), Padding( - padding: EdgeInsets.only(left: 10), - child: Container( - width: 34, - height: 34, - padding: EdgeInsets.only(top: 0), - child: Semantics( - label: S.of(context).address_book, - child: InkWell( - onTap: () async { - final contact = - await Navigator.of(context).pushNamed( - Routes.pickerAddressBook, - arguments: widget.initialCurrency, - ); - - if (contact is ContactBase) { - setState(() => - addressController.text = contact.address); - widget.onPushAddressBookButton?.call(context); - } - }, - child: Container( - padding: EdgeInsets.all(8), - decoration: BoxDecoration( - color: widget.addressButtonsColor, - borderRadius: - BorderRadius.all(Radius.circular(6))), - child: Image.asset( - 'assets/images/open_book.png', - color: Theme.of(context) - .extension()! - .textFieldButtonIconColor, - )), - ), - )), - ), - Padding( - padding: EdgeInsets.only(left: 2), - child: Container( - width: 34, - height: 34, - padding: EdgeInsets.only(top: 0), - child: Semantics( - label: S.of(context).copy_address, - child: InkWell( - onTap: () { - Clipboard.setData( - ClipboardData(text: addressController.text)); - showBar( - context, S.of(context).copied_to_clipboard); - }, - child: Container( - padding: EdgeInsets.fromLTRB(8, 8, 0, 8), - color: Colors.transparent, - child: copyImage), - ), - ))) - ]))) - ])), - ), + padding: EdgeInsets.only(left: 2), + child: Container( + width: 34, + height: 34, + padding: EdgeInsets.only(top: 0), + child: Semantics( + label: S.of(context).copy_address, + child: InkWell( + onTap: () { + Clipboard.setData(ClipboardData( + text: addressController.text)); + showBar( + context, S.of(context).copied_to_clipboard); + }, + child: Container( + padding: EdgeInsets.fromLTRB(8, 8, 0, 8), + color: Colors.transparent, + child: copyImage), + ), + ))) + ]))) + ])), + ), + ], ]), ); } diff --git a/lib/src/screens/exchange_trade/exchange_trade_page.dart b/lib/src/screens/exchange_trade/exchange_trade_page.dart index 4d3334f9f..a0d0d6591 100644 --- a/lib/src/screens/exchange_trade/exchange_trade_page.dart +++ b/lib/src/screens/exchange_trade/exchange_trade_page.dart @@ -24,22 +24,17 @@ import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart'; import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart'; -void showInformation( - ExchangeTradeViewModel exchangeTradeViewModel, BuildContext context) { +void showInformation(ExchangeTradeViewModel exchangeTradeViewModel, BuildContext context) { final trade = exchangeTradeViewModel.trade; final walletName = exchangeTradeViewModel.wallet.name; final information = exchangeTradeViewModel.isSendable - ? S.current.exchange_result_confirm( - trade.amount, trade.from.toString(), walletName) + - exchangeTradeViewModel.extraInfo - : S.current.exchange_result_description( - trade.amount, trade.from.toString()) + - exchangeTradeViewModel.extraInfo; + ? S.current.exchange_result_confirm(trade.amount, trade.from.toString(), walletName) + + exchangeTradeViewModel.extraInfo + : S.current.exchange_result_description(trade.amount, trade.from.toString()) + + exchangeTradeViewModel.extraInfo; - showPopUp( - context: context, - builder: (_) => InformationPage(information: information)); + showPopUp(context: context, builder: (_) => InformationPage(information: information)); } class ExchangeTradePage extends BasePage { @@ -72,8 +67,7 @@ class ExchangeTradePage extends BasePage { } @override - Widget body(BuildContext context) => - ExchangeTradeForm(exchangeTradeViewModel); + Widget body(BuildContext context) => ExchangeTradeForm(exchangeTradeViewModel); } class ExchangeTradeForm extends StatefulWidget { @@ -138,7 +132,9 @@ class ExchangeTradeState extends State { style: TextStyle( fontSize: 14.0, fontWeight: FontWeight.w500, - color: Theme.of(context).extension()!.detailsTitlesColor), + color: Theme.of(context) + .extension()! + .detailsTitlesColor), ), if (trade.expiredAt != null) TimerWidget(trade.expiredAt!, @@ -159,9 +155,9 @@ class ExchangeTradeState extends State { decoration: BoxDecoration( border: Border.all( width: 3, - color: Theme.of(context).extension()!.qrCodeColor - ) - ), + color: Theme.of(context) + .extension()! + .qrCodeColor)), child: QrImage(data: trade.inputAddress ?? fetchingLabel), )))), Spacer(flex: 3) @@ -192,10 +188,8 @@ class ExchangeTradeState extends State { ? Builder( builder: (context) => GestureDetector( onTap: () { - Clipboard.setData( - ClipboardData(text: value)); - showBar(context, - S.of(context).copied_to_clipboard); + Clipboard.setData(ClipboardData(text: value)); + showBar(context, S.of(context).copied_to_clipboard); }, child: content, )) @@ -209,17 +203,14 @@ class ExchangeTradeState extends State { bottomSectionPadding: EdgeInsets.fromLTRB(24, 0, 24, 24), bottomSection: Observer(builder: (_) { final trade = widget.exchangeTradeViewModel.trade; - final sendingState = - widget.exchangeTradeViewModel.sendViewModel.state; + final sendingState = widget.exchangeTradeViewModel.sendViewModel.state; return widget.exchangeTradeViewModel.isSendable && !(sendingState is TransactionCommitted) ? LoadingPrimaryButton( - isDisabled: trade.inputAddress == null || - trade.inputAddress!.isEmpty, + isDisabled: trade.inputAddress == null || trade.inputAddress!.isEmpty, isLoading: sendingState is IsExecutingState, - onPressed: () => - widget.exchangeTradeViewModel.confirmSending(), + onPressed: () => widget.exchangeTradeViewModel.confirmSending(), text: S.of(context).confirm, color: Theme.of(context).primaryColor, textColor: Colors.white) @@ -257,27 +248,26 @@ class ExchangeTradeState extends State { return ConfirmSendingAlert( alertTitle: S.of(popupContext).confirm_sending, amount: S.of(popupContext).send_amount, - amountValue: widget.exchangeTradeViewModel.sendViewModel - .pendingTransaction!.amountFormatted, + amountValue: widget + .exchangeTradeViewModel.sendViewModel.pendingTransaction!.amountFormatted, fee: S.of(popupContext).send_fee, - feeValue: widget.exchangeTradeViewModel.sendViewModel - .pendingTransaction!.feeFormatted, - feeRate: widget.exchangeTradeViewModel.sendViewModel.pendingTransaction!.feeRate, + feeValue: widget + .exchangeTradeViewModel.sendViewModel.pendingTransaction!.feeFormatted, + feeRate: + widget.exchangeTradeViewModel.sendViewModel.pendingTransaction!.feeRate, rightButtonText: S.of(popupContext).send, leftButtonText: S.of(popupContext).cancel, actionRightButton: () async { Navigator.of(popupContext).pop(); - await widget.exchangeTradeViewModel.sendViewModel - .commitTransaction(); + await widget.exchangeTradeViewModel.sendViewModel.commitTransaction(); transactionStatePopup(); }, actionLeftButton: () => Navigator.of(popupContext).pop(), - feeFiatAmount: widget.exchangeTradeViewModel - .pendingTransactionFeeFiatAmountFormatted, - fiatAmountValue: widget.exchangeTradeViewModel - .pendingTransactionFiatAmountValueFormatted, - outputs: widget.exchangeTradeViewModel.sendViewModel - .outputs); + feeFiatAmount: + widget.exchangeTradeViewModel.pendingTransactionFeeFiatAmountFormatted, + fiatAmountValue: + widget.exchangeTradeViewModel.pendingTransactionFiatAmountValueFormatted, + outputs: widget.exchangeTradeViewModel.sendViewModel.outputs); }); }); } @@ -305,85 +295,26 @@ class ExchangeTradeState extends State { void transactionStatePopup() { if (this.mounted) { showPopUp( - context: context, - builder: (BuildContext popupContext) { - return Observer(builder: (_) { - final state = widget - .exchangeTradeViewModel.sendViewModel.state; + context: context, + builder: (BuildContext popupContext) { + return Observer(builder: (_) { + final state = widget.exchangeTradeViewModel.sendViewModel.state; - if (state is TransactionCommitted) { - return Stack( - children: [ - Container( - color: Theme.of(popupContext).colorScheme.background, - child: Center( - child: Image.asset( - 'assets/images/birthday_cake.png'), - ), - ), - Center( - child: Padding( - padding: EdgeInsets.only( - top: 220, left: 24, right: 24), - child: Text( - S.of(popupContext).send_success(widget - .exchangeTradeViewModel - .wallet - .currency - .toString()), - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 22, - fontWeight: FontWeight.bold, - color: Theme.of(popupContext).extension()!.titleColor, - decoration: TextDecoration.none, - ), + if (state is TransactionCommitted) { + return Stack( + children: [ + Container( + color: Theme.of(popupContext).colorScheme.background, + child: Center( + child: Image.asset('assets/images/birthday_cake.png'), ), ), - ), - Positioned( - left: 24, - right: 24, - bottom: 24, - child: PrimaryButton( - onPressed: () { - Navigator.pushNamedAndRemoveUntil( - popupContext, - Routes.dashboard, - (route) => false, - ); - RequestReviewHandler.requestReview(); - }, - text: S.of(popupContext).got_it, - color: Theme.of(popupContext).primaryColor, - textColor: Colors.white)) - ], - ); - } - - return Stack( - children: [ - Container( - color: Theme.of(popupContext).colorScheme.background, - child: Center( - child: Image.asset( - 'assets/images/birthday_cake.png'), - ), - ), - BackdropFilter( - filter: ImageFilter.blur( - sigmaX: 3.0, sigmaY: 3.0), - child: Container( - decoration: BoxDecoration( - color: Theme.of(popupContext) - .colorScheme - .background - .withOpacity(0.25)), - child: Center( + Center( child: Padding( - padding: EdgeInsets.only(top: 220), + padding: EdgeInsets.only(top: 220, left: 24, right: 24), child: Text( - S.of(popupContext).send_sending, + S.of(popupContext).send_success( + widget.exchangeTradeViewModel.wallet.currency.toString()), textAlign: TextAlign.center, style: TextStyle( fontSize: 22, @@ -394,12 +325,60 @@ class ExchangeTradeState extends State { ), ), ), + Positioned( + left: 24, + right: 24, + bottom: 24, + child: PrimaryButton( + onPressed: () { + Navigator.pushNamedAndRemoveUntil( + popupContext, + Routes.dashboard, + (route) => false, + ); + RequestReviewHandler.requestReview(); + }, + text: S.of(popupContext).got_it, + color: Theme.of(popupContext).primaryColor, + textColor: Colors.white)) + ], + ); + } + + return Stack( + children: [ + Container( + color: Theme.of(popupContext).colorScheme.background, + child: Center( + child: Image.asset('assets/images/birthday_cake.png'), + ), ), - ) - ], - ); + BackdropFilter( + filter: ImageFilter.blur(sigmaX: 3.0, sigmaY: 3.0), + child: Container( + decoration: BoxDecoration( + color: Theme.of(popupContext).colorScheme.background.withOpacity(0.25)), + child: Center( + child: Padding( + padding: EdgeInsets.only(top: 220), + child: Text( + S.of(popupContext).send_sending, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 22, + fontWeight: FontWeight.bold, + color: Theme.of(popupContext).extension()!.titleColor, + decoration: TextDecoration.none, + ), + ), + ), + ), + ), + ) + ], + ); + }); }); - }); } } } diff --git a/lib/src/screens/receive/lightning_invoice_page.dart b/lib/src/screens/receive/lightning_invoice_page.dart new file mode 100644 index 000000000..9cab90462 --- /dev/null +++ b/lib/src/screens/receive/lightning_invoice_page.dart @@ -0,0 +1,340 @@ +import 'package:cake_wallet/lightning/lightning.dart'; +import 'package:cake_wallet/src/screens/receive/widgets/lightning_input_form.dart'; +import 'package:cake_wallet/src/screens/receive/widgets/qr_image.dart'; +import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart'; +import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart'; +import 'package:cake_wallet/themes/extensions/exchange_page_theme.dart'; +import 'package:cake_wallet/themes/extensions/keyboard_theme.dart'; +import 'package:cake_wallet/core/execution_state.dart'; +import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/src/screens/dashboard/widgets/present_receive_option_picker.dart'; +import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; +import 'package:cake_wallet/src/widgets/keyboard_done_button.dart'; +import 'package:cake_wallet/utils/responsive_layout_util.dart'; +import 'package:cake_wallet/utils/show_bar.dart'; +import 'package:cake_wallet/view_model/dashboard/receive_option_view_model.dart'; +import 'package:cake_wallet/view_model/lightning_invoice_page_view_model.dart'; +import 'package:cake_wallet/view_model/lightning_view_model.dart'; +import 'package:cw_core/receive_page_option.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:keyboard_actions/keyboard_actions.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/src/widgets/trail_button.dart'; +import 'package:cake_wallet/utils/show_pop_up.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/widgets/primary_button.dart'; +import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart'; +import 'package:mobx/mobx.dart'; +import 'package:qr_flutter/qr_flutter.dart' as qr; + +class LightningInvoicePage extends BasePage { + LightningInvoicePage({ + required this.lightningInvoicePageViewModel, + required this.receiveOptionViewModel, + }) : _amountFocusNode = FocusNode() {} + + final _descriptionController = TextEditingController(); + final _amountController = TextEditingController(); + final FocusNode _amountFocusNode; + + final LightningInvoicePageViewModel lightningInvoicePageViewModel; + final ReceiveOptionViewModel receiveOptionViewModel; + final _formKey = GlobalKey(); + + bool effectsInstalled = false; + + @override + bool get gradientAll => true; + + @override + bool get resizeToAvoidBottomInset => false; + + @override + bool get extendBodyBehindAppBar => true; + + @override + AppBarStyle get appBarStyle => AppBarStyle.transparent; + + @override + void onClose(BuildContext context) => Navigator.popUntil(context, (route) => route.isFirst); + + @override + Widget middle(BuildContext context) => PresentReceiveOptionPicker( + receiveOptionViewModel: receiveOptionViewModel, color: titleColor(context)); + + @override + Widget trailing(BuildContext context) => TrailButton( + caption: S.of(context).clear, + onPressed: () { + _formKey.currentState?.reset(); + }); + + Future _onNavigateBack(BuildContext context) async { + onClose(context); + return false; + } + + @override + Widget body(BuildContext context) { + WidgetsBinding.instance.addPostFrameCallback((_) => _setReactions(context)); + + return WillPopScope( + onWillPop: () => _onNavigateBack(context), + child: KeyboardActions( + disableScroll: true, + config: KeyboardActionsConfig( + keyboardActionsPlatform: KeyboardActionsPlatform.IOS, + keyboardBarColor: Theme.of(context).extension()!.keyboardBarColor, + nextFocus: false, + actions: [ + KeyboardActionsItem( + focusNode: _amountFocusNode, + toolbarButtons: [(_) => KeyboardDoneButton()], + ), + ]), + child: Container( + color: Theme.of(context).colorScheme.background, + child: ScrollableWithBottomSection( + contentPadding: EdgeInsets.only(bottom: 24), + content: Container( + decoration: responsiveLayoutUtil.shouldRenderMobileUI + ? BoxDecoration( + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(24), bottomRight: Radius.circular(24)), + gradient: LinearGradient( + colors: [ + Theme.of(context) + .extension()! + .firstGradientTopPanelColor, + Theme.of(context) + .extension()! + .secondGradientTopPanelColor, + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + ) + : null, + child: Padding( + padding: EdgeInsets.fromLTRB(24, 120, 24, 0), + child: LightningInvoiceForm( + descriptionController: _descriptionController, + amountController: _amountController, + depositAmountFocus: _amountFocusNode, + formKey: _formKey, + lightningInvoicePageViewModel: lightningInvoicePageViewModel, + ), + ), + ), + bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24), + bottomSection: Column( + children: [ + Container( + padding: const EdgeInsets.only(top: 12, bottom: 12, right: 6), + margin: const EdgeInsets.only(left: 24, right: 24, bottom: 48), + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(15)), + color: Color.fromARGB(255, 170, 147, 30), + border: Border.all( + color: Color.fromARGB(178, 223, 214, 0), + width: 2, + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + width: 48, + height: 48, + margin: EdgeInsets.only(left: 12, bottom: 48, right: 20), + child: Image.asset( + "assets/images/warning.png", + color: Color.fromARGB(128, 255, 255, 255), + ), + ), + FutureBuilder( + future: lightningInvoicePageViewModel.lightningViewModel + .invoiceSoftLimitsSats(), + builder: (context, snapshot) { + if (snapshot.data == null) { + return Expanded( + child: + Container(child: Center(child: CircularProgressIndicator()))); + } + late String finalText; + InvoiceSoftLimitsResult limits = snapshot.data as InvoiceSoftLimitsResult; + if (limits.inboundLiquidity == 0) { + finalText = S.of(context).lightning_invoice_min( + limits.feePercent.toString(), + lightning!.satsToLightningString(limits.minFee)); + } else { + finalText = S.of(context).lightning_invoice_min_max( + limits.feePercent.toString(), + lightning!.satsToLightningString(limits.minFee), + lightning!.satsToLightningString(limits.inboundLiquidity), + ); + } + + return Expanded( + child: Text( + finalText, + maxLines: 5, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + color: Theme.of(context).extension()!.textColor, + ), + ), + ); + }, + ), + ], + ), + ), + Observer(builder: (_) { + return LoadingPrimaryButton( + text: S.of(context).create_invoice, + onPressed: () { + FocusScope.of(context).unfocus(); + lightningInvoicePageViewModel.setRequestParams( + inputAmount: _amountController.text, + inputDescription: _descriptionController.text, + ); + lightningInvoicePageViewModel.createInvoice(); + }, + color: Theme.of(context).primaryColor, + textColor: Colors.white, + isLoading: lightningInvoicePageViewModel.state is IsExecutingState, + ); + }), + ], + ), + ), + ), + ), + ); + } + + void _setReactions(BuildContext context) { + if (effectsInstalled) { + return; + } + + reaction((_) => receiveOptionViewModel.selectedReceiveOption, (ReceivePageOption option) async { + if (option == lightning!.getOptionOnchain()) { + Navigator.popAndPushNamed( + context, + Routes.lightningReceiveOnchain, + arguments: [lightning!.getOptionOnchain()], + ); + } + }); + + reaction((_) => lightningInvoicePageViewModel.state, (ExecutionState state) { + if (state is ExecutedSuccessfullyState) { + showPopUp( + context: context, + builder: (BuildContext context) { + return AlertWithTwoActions( + alertTitle: S.of(context).invoice_created, + alertContent: '', + contentWidget: Column( + children: [ + Center( + child: AspectRatio( + aspectRatio: 1.0, + child: Container( + padding: EdgeInsets.all(5), + decoration: BoxDecoration( + border: Border.all( + width: 3, + color: Theme.of(context).extension()!.textColor, + ), + ), + child: Container( + decoration: BoxDecoration( + border: Border.all( + width: 3, + color: Colors.white, + ), + ), + child: QrImage( + data: state.payload as String, + version: 14, + errorCorrectionLevel: qr.QrErrorCorrectLevel.L, + )), + ), + ), + ), + Container( + padding: const EdgeInsets.only(top: 12, bottom: 12, right: 6), + margin: const EdgeInsets.only(top: 32), + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(15)), + color: Color.fromARGB(255, 170, 147, 30), + border: Border.all( + color: Color.fromARGB(178, 223, 214, 0), + width: 2, + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + width: 32, + height: 32, + margin: EdgeInsets.only(left: 12, bottom: 48, right: 12), + child: Image.asset( + "assets/images/warning.png", + color: Color.fromARGB(128, 255, 255, 255), + ), + ), + Expanded( + child: Material( + color: Colors.transparent, + child: Text( + S.of(context).lightning_invoice_warning, + maxLines: 5, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500, + color: + Theme.of(context).extension()!.textColor, + ), + ), + ), + ), + ], + ), + ), + ], + ), + rightButtonText: S.of(context).ok, + actionRightButton: () => Navigator.of(context).pop(), + actionLeftButton: () async { + Clipboard.setData(ClipboardData(text: state.payload as String)); + showBar(context, S.of(context).copied_to_clipboard); + }, + leftButtonText: S.of(context).copy, + ); + }); + } + + if (state is FailureState) { + showPopUp( + context: context, + builder: (BuildContext context) { + return AlertWithOneAction( + alertTitle: S.of(context).error, + alertContent: state.error.toString(), + buttonText: S.of(context).ok, + buttonAction: () => Navigator.of(context).pop()); + }); + } + }); + + effectsInstalled = true; + } +} diff --git a/lib/src/screens/receive/lightning_receive_page.dart b/lib/src/screens/receive/lightning_receive_page.dart new file mode 100644 index 000000000..145f5c518 --- /dev/null +++ b/lib/src/screens/receive/lightning_receive_page.dart @@ -0,0 +1,252 @@ +import 'package:cake_wallet/entities/qr_view_data.dart'; +import 'package:cake_wallet/lightning/lightning.dart'; +import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/src/screens/dashboard/widgets/present_receive_option_picker.dart'; +import 'package:cake_wallet/src/screens/receive/widgets/qr_image.dart'; +import 'package:cake_wallet/src/widgets/gradient_background.dart'; +import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart'; +import 'package:cake_wallet/themes/extensions/qr_code_theme.dart'; +import 'package:cake_wallet/utils/brightness_util.dart'; +import 'package:cake_wallet/utils/show_bar.dart'; +import 'package:cake_wallet/view_model/dashboard/receive_option_view_model.dart'; +import 'package:cake_wallet/view_model/lightning_view_model.dart'; +import 'package:cw_core/receive_page_option.dart'; +import 'package:flutter/material.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:mobx/mobx.dart'; + +class LightningReceiveOnchainPage extends BasePage { + LightningReceiveOnchainPage( + {required this.addressListViewModel, + required this.receiveOptionViewModel, + required this.lightningViewModel}) + : _amountController = TextEditingController(), + _formKey = GlobalKey() { + _amountController.addListener(() { + if (_formKey.currentState!.validate()) { + addressListViewModel.changeAmount(_amountController.text); + } + }); + } + + final WalletAddressListViewModel addressListViewModel; + final ReceiveOptionViewModel receiveOptionViewModel; + final LightningViewModel lightningViewModel; + final TextEditingController _amountController; + final GlobalKey _formKey; + + bool effectsInstalled = false; + + @override + String get title => S.current.receive; + + @override + bool get gradientBackground => true; + + @override + bool get resizeToAvoidBottomInset => true; + + @override + Widget middle(BuildContext context) => PresentReceiveOptionPicker( + color: titleColor(context), receiveOptionViewModel: receiveOptionViewModel); + + @override + Widget Function(BuildContext, Widget) get rootWrapper => + (BuildContext context, Widget scaffold) => GradientBackground(scaffold: scaffold); + + @override + Widget body(BuildContext context) { + WidgetsBinding.instance.addPostFrameCallback((_) => _setReactions(context)); + final copyImage = Image.asset('assets/images/copy_address.png', + color: Theme.of(context).extension()!.qrWidgetCopyButtonColor); + String heroTag = "lightning_receive"; + return Column( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + FutureBuilder( + future: lightningViewModel.receiveOnchain(), + builder: ((context, snapshot) { + if (snapshot.data == null) { + return CircularProgressIndicator(); + } + ReceiveOnchainResult results = snapshot.data as ReceiveOnchainResult; + return Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.only(bottom: 12), + child: Text( + S.of(context).qr_fullscreen, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + color: Theme.of(context).extension()!.textColor), + ), + ), + Row( + children: [ + Spacer(flex: 3), + Observer( + builder: (_) => Flexible( + flex: 5, + child: GestureDetector( + onTap: () { + BrightnessUtil.changeBrightnessForFunction( + () async { + await Navigator.pushNamed(context, Routes.fullscreenQR, + arguments: QrViewData( + data: results.bitcoinAddress, + heroTag: heroTag, + )); + }, + ); + }, + child: Hero( + tag: Key(heroTag), + child: Center( + child: AspectRatio( + aspectRatio: 1.0, + child: Container( + padding: EdgeInsets.all(5), + decoration: BoxDecoration( + border: Border.all( + width: 3, + color: Theme.of(context) + .extension()! + .textColor, + ), + ), + child: Container( + decoration: BoxDecoration( + border: Border.all( + width: 3, + color: Colors.white, + ), + ), + child: QrImage(data: results.bitcoinAddress)), + ), + ), + ), + ), + ), + ), + ), + Spacer(flex: 3) + ], + ), + Padding( + padding: EdgeInsets.only(top: 20, bottom: 8, left: 24, right: 24), + child: Builder( + builder: (context) => Observer( + builder: (context) => GestureDetector( + onTap: () { + Clipboard.setData(ClipboardData(text: results.bitcoinAddress)); + showBar(context, S.of(context).copied_to_clipboard); + }, + child: Row( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Text( + results.bitcoinAddress, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 15, + fontWeight: FontWeight.w500, + color: Theme.of(context) + .extension()! + .textColor), + ), + ), + Padding( + padding: EdgeInsets.only(left: 12), + child: copyImage, + ) + ], + ), + ), + ), + ), + ) + ], + ); + }), + ), + Container( + padding: const EdgeInsets.only(top: 24, bottom: 24, right: 6), + margin: const EdgeInsets.symmetric(horizontal: 24), + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(15)), + color: Color.fromARGB(255, 170, 147, 30), + border: Border.all( + color: Color.fromARGB(178, 223, 214, 0), + width: 2, + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + width: 48, + height: 48, + margin: EdgeInsets.only(left: 12, bottom: 48, right: 20), + child: Image.asset( + "assets/images/warning.png", + color: Color.fromARGB(128, 255, 255, 255), + ), + ), + FutureBuilder( + future: lightningViewModel.receiveOnchain(), + builder: (context, snapshot) { + if (snapshot.data == null) { + return Expanded( + child: Container(child: Center(child: CircularProgressIndicator()))); + } + ReceiveOnchainResult results = snapshot.data as ReceiveOnchainResult; + return Expanded( + child: Text( + S.of(context).lightning_receive_limits( + lightning!.satsToLightningString(results.minAllowedDeposit), + lightning!.satsToLightningString(results.maxAllowedDeposit), + results.feePercent.toString(), + lightning!.satsToLightningString(results.fee), + ), + maxLines: 10, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + color: Theme.of(context).extension()!.textColor, + ), + ), + ); + }), + ], + ), + ), + ], + ); + } + + void _setReactions(BuildContext context) { + if (effectsInstalled) { + return; + } + + reaction((_) => receiveOptionViewModel.selectedReceiveOption, (ReceivePageOption option) async { + if (option == lightning!.getOptionInvoice()) { + Navigator.popAndPushNamed( + context, + Routes.lightningInvoice, + arguments: [lightning!.getOptionInvoice()], + ); + } + }); + + effectsInstalled = true; + } +} diff --git a/lib/src/screens/receive/widgets/anonpay_currency_input_field.dart b/lib/src/screens/receive/widgets/anonpay_currency_input_field.dart index be39ac3bb..d9e863cef 100644 --- a/lib/src/screens/receive/widgets/anonpay_currency_input_field.dart +++ b/lib/src/screens/receive/widgets/anonpay_currency_input_field.dart @@ -7,15 +7,16 @@ import 'package:flutter/services.dart'; import 'package:cake_wallet/themes/extensions/send_page_theme.dart'; class AnonpayCurrencyInputField extends StatelessWidget { - const AnonpayCurrencyInputField( - {super.key, - required this.onTapPicker, - required this.selectedCurrency, - required this.focusNode, - required this.controller, - required this.minAmount, - required this.maxAmount}); - final Function() onTapPicker; + const AnonpayCurrencyInputField({ + super.key, + this.onTapPicker, + required this.selectedCurrency, + required this.focusNode, + required this.controller, + required this.minAmount, + required this.maxAmount, + }); + final Function()? onTapPicker; final Currency selectedCurrency; final FocusNode focusNode; final TextEditingController controller; @@ -23,6 +24,7 @@ class AnonpayCurrencyInputField extends StatelessWidget { final String maxAmount; @override Widget build(BuildContext context) { + bool hasDecimals = selectedCurrency.name.toLowerCase() != "sats"; final arrowBottomPurple = Image.asset( 'assets/images/arrow_bottom_purple_icon.png', color: Colors.white, @@ -34,40 +36,50 @@ class AnonpayCurrencyInputField extends StatelessWidget { decoration: BoxDecoration( border: Border( bottom: BorderSide( - color: - Theme.of(context).extension()!.textFieldBorderBottomPanelColor, + color: Theme.of(context) + .extension()! + .textFieldBorderBottomPanelColor, width: 1)), ), child: Padding( padding: EdgeInsets.only(top: 20), child: Row( children: [ - Container( - padding: EdgeInsets.only(right: 8), - height: 32, - child: InkWell( - onTap: onTapPicker, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: EdgeInsets.only(right: 5), - child: arrowBottomPurple, - ), - Text(selectedCurrency.name.toUpperCase(), - style: TextStyle( - fontWeight: FontWeight.w600, fontSize: 16, color: Colors.white)) - ]), - ), - ), + if (onTapPicker != null) + Container( + padding: EdgeInsets.only(right: 8), + height: 32, + child: InkWell( + onTap: onTapPicker, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: EdgeInsets.only(right: 5), + child: arrowBottomPurple, + ), + Text(selectedCurrency.name.toUpperCase(), + style: TextStyle( + fontWeight: FontWeight.w600, + fontSize: 16, + color: Colors.white)) + ]), + ), + ) + else + Text(selectedCurrency.name.toUpperCase(), + style: TextStyle( + fontWeight: FontWeight.w600, fontSize: 16, color: Colors.white)), selectedCurrency.tag != null ? Padding( padding: const EdgeInsets.only(right: 3.0), child: Container( height: 32, decoration: BoxDecoration( - color: Theme.of(context).extension()!.textFieldButtonColor, + color: Theme.of(context) + .extension()! + .textFieldButtonColor, borderRadius: BorderRadius.all(Radius.circular(6))), child: Center( child: Padding( @@ -77,7 +89,9 @@ class AnonpayCurrencyInputField extends StatelessWidget { style: TextStyle( fontSize: 12, fontWeight: FontWeight.bold, - color: Theme.of(context).extension()!.textFieldButtonIconColor, + color: Theme.of(context) + .extension()! + .textFieldButtonIconColor, ), ), ), @@ -102,20 +116,21 @@ class AnonpayCurrencyInputField extends StatelessWidget { textInputAction: TextInputAction.next, enabled: true, textAlign: TextAlign.left, - keyboardType: - TextInputType.numberWithOptions(signed: false, decimal: true), + keyboardType: TextInputType.numberWithOptions( + signed: false, decimal: hasDecimals), inputFormatters: [ - FilteringTextInputFormatter.deny(RegExp('[\\-|\\ ]')) + FilteringTextInputFormatter.deny(RegExp('[\\-|\\ ]')), + if (!hasDecimals) FilteringTextInputFormatter.deny(RegExp('[\.,]')), ], - hintText: '0.0000', + hintText: hasDecimals ? '0.0000' : '0', borderColor: Colors.transparent, - //widget.borderColor, textStyle: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, color: Colors.white), placeholderTextStyle: TextStyle( fontSize: 16, fontWeight: FontWeight.w600, - color: Theme.of(context).extension()!.hintTextColor, + color: + Theme.of(context).extension()!.hintTextColor, ), validator: null, ), @@ -131,19 +146,23 @@ class AnonpayCurrencyInputField extends StatelessWidget { child: Row( mainAxisAlignment: MainAxisAlignment.start, children: [ - Text( - S.of(context).min_value(minAmount, selectedCurrency.toString()), - style: TextStyle( - fontSize: 10, - height: 1.2, - color: Theme.of(context).extension()!.hintTextColor), - ), - SizedBox(width: 10), - Text(S.of(context).max_value(maxAmount, selectedCurrency.toString()), + if (minAmount.isNotEmpty) ...[ + Text( + S.of(context).min_value(minAmount, selectedCurrency.toString()), style: TextStyle( fontSize: 10, height: 1.2, - color: Theme.of(context).extension()!.hintTextColor)), + color: Theme.of(context).extension()!.hintTextColor), + ), + ], + SizedBox(width: 10), + if (maxAmount.isNotEmpty) ...[ + Text(S.of(context).max_value(maxAmount, selectedCurrency.toString()), + style: TextStyle( + fontSize: 10, + height: 1.2, + color: Theme.of(context).extension()!.hintTextColor)) + ], ], ), ) diff --git a/lib/src/screens/receive/widgets/lightning_input_form.dart b/lib/src/screens/receive/widgets/lightning_input_form.dart new file mode 100644 index 000000000..59727d629 --- /dev/null +++ b/lib/src/screens/receive/widgets/lightning_input_form.dart @@ -0,0 +1,81 @@ +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/lightning/lightning.dart'; +import 'package:cake_wallet/src/screens/receive/widgets/anonpay_currency_input_field.dart'; +import 'package:cake_wallet/src/widgets/base_text_form_field.dart'; +import 'package:cake_wallet/themes/extensions/exchange_page_theme.dart'; +import 'package:cake_wallet/typography.dart'; +import 'package:cake_wallet/view_model/lightning_invoice_page_view_model.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:cake_wallet/lightning/lightning.dart'; + +class LightningInvoiceForm extends StatelessWidget { + LightningInvoiceForm({ + super.key, + required this.formKey, + required this.lightningInvoicePageViewModel, + required this.amountController, + required this.descriptionController, + required this.depositAmountFocus, + }) : _descriptionFocusNode = FocusNode() { + amountController.text = lightningInvoicePageViewModel.amount; + descriptionController.text = lightningInvoicePageViewModel.description; + } + + final TextEditingController amountController; + final TextEditingController descriptionController; + final LightningInvoicePageViewModel lightningInvoicePageViewModel; + final FocusNode depositAmountFocus; + final FocusNode _descriptionFocusNode; + final GlobalKey formKey; + + @override + Widget build(BuildContext context) { + return Form( + key: formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + S.of(context).invoice_details, + style: textMediumSemiBold(), + ), + Observer(builder: (_) { + return AnonpayCurrencyInputField( + controller: amountController, + focusNode: depositAmountFocus, + maxAmount: '', + minAmount: (lightningInvoicePageViewModel.minimum != null) + ? lightning! + .satsToLightningString(lightningInvoicePageViewModel.minimum!.round()) + : '...', + selectedCurrency: CryptoCurrency.btcln, + ); + }), + SizedBox( + height: 24, + ), + BaseTextFormField( + controller: descriptionController, + focusNode: _descriptionFocusNode, + textInputAction: TextInputAction.next, + borderColor: + Theme.of(context).extension()!.textFieldBorderTopPanelColor, + suffixIcon: SizedBox(width: 36), + hintText: S.of(context).optional_description, + placeholderTextStyle: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Theme.of(context).extension()!.hintTextColor, + ), + textStyle: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Colors.white), + validator: null, + ), + SizedBox( + height: 52, + ), + ], + )); + } +} diff --git a/lib/src/screens/receive/widgets/qr_image.dart b/lib/src/screens/receive/widgets/qr_image.dart index f388fdd0b..74b561412 100644 --- a/lib/src/screens/receive/widgets/qr_image.dart +++ b/lib/src/screens/receive/widgets/qr_image.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:qr_flutter/qr_flutter.dart' as qr; +import 'package:qr_flutter/qr_flutter.dart'; class QrImage extends StatelessWidget { QrImage({ @@ -23,7 +24,9 @@ class QrImage extends StatelessWidget { return qr.QrImageView( data: data, errorCorrectionLevel: errorCorrectionLevel, - version: version ?? 9, // Previous value: 7 something happened after flutter upgrade monero wallets addresses are longer than ver. 7 ??? + // Previous value: 7 something happened after flutter upgrade monero wallets addresses are longer than ver. 7 ??? + // changed from 9 to auto + version: version ?? QrVersions.auto, size: size, foregroundColor: foregroundColor, backgroundColor: backgroundColor, diff --git a/lib/src/screens/restore/wallet_restore_page.dart b/lib/src/screens/restore/wallet_restore_page.dart index 80630e51c..be286a34e 100644 --- a/lib/src/screens/restore/wallet_restore_page.dart +++ b/lib/src/screens/restore/wallet_restore_page.dart @@ -270,7 +270,8 @@ class WalletRestorePage extends BasePage { // bip39: const validSeedLengths = [12, 18, 24]; - if (walletRestoreViewModel.type == WalletType.bitcoin && + final type = walletRestoreViewModel.type; + if ((type == WalletType.bitcoin || type == WalletType.lightning) && !(validSeedLengths.contains(seedWords.length))) { return false; } diff --git a/lib/src/screens/send/lightning_send_confirm_page.dart b/lib/src/screens/send/lightning_send_confirm_page.dart new file mode 100644 index 000000000..f5616eb46 --- /dev/null +++ b/lib/src/screens/send/lightning_send_confirm_page.dart @@ -0,0 +1,311 @@ +import 'package:breez_sdk/breez_sdk.dart'; +import 'package:breez_sdk/bridge_generated.dart' as BZG; +import 'package:cake_wallet/entities/fiat_currency.dart'; +import 'package:cake_wallet/lightning/lightning.dart'; +import 'package:cake_wallet/src/screens/receive/widgets/anonpay_currency_input_field.dart'; +import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; +import 'package:cake_wallet/src/widgets/base_text_form_field.dart'; +import 'package:cake_wallet/src/widgets/keyboard_done_button.dart'; +import 'package:cake_wallet/themes/extensions/exchange_page_theme.dart'; +import 'package:cake_wallet/themes/extensions/keyboard_theme.dart'; +import 'package:cake_wallet/themes/theme_base.dart'; +import 'package:cake_wallet/utils/responsive_layout_util.dart'; +import 'package:cake_wallet/utils/show_pop_up.dart'; +import 'package:cake_wallet/view_model/lightning_send_view_model.dart'; +import 'package:cake_wallet/view_model/lightning_view_model.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_mobx/flutter_mobx.dart'; +import 'package:keyboard_actions/keyboard_actions.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/src/widgets/primary_button.dart'; +import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart'; +import 'package:cake_wallet/generated/i18n.dart'; + +class LightningSendConfirmPage extends BasePage { + LightningSendConfirmPage({required this.invoice, required this.lightningSendViewModel}) + : _formKey = GlobalKey() { + initialSatAmount = ((invoice.amountMsat ?? 0) ~/ 1000); + _amountController = TextEditingController(); + _fiatAmountController = TextEditingController(); + _amountController.text = initialSatAmount.toString(); + _fiatAmountController.text = lightningSendViewModel.formattedFiatAmount(initialSatAmount); + } + + final GlobalKey _formKey; + final controller = PageController(initialPage: 0); + + BZG.LNInvoice invoice; + late int initialSatAmount; + late TextEditingController _amountController; + late TextEditingController _fiatAmountController; + final FocusNode _depositAmountFocus = FocusNode(); + final LightningSendViewModel lightningSendViewModel; + + bool _effectsInstalled = false; + + @override + String get title => S.current.send; + + @override + bool get gradientAll => true; + + @override + bool get resizeToAvoidBottomInset => false; + + @override + bool get extendBodyBehindAppBar => true; + + @override + Widget? leading(BuildContext context) { + final _backButton = Icon( + Icons.arrow_back_ios, + color: titleColor(context), + size: 16, + ); + final _closeButton = + currentTheme.type == ThemeType.dark ? closeButtonImageDarkTheme : closeButtonImage; + + bool isMobileView = responsiveLayoutUtil.shouldRenderMobileUI; + + return MergeSemantics( + child: SizedBox( + height: isMobileView ? 37 : 45, + width: isMobileView ? 37 : 45, + child: ButtonTheme( + minWidth: double.minPositive, + child: Semantics( + label: !isMobileView ? S.of(context).close : S.of(context).seed_alert_back, + child: TextButton( + style: ButtonStyle( + overlayColor: MaterialStateColor.resolveWith((states) => Colors.transparent), + ), + onPressed: () => onClose(context), + child: !isMobileView ? _closeButton : _backButton, + ), + ), + ), + ), + ); + } + + @override + AppBarStyle get appBarStyle => AppBarStyle.transparent; + + @override + void onClose(BuildContext context) { + Navigator.of(context).pop(); + } + + @override + Widget body(BuildContext context) { + _setEffects(context); + + return WillPopScope( + onWillPop: () => _onNavigateBack(context), + child: KeyboardActions( + disableScroll: true, + config: KeyboardActionsConfig( + keyboardActionsPlatform: KeyboardActionsPlatform.IOS, + keyboardBarColor: Theme.of(context).extension()!.keyboardBarColor, + nextFocus: false, + actions: [ + KeyboardActionsItem( + focusNode: FocusNode(), + toolbarButtons: [(_) => KeyboardDoneButton()], + ), + ]), + child: Container( + color: Theme.of(context).colorScheme.background, + child: ScrollableWithBottomSection( + contentPadding: EdgeInsets.only(bottom: 24), + content: Container( + decoration: responsiveLayoutUtil.shouldRenderMobileUI + ? BoxDecoration( + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(24), bottomRight: Radius.circular(24)), + gradient: LinearGradient( + colors: [ + Theme.of(context) + .extension()! + .firstGradientTopPanelColor, + Theme.of(context) + .extension()! + .secondGradientTopPanelColor, + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + ) + : null, + child: Observer(builder: (_) { + return Padding( + padding: EdgeInsets.fromLTRB(24, 120, 24, 0), + child: Column( + children: [ + BaseTextFormField( + enabled: false, + borderColor: Theme.of(context) + .extension()! + .textFieldBorderTopPanelColor, + suffixIcon: SizedBox(width: 36), + initialValue: "${S.of(context).invoice}: ${invoice.bolt11}", + placeholderTextStyle: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Theme.of(context).extension()!.hintTextColor, + ), + textStyle: TextStyle( + fontSize: 16, fontWeight: FontWeight.w600, color: Colors.white), + validator: null, + ), + SizedBox(height: 24), + if (invoice.amountMsat == null) + Observer(builder: (_) { + return AnonpayCurrencyInputField( + controller: _amountController, + focusNode: _depositAmountFocus, + maxAmount: '', + minAmount: '', + selectedCurrency: CryptoCurrency.btcln, + ); + }) + else + BaseTextFormField( + enabled: false, + borderColor: Theme.of(context) + .extension()! + .textFieldBorderTopPanelColor, + suffixIcon: SizedBox(width: 36), + initialValue: + "sats: ${lightning!.bitcoinAmountToLightningString(amount: initialSatAmount)}", + placeholderTextStyle: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Theme.of(context).extension()!.hintTextColor, + ), + textStyle: TextStyle( + fontSize: 16, fontWeight: FontWeight.w600, color: Colors.white), + validator: null, + ), + SizedBox(height: 24), + BaseTextFormField( + enabled: false, + controller: _fiatAmountController, + prefixIcon: Padding( + padding: EdgeInsets.only(top: 9), + child: Text( + lightningSendViewModel.fiat.title + ':', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Colors.white, + ), + ), + ), + borderColor: Theme.of(context) + .extension()! + .textFieldBorderTopPanelColor, + suffixIcon: SizedBox(width: 36), + placeholderTextStyle: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Theme.of(context).extension()!.hintTextColor, + ), + textStyle: TextStyle( + fontSize: 16, fontWeight: FontWeight.w600, color: Colors.white), + validator: null, + ), + SizedBox(height: 24), + if (invoice.description?.isNotEmpty ?? false) ...[ + BaseTextFormField( + enabled: false, + initialValue: "${S.of(context).description}: ${invoice.description}", + textInputAction: TextInputAction.next, + borderColor: Theme.of(context) + .extension()! + .textFieldBorderTopPanelColor, + suffixIcon: SizedBox(width: 36), + placeholderTextStyle: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Theme.of(context).extension()!.hintTextColor, + ), + textStyle: TextStyle( + fontSize: 16, fontWeight: FontWeight.w600, color: Colors.white), + validator: null, + ), + SizedBox(height: 24), + ], + ], + ), + ); + }), + ), + bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24), + bottomSection: Observer(builder: (_) { + return Column( + children: [ + LoadingPrimaryButton( + text: S.of(context).send, + onPressed: () async { + try { + await lightningSendViewModel.send( + invoice, + int.parse(_amountController.text) + ); + showPopUp( + context: context, + builder: (BuildContext context) { + return AlertWithOneAction( + alertTitle: '', + alertContent: + S.of(context).send_success(CryptoCurrency.btc.toString()), + buttonText: S.of(context).ok, + buttonAction: () { + Navigator.of(context).pop(); + }); + }); + } catch (e) { + showPopUp( + context: context, + builder: (BuildContext context) { + return AlertWithOneAction( + alertTitle: S.of(context).error, + alertContent: e.toString(), + buttonText: S.of(context).ok, + buttonAction: () => Navigator.of(context).pop()); + }); + } + }, + color: Theme.of(context).primaryColor, + textColor: Colors.white, + isLoading: lightningSendViewModel.loading, + ), + ], + ); + }), + ), + ), + ), + ); + } + + Future _onNavigateBack(BuildContext context) async { + onClose(context); + return false; + } + + void _setEffects(BuildContext context) { + if (_effectsInstalled) { + return; + } + + _amountController.addListener(() { + final amount = _amountController.text; + _fiatAmountController.text = lightningSendViewModel.formattedFiatAmount(int.parse(amount)); + }); + + _effectsInstalled = true; + } +} diff --git a/lib/src/screens/send/lightning_send_page.dart b/lib/src/screens/send/lightning_send_page.dart new file mode 100644 index 000000000..3e44fc12c --- /dev/null +++ b/lib/src/screens/send/lightning_send_page.dart @@ -0,0 +1,233 @@ +import 'package:breez_sdk/breez_sdk.dart'; +import 'package:breez_sdk/bridge_generated.dart'; +import 'package:cake_wallet/core/auth_service.dart'; +import 'package:cake_wallet/src/widgets/address_text_field.dart'; +import 'package:cake_wallet/src/widgets/alert_with_one_action.dart'; +import 'package:cake_wallet/src/widgets/keyboard_done_button.dart'; +import 'package:cake_wallet/themes/extensions/exchange_page_theme.dart'; +import 'package:cake_wallet/themes/extensions/keyboard_theme.dart'; +import 'package:cake_wallet/themes/extensions/send_page_theme.dart'; +import 'package:cake_wallet/themes/theme_base.dart'; +import 'package:cake_wallet/utils/payment_request.dart'; +import 'package:cake_wallet/utils/responsive_layout_util.dart'; +import 'package:cake_wallet/utils/show_pop_up.dart'; +import 'package:cake_wallet/view_model/lightning_send_view_model.dart'; +import 'package:cake_wallet/view_model/send/output.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:flutter/material.dart'; +import 'package:keyboard_actions/keyboard_actions.dart'; +import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/src/screens/base_page.dart'; +import 'package:cake_wallet/src/widgets/primary_button.dart'; +import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart'; +import 'package:cake_wallet/src/widgets/trail_button.dart'; +import 'package:cake_wallet/generated/i18n.dart'; + +class LightningSendPage extends BasePage { + LightningSendPage({ + required this.output, + required this.authService, + required this.lightningSendViewModel, + }) : _formKey = GlobalKey(); + + final Output output; + final AuthService authService; + final LightningSendViewModel lightningSendViewModel; + final GlobalKey _formKey; + + final bolt11Controller = TextEditingController(); + final bolt11FocusNode = FocusNode(); + + bool _effectsInstalled = false; + + @override + String get title => S.current.send; + + @override + bool get gradientAll => true; + + @override + bool get resizeToAvoidBottomInset => false; + + @override + bool get extendBodyBehindAppBar => true; + + @override + Widget? leading(BuildContext context) { + final _backButton = Icon( + Icons.arrow_back_ios, + color: titleColor(context), + size: 16, + ); + final _closeButton = + currentTheme.type == ThemeType.dark ? closeButtonImageDarkTheme : closeButtonImage; + + bool isMobileView = responsiveLayoutUtil.shouldRenderMobileUI; + + return MergeSemantics( + child: SizedBox( + height: isMobileView ? 37 : 45, + width: isMobileView ? 37 : 45, + child: ButtonTheme( + minWidth: double.minPositive, + child: Semantics( + label: !isMobileView ? S.of(context).close : S.of(context).seed_alert_back, + child: TextButton( + style: ButtonStyle( + overlayColor: MaterialStateColor.resolveWith((states) => Colors.transparent), + ), + onPressed: () => onClose(context), + child: !isMobileView ? _closeButton : _backButton, + ), + ), + ), + ), + ); + } + + @override + AppBarStyle get appBarStyle => AppBarStyle.transparent; + + @override + void onClose(BuildContext context) { + Navigator.of(context).pop(); + } + + @override + Widget trailing(context) => TrailButton( + caption: S.of(context).clear, + onPressed: () { + _formKey.currentState?.reset(); + }); + + @override + Widget body(BuildContext context) { + _setEffects(context); + + return WillPopScope( + onWillPop: () => _onNavigateBack(context), + child: KeyboardActions( + disableScroll: true, + config: KeyboardActionsConfig( + keyboardActionsPlatform: KeyboardActionsPlatform.IOS, + keyboardBarColor: Theme.of(context).extension()!.keyboardBarColor, + nextFocus: false, + actions: [ + KeyboardActionsItem( + focusNode: FocusNode(), + toolbarButtons: [(_) => KeyboardDoneButton()], + ), + ]), + child: Container( + color: Theme.of(context).colorScheme.background, + child: ScrollableWithBottomSection( + contentPadding: EdgeInsets.only(bottom: 24), + content: Container( + decoration: responsiveLayoutUtil.shouldRenderMobileUI + ? BoxDecoration( + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(24), bottomRight: Radius.circular(24)), + gradient: LinearGradient( + colors: [ + Theme.of(context) + .extension()! + .firstGradientTopPanelColor, + Theme.of(context) + .extension()! + .secondGradientTopPanelColor, + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + ) + : null, + child: Padding( + padding: EdgeInsets.fromLTRB(24, 120, 24, 0), + child: Column( + children: [ + AddressTextField( + focusNode: bolt11FocusNode, + controller: bolt11Controller, + onURIScanned: (uri) { + final paymentRequest = PaymentRequest.fromUri(uri); + bolt11Controller.text = paymentRequest.address; + }, + options: [ + AddressTextFieldOption.paste, + AddressTextFieldOption.qrCode, + AddressTextFieldOption.addressBook + ], + buttonColor: + Theme.of(context).extension()!.textFieldButtonColor, + borderColor: + Theme.of(context).extension()!.textFieldBorderColor, + textStyle: + TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white), + hintStyle: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + color: Theme.of(context).extension()!.textFieldHintColor), + onPushPasteButton: (context) async { + output.resetParsedAddress(); + await output.fetchParsedAddress(context); + await send(context); + }, + onPushAddressBookButton: (context) async { + output.resetParsedAddress(); + }, + onSelectedContact: (contact) { + output.loadContact(contact); + }, + selectedCurrency: CryptoCurrency.btc, + ), + SizedBox(height: 24), + ], + ), + ), + ), + bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24), + bottomSection: Column( + children: [ + PrimaryButton( + text: S.of(context).send, + color: Theme.of(context).primaryColor, + textColor: Colors.white, + onPressed: () => send(context), + ), + ], + ), + ), + ), + ), + ); + } + + Future send(BuildContext context) async { + try { + await lightningSendViewModel.processInput(context, bolt11Controller.text); + } catch (e) { + showPopUp( + context: context, + builder: (BuildContext context) { + return AlertWithOneAction( + alertTitle: S.of(context).error, + alertContent: e.toString(), + buttonText: S.of(context).ok, + buttonAction: () => Navigator.of(context).pop()); + }); + } + } + + Future _onNavigateBack(BuildContext context) async { + onClose(context); + return false; + } + + void _setEffects(BuildContext context) { + if (_effectsInstalled) { + return; + } + + _effectsInstalled = true; + } +} diff --git a/lib/src/screens/settings/connection_sync_page.dart b/lib/src/screens/settings/connection_sync_page.dart index c4d85a3a5..e417ad440 100644 --- a/lib/src/screens/settings/connection_sync_page.dart +++ b/lib/src/screens/settings/connection_sync_page.dart @@ -90,14 +90,22 @@ class ConnectionSyncPage extends BasePage { }), ], ], - SettingsCellWithArrow( - title: S.current.manage_nodes, - handler: (context) => Navigator.of(context).pushNamed(Routes.manageNodes), + Observer( + builder: (context) { + if (!dashboardViewModel.hasNodes) return const SizedBox(); + return Column( + children: [ + SettingsCellWithArrow( + title: S.current.manage_nodes, + handler: (context) => Navigator.of(context).pushNamed(Routes.manageNodes), + ), + ], + ); + }, ), Observer( builder: (context) { if (!dashboardViewModel.hasPowNodes) return const SizedBox(); - return Column( children: [ SettingsCellWithArrow( diff --git a/lib/src/screens/wallet_list/wallet_list_page.dart b/lib/src/screens/wallet_list/wallet_list_page.dart index 2a4841608..8d82afc66 100644 --- a/lib/src/screens/wallet_list/wallet_list_page.dart +++ b/lib/src/screens/wallet_list/wallet_list_page.dart @@ -96,6 +96,7 @@ class WalletListBody extends StatefulWidget { class WalletListBodyState extends State { final moneroIcon = Image.asset('assets/images/monero_logo.png', height: 24, width: 24); final bitcoinIcon = Image.asset('assets/images/bitcoin.png', height: 24, width: 24); + final lightningIcon = Image.asset('assets/images/lightning_logo.png', height: 24, width: 24); final tBitcoinIcon = Image.asset('assets/images/tbtc.png', height: 24, width: 24); final litecoinIcon = Image.asset('assets/images/litecoin_icon.png', height: 24, width: 24); final nonWalletTypeIcon = Image.asset('assets/images/close.png', height: 24, width: 24); @@ -309,6 +310,8 @@ class WalletListBodyState extends State { return tBitcoinIcon; } return bitcoinIcon; + case WalletType.lightning: + return lightningIcon; case WalletType.monero: return moneroIcon; case WalletType.litecoin: diff --git a/lib/src/widgets/alert_with_two_actions.dart b/lib/src/widgets/alert_with_two_actions.dart index ddb11c3ee..80423840e 100644 --- a/lib/src/widgets/alert_with_two_actions.dart +++ b/lib/src/widgets/alert_with_two_actions.dart @@ -10,6 +10,7 @@ class AlertWithTwoActions extends BaseAlertDialog { required this.rightButtonText, required this.actionLeftButton, required this.actionRightButton, + this.contentWidget, this.alertBarrierDismissible = true, this.isDividerExist = false, // this.leftActionColor, @@ -20,6 +21,7 @@ class AlertWithTwoActions extends BaseAlertDialog { final String alertContent; final String leftButtonText; final String rightButtonText; + final Widget? contentWidget; final VoidCallback actionLeftButton; final VoidCallback actionRightButton; final bool alertBarrierDismissible; @@ -47,4 +49,9 @@ class AlertWithTwoActions extends BaseAlertDialog { // Color get rightButtonColor => rightActionColor; @override bool get isDividerExists => isDividerExist; + + @override + Widget content(BuildContext context) { + return contentWidget ?? super.content(context); + } } diff --git a/lib/store/app_store.dart b/lib/store/app_store.dart index 7d61abfc5..703282d11 100644 --- a/lib/store/app_store.dart +++ b/lib/store/app_store.dart @@ -3,6 +3,7 @@ import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/reactions/wallet_connect.dart'; import 'package:cake_wallet/utils/exception_handler.dart'; import 'package:cw_core/transaction_info.dart'; +import 'package:cw_core/wallet_type.dart'; import 'package:mobx/mobx.dart'; import 'package:cw_core/balance.dart'; import 'package:cw_core/wallet_base.dart'; @@ -37,7 +38,8 @@ abstract class AppStoreBase with Store { @action Future changeCurrentWallet( WalletBase, TransactionInfo> wallet) async { - this.wallet?.close(); + bool switchingToSameWalletType = this.wallet?.type == wallet.type; + this.wallet?.close(switchingToSameWalletType: switchingToSameWalletType); this.wallet = wallet; this.wallet!.setExceptionHandler(ExceptionHandler.onError); diff --git a/lib/store/settings_store.dart b/lib/store/settings_store.dart index 160d50228..6dd9a1733 100644 --- a/lib/store/settings_store.dart +++ b/lib/store/settings_store.dart @@ -956,6 +956,7 @@ abstract class SettingsStoreBase with Store { if (bitcoinElectrumServer != null) { nodes[WalletType.bitcoin] = bitcoinElectrumServer; + nodes[WalletType.lightning] = bitcoinElectrumServer; } if (litecoinElectrumServer != null) { @@ -1320,6 +1321,7 @@ abstract class SettingsStoreBase with Store { if (bitcoinElectrumServer != null) { nodes[WalletType.bitcoin] = bitcoinElectrumServer; + nodes[WalletType.lightning] = bitcoinElectrumServer; } if (litecoinElectrumServer != null) { diff --git a/lib/view_model/advanced_privacy_settings_view_model.dart b/lib/view_model/advanced_privacy_settings_view_model.dart index 73308f15a..d12679298 100644 --- a/lib/view_model/advanced_privacy_settings_view_model.dart +++ b/lib/view_model/advanced_privacy_settings_view_model.dart @@ -48,6 +48,7 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store { case WalletType.haven: case WalletType.nano: case WalletType.banano: + case WalletType.lightning: return false; } } diff --git a/lib/view_model/dashboard/balance_view_model.dart b/lib/view_model/dashboard/balance_view_model.dart index c8acb9c2c..5c14447a2 100644 --- a/lib/view_model/dashboard/balance_view_model.dart +++ b/lib/view_model/dashboard/balance_view_model.dart @@ -65,7 +65,9 @@ abstract class BalanceViewModelBase with Store { @computed double get price { - final price = fiatConvertationStore.prices[appStore.wallet!.currency]; + CryptoCurrency currency = appStore.wallet!.currency; + + final price = fiatConvertationStore.prices[currency]; if (price == null) { // price should update on next fetch: @@ -153,6 +155,8 @@ abstract class BalanceViewModelBase with Store { case WalletType.nano: case WalletType.banano: return S.current.receivable_balance; + case WalletType.lightning: + return S.current.max_receivable; default: return S.current.unconfirmed; } @@ -249,6 +253,7 @@ abstract class BalanceViewModelBase with Store { asset: key, formattedAssetTitle: _formatterAsset(key))); } + final fiatCurrency = settingsStore.fiatCurrency; final price = fiatConvertationStore.prices[key] ?? 0; @@ -262,7 +267,7 @@ abstract class BalanceViewModelBase with Store { ' ' + _getFiatBalance(price: price, cryptoAmount: value.formattedAdditionalBalance)); - final availableFiatBalance = isFiatDisabled + var availableFiatBalance = isFiatDisabled ? '' : (fiatCurrency.toString() + ' ' + @@ -397,6 +402,7 @@ abstract class BalanceViewModelBase with Store { } String _getFiatBalance({required double price, String? cryptoAmount}) { + cryptoAmount = cryptoAmount?.replaceAll(',', '');// fix for amounts > 1000 if (cryptoAmount == null || cryptoAmount.isEmpty || double.tryParse(cryptoAmount) == null) { return '0.00'; } diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart index e0e473df4..c7b2edfdc 100644 --- a/lib/view_model/dashboard/dashboard_view_model.dart +++ b/lib/view_model/dashboard/dashboard_view_model.dart @@ -1,5 +1,6 @@ import 'dart:convert'; +import 'package:breez_sdk/bridge_generated.dart'; import 'package:cake_wallet/buy/buy_provider.dart'; import 'package:cake_wallet/core/key_service.dart'; import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart'; @@ -29,6 +30,7 @@ import 'package:cake_wallet/view_model/dashboard/formatted_item_list.dart'; import 'package:cake_wallet/view_model/dashboard/order_list_item.dart'; import 'package:cake_wallet/view_model/dashboard/trade_list_item.dart'; import 'package:cake_wallet/view_model/dashboard/transaction_list_item.dart'; +import 'package:cake_wallet/view_model/lightning_view_model.dart'; import 'package:cake_wallet/view_model/settings/sync_mode.dart'; import 'package:cake_wallet/wallet_type_utils.dart'; import 'package:cryptography/cryptography.dart'; @@ -54,19 +56,20 @@ part 'dashboard_view_model.g.dart'; class DashboardViewModel = DashboardViewModelBase with _$DashboardViewModel; abstract class DashboardViewModelBase with Store { - DashboardViewModelBase( - {required this.balanceViewModel, - required this.appStore, - required this.tradesStore, - required this.tradeFilterStore, - required this.transactionFilterStore, - required this.settingsStore, - required this.yatStore, - required this.ordersStore, - required this.anonpayTransactionsStore, - required this.sharedPreferences, - required this.keyService}) - : hasSellAction = false, + DashboardViewModelBase({ + required this.balanceViewModel, + required this.appStore, + required this.tradesStore, + required this.tradeFilterStore, + required this.transactionFilterStore, + required this.settingsStore, + required this.yatStore, + required this.ordersStore, + required this.anonpayTransactionsStore, + required this.sharedPreferences, + required this.keyService, + required this.lightningViewModel, + }) : hasSellAction = false, hasBuyAction = false, hasExchangeAction = false, isShowFirstYatIntroduction = false, @@ -345,6 +348,8 @@ abstract class DashboardViewModelBase with Store { TransactionFilterStore transactionFilterStore; + LightningViewModel lightningViewModel; + Map> filterItems; BuyProvider? get defaultBuyProvider => ProvidersHelper.getProviderByType( @@ -383,7 +388,12 @@ abstract class DashboardViewModelBase with Store { void furtherShowYatPopup(bool shouldShow) => settingsStore.shouldShowYatPopup = shouldShow; @computed - bool get isEnabledExchangeAction => settingsStore.exchangeStatus != ExchangeApiMode.disabled; + bool get isEnabledExchangeAction { + if (wallet.type == WalletType.lightning) { + return false; + } + return settingsStore.exchangeStatus != ExchangeApiMode.disabled; + } @observable bool hasExchangeAction; @@ -407,9 +417,26 @@ abstract class DashboardViewModelBase with Store { ReactionDisposer? _onMoneroBalanceChangeReaction; + @computed + bool get hasNodes => wallet.type != WalletType.lightning; + @computed bool get hasPowNodes => wallet.type == WalletType.nano || wallet.type == WalletType.banano; + String get serviceMessage { + if (wallet.type == WalletType.lightning) { + final serviceStatus = lightningViewModel.serviceHealthCheck(); + if (serviceStatus == HealthCheckStatus.ServiceDisruption) { + return S.current.breez_warning_disruption; + } else if (serviceStatus == HealthCheckStatus.Maintenance) { + return S.current.breez_warning_maintenance; + } + return ""; + } + + return ""; + } + bool get showRepWarning { if (wallet.type != WalletType.nano) { return false; diff --git a/lib/view_model/dashboard/receive_option_view_model.dart b/lib/view_model/dashboard/receive_option_view_model.dart index 1e4726eee..cf628c4c5 100644 --- a/lib/view_model/dashboard/receive_option_view_model.dart +++ b/lib/view_model/dashboard/receive_option_view_model.dart @@ -1,4 +1,5 @@ import 'package:cake_wallet/bitcoin/bitcoin.dart'; +import 'package:cake_wallet/lightning/lightning.dart'; import 'package:cw_core/receive_page_option.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_type.dart'; @@ -16,14 +17,28 @@ abstract class ReceiveOptionViewModelBase with Store { : ReceivePageOption.mainnet), _options = [] { final walletType = _wallet.type; - _options = walletType == WalletType.haven - ? [ReceivePageOption.mainnet] - : walletType == WalletType.bitcoin - ? [ - ...bitcoin!.getBitcoinReceivePageOptions(), - ...ReceivePageOptions.where((element) => element != ReceivePageOption.mainnet) - ] - : ReceivePageOptions; + + switch (walletType) { + case WalletType.haven: + _options = [ReceivePageOption.mainnet]; + break; + case WalletType.lightning: + _options = [...lightning!.getLightningReceivePageOptions()]; + break; + case WalletType.bitcoin: + _options = [ + ...bitcoin!.getBitcoinReceivePageOptions(), + ...ReceivePageOptions.where((element) => element != ReceivePageOption.mainnet) + ]; + break; + default: + _options = [ + ReceivePageOption.mainnet, + ReceivePageOption.anonPayDonationLink, + ReceivePageOption.anonPayInvoice + ]; + break; + } } final WalletBase _wallet; diff --git a/lib/view_model/dashboard/transaction_list_item.dart b/lib/view_model/dashboard/transaction_list_item.dart index 5e4448f7a..0c701b37b 100644 --- a/lib/view_model/dashboard/transaction_list_item.dart +++ b/lib/view_model/dashboard/transaction_list_item.dart @@ -2,6 +2,7 @@ import 'package:cake_wallet/entities/balance_display_mode.dart'; import 'package:cake_wallet/entities/fiat_currency.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/lightning/lightning.dart'; import 'package:cake_wallet/nano/nano.dart'; import 'package:cake_wallet/polygon/polygon.dart'; import 'package:cake_wallet/reactions/wallet_connect.dart'; @@ -122,6 +123,11 @@ class TransactionListItem extends ActionListItem with Keyable { cryptoAmount: bitcoin!.formatterBitcoinAmountToDouble(amount: transaction.amount), price: price); break; + case WalletType.lightning: + amount = calculateFiatAmountRaw( + cryptoAmount: lightning!.formatterLightningAmountToDouble(amount: transaction.amount), + price: price); + break; case WalletType.haven: final asset = haven!.assetOfTransaction(transaction); final price = balanceViewModel.fiatConvertationStore.prices[asset]; diff --git a/lib/view_model/exchange/exchange_trade_view_model.dart b/lib/view_model/exchange/exchange_trade_view_model.dart index c5ce7a591..f29838aa2 100644 --- a/lib/view_model/exchange/exchange_trade_view_model.dart +++ b/lib/view_model/exchange/exchange_trade_view_model.dart @@ -174,6 +174,11 @@ abstract class ExchangeTradeViewModelBase with Store { } static bool _checkIfCanSend(TradesStore tradesStore, WalletBase wallet) { + + if (wallet.currency == CryptoCurrency.btcln) { + return false; + } + bool _isEthToken() => wallet.currency == CryptoCurrency.eth && tradesStore.trade!.from.tag == CryptoCurrency.eth.title; diff --git a/lib/view_model/exchange/exchange_view_model.dart b/lib/view_model/exchange/exchange_view_model.dart index b4711068c..ccf106ad3 100644 --- a/lib/view_model/exchange/exchange_view_model.dart +++ b/lib/view_model/exchange/exchange_view_model.dart @@ -278,12 +278,16 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with bool get hasAllAmount => (wallet.type == WalletType.bitcoin || + wallet.type == WalletType.lightning || wallet.type == WalletType.litecoin || wallet.type == WalletType.bitcoinCash) && depositCurrency == wallet.currency; bool get isMoneroWallet => wallet.type == WalletType.monero; + // lightning doesn't have the same concept of "addresses" (since it uses invoices) + bool get hasAddress => wallet.type != WalletType.lightning; + bool get isLowFee { switch (wallet.type) { case WalletType.monero: @@ -539,6 +543,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with /// return after the first successful trade return; } catch (e) { + print(e); continue; } } @@ -651,6 +656,10 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with depositCurrency = CryptoCurrency.btc; receiveCurrency = CryptoCurrency.xmr; break; + case WalletType.lightning: + depositCurrency = CryptoCurrency.btcln; + receiveCurrency = CryptoCurrency.xmr; + break; case WalletType.litecoin: depositCurrency = CryptoCurrency.ltc; receiveCurrency = CryptoCurrency.xmr; diff --git a/lib/view_model/lightning_invoice_page_view_model.dart b/lib/view_model/lightning_invoice_page_view_model.dart new file mode 100644 index 000000000..4ab8b7b2f --- /dev/null +++ b/lib/view_model/lightning_invoice_page_view_model.dart @@ -0,0 +1,130 @@ +import 'package:cake_wallet/core/execution_state.dart'; +import 'package:cake_wallet/entities/fiat_currency.dart'; +import 'package:cake_wallet/store/settings_store.dart'; +import 'package:cake_wallet/view_model/lightning_view_model.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/currency.dart'; +import 'package:cw_core/receive_page_option.dart'; +import 'package:cw_core/wallet_base.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:mobx/mobx.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +part 'lightning_invoice_page_view_model.g.dart'; + +class LightningInvoicePageViewModel = LightningInvoicePageViewModelBase + with _$LightningInvoicePageViewModel; + +abstract class LightningInvoicePageViewModelBase with Store { + LightningInvoicePageViewModelBase( + this.settingsStore, + this._wallet, + this.sharedPreferences, + this.lightningViewModel, + ) : description = '', + amount = '', + state = InitialExecutionState(), + selectedCurrency = walletTypeToCryptoCurrency(_wallet.type), + cryptoCurrency = walletTypeToCryptoCurrency(_wallet.type) { + _fetchLimits(); + } + + List get currencies => [walletTypeToCryptoCurrency(_wallet.type), ...FiatCurrency.all]; + final SettingsStore settingsStore; + final WalletBase _wallet; + final SharedPreferences sharedPreferences; + final LightningViewModel lightningViewModel; + + @observable + Currency selectedCurrency; + + CryptoCurrency cryptoCurrency; + + @observable + String description; + + @observable + String amount; + + @observable + ExecutionState state; + + @computed + int get selectedCurrencyIndex => currencies.indexOf(selectedCurrency); + + @observable + double? minimum; + + @observable + double? maximum; + + @action + void selectCurrency(Currency currency) { + selectedCurrency = currency; + maximum = minimum = null; + if (currency is CryptoCurrency) { + cryptoCurrency = currency; + } else { + cryptoCurrency = walletTypeToCryptoCurrency(_wallet.type); + } + + _fetchLimits(); + } + + @action + Future createInvoice() async { + state = IsExecutingState(); + if (amount.isNotEmpty) { + final amountInCrypto = double.tryParse(amount); + if (amountInCrypto == null) { + state = FailureState('Amount is invalid'); + return; + } + if (minimum != null && amountInCrypto < minimum!) { + state = FailureState('Amount is too small'); + return; + } + if (amountInCrypto > 4000000) { + state = FailureState('Amount is too big'); + return; + } + } + + try { + String bolt11 = await lightningViewModel.createInvoice( + amountSats: amount, + description: description, + ); + state = ExecutedSuccessfullyState(payload: bolt11); + } catch (e) { + state = FailureState(e.toString()); + } + } + + @action + void setRequestParams({ + required String inputAmount, + required String inputDescription, + }) { + description = inputDescription; + amount = inputAmount; + } + + @action + Future _fetchLimits() async { + final limits = await lightningViewModel.invoiceSoftLimitsSats(); + minimum = limits.minFee.toDouble(); + maximum = limits.inboundLiquidity.toDouble(); + } + + @action + void reset() { + selectedCurrency = walletTypeToCryptoCurrency(_wallet.type); + cryptoCurrency = walletTypeToCryptoCurrency(_wallet.type); + description = ''; + amount = ''; + try { + _fetchLimits(); + } catch (_) {} + } +} diff --git a/lib/view_model/lightning_send_view_model.dart b/lib/view_model/lightning_send_view_model.dart new file mode 100644 index 000000000..c78782980 --- /dev/null +++ b/lib/view_model/lightning_send_view_model.dart @@ -0,0 +1,93 @@ +import 'dart:async'; +import 'package:breez_sdk/breez_sdk.dart'; +import 'package:breez_sdk/bridge_generated.dart' as BZG; +import 'package:cake_wallet/entities/calculate_fiat_amount_raw.dart'; +import 'package:cake_wallet/entities/fiat_currency.dart'; +import 'package:cake_wallet/lightning/lightning.dart'; +import 'package:cake_wallet/routes.dart'; +import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart'; +import 'package:cake_wallet/store/settings_store.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:flutter/widgets.dart'; +import 'package:mobx/mobx.dart'; + +part 'lightning_send_view_model.g.dart'; + +class LightningSendViewModel = LightningSendViewModelBase with _$LightningSendViewModel; + +abstract class LightningSendViewModelBase with Store { + LightningSendViewModelBase({ + required this.settingsStore, + required this.fiatConversionStore, + }) {} + + final SettingsStore settingsStore; + final FiatConversionStore fiatConversionStore; + + @observable + bool loading = false; + + @action + void setLoading(bool value) { + loading = value; + } + + FiatCurrency get fiat => settingsStore.fiatCurrency; + + String formattedFiatAmount(int sats) { + String amount = calculateFiatAmountRaw( + cryptoAmount: lightning!.formatterLightningAmountToDouble(amount: sats), + price: fiatConversionStore.prices[CryptoCurrency.btcln], + ); + return amount; + } + + @action + Future send(BZG.LNInvoice invoice, int satAmount) async { + try { + setLoading(true); + + final sdk = await BreezSDK(); + late BZG.SendPaymentRequest req; + + if (invoice.amountMsat == null) { + req = BZG.SendPaymentRequest( + bolt11: invoice.bolt11, + amountMsat: satAmount * 1000, + ); + } else { + req = BZG.SendPaymentRequest(bolt11: invoice.bolt11); + } + + await sdk.sendPayment(req: req); + + setLoading(false); + } catch (e) { + setLoading(false); + } + } + + @action + Future processInput(BuildContext context, String input) async { + FocusScope.of(context).unfocus(); + + final sdk = await BreezSDK(); + + late BZG.InputType inputType; + + try { + inputType = await sdk.parseInput(input: input); + } catch (_) { + throw Exception("Unknown input type"); + } + + if (inputType is BZG.InputType_Bolt11) { + final bolt11 = await sdk.parseInvoice(input); + Navigator.of(context).pushNamed(Routes.lightningSendConfirm, arguments: bolt11); + } else if (inputType is BZG.InputType_LnUrlPay) { + throw Exception("Unsupported input type"); + } else { + throw Exception("Unknown input type"); + } + } +} diff --git a/lib/view_model/lightning_view_model.dart b/lib/view_model/lightning_view_model.dart new file mode 100644 index 000000000..ea573785f --- /dev/null +++ b/lib/view_model/lightning_view_model.dart @@ -0,0 +1,130 @@ +import 'dart:async'; +import 'package:breez_sdk/breez_sdk.dart'; +import 'package:breez_sdk/bridge_generated.dart' as BZG; +import 'package:mobx/mobx.dart'; +import 'package:cw_lightning/.secrets.g.dart' as secrets; + +part 'lightning_view_model.g.dart'; + +class LightningViewModel = LightningViewModelBase with _$LightningViewModel; + +abstract class LightningViewModelBase with Store { + LightningViewModelBase() {} + + Future receiveOnchain() async { + final sdk = await BreezSDK(); + + BZG.ReceiveOnchainRequest req = const BZG.ReceiveOnchainRequest(); + BZG.SwapInfo swapInfo = await sdk.receiveOnchain(req: req); + print("Minimum amount allowed to deposit in sats: ${swapInfo.minAllowedDeposit}"); + print("Maximum amount allowed to deposit in sats: ${swapInfo.maxAllowedDeposit}"); + + int fee = 0; + double feePercent = 0; + + try { + final nodeState = (await sdk.nodeInfo())!; + int inboundLiquidity = nodeState.inboundLiquidityMsats ~/ 1000; + final openingFees = await sdk.openChannelFee( + req: BZG.OpenChannelFeeRequest(amountMsat: inboundLiquidity + 1)); + feePercent = (openingFees.feeParams.proportional * 100) / 1000000; + fee = openingFees.feeParams.minMsat ~/ 1000; + } catch (_) {} + + return ReceiveOnchainResult( + bitcoinAddress: swapInfo.bitcoinAddress, + minAllowedDeposit: swapInfo.minAllowedDeposit, + maxAllowedDeposit: swapInfo.maxAllowedDeposit, + feePercent: feePercent, + fee: fee, + ); + } + + Future createInvoice({required String amountSats, String? description}) async { + final sdk = await BreezSDK(); + final req = BZG.ReceivePaymentRequest( + amountMsat: (double.parse(amountSats) * 1000).round(), + description: description ?? '', + ); + final res = await sdk.receivePayment(req: req); + return res.lnInvoice.bolt11; + } + + Future invoiceSoftLimitsSats() async { + double feePercent = 0.4; + int minFee = (2500 * 1000) ~/ 1000; // 2500 sats + int inboundLiquidity = 1000000000 * 1000 * 10; // 10 BTC + int balance = 0; + + final sdk = await BreezSDK(); + + try { + final nodeState = (await sdk.nodeInfo())!; + inboundLiquidity = nodeState.inboundLiquidityMsats ~/ 1000; + + final openingFees = await sdk.openChannelFee( + req: BZG.OpenChannelFeeRequest(amountMsat: inboundLiquidity + 1)); + + feePercent = (openingFees.feeParams.proportional * 100) / 1000000; + minFee = openingFees.feeParams.minMsat ~/ 1000; + balance = nodeState.channelsBalanceMsat ~/ 1000; + } catch (_) {} + return InvoiceSoftLimitsResult( + minFee: minFee, + inboundLiquidity: inboundLiquidity, + feePercent: feePercent, + balance: balance, + ); + } + + Future getBalanceSats() async { + try { + final sdk = await BreezSDK(); + final nodeState = (await sdk.nodeInfo())!; + return nodeState.channelsBalanceMsat ~/ 1000; + } catch (_) { + return 0; + } + } + + Future serviceHealthCheck() async { + try { + final sdk = await BreezSDK(); + BZG.ServiceHealthCheckResponse response = + await sdk.serviceHealthCheck(apiKey: secrets.breezApiKey); + return response.status; + } catch (_) { + return BZG.HealthCheckStatus.ServiceDisruption; + } + } +} + +class ReceiveOnchainResult { + final String bitcoinAddress; + final int minAllowedDeposit; + final int maxAllowedDeposit; + final int fee; + final double feePercent; + + ReceiveOnchainResult({ + required this.bitcoinAddress, + required this.minAllowedDeposit, + required this.maxAllowedDeposit, + required this.fee, + required this.feePercent, + }); +} + +class InvoiceSoftLimitsResult { + final double feePercent; + final int minFee; + final int inboundLiquidity; + final int balance; + + InvoiceSoftLimitsResult({ + required this.inboundLiquidity, + required this.feePercent, + required this.minFee, + required this.balance, + }); +} diff --git a/lib/view_model/node_list/node_create_or_edit_view_model.dart b/lib/view_model/node_list/node_create_or_edit_view_model.dart index 850e248f2..50d5a442e 100644 --- a/lib/view_model/node_list/node_create_or_edit_view_model.dart +++ b/lib/view_model/node_list/node_create_or_edit_view_model.dart @@ -85,6 +85,7 @@ abstract class NodeCreateOrEditViewModelBase with Store { case WalletType.litecoin: case WalletType.bitcoinCash: case WalletType.bitcoin: + case WalletType.lightning: return false; } } diff --git a/lib/view_model/node_list/node_list_view_model.dart b/lib/view_model/node_list/node_list_view_model.dart index ea1dd574e..b045e46a8 100644 --- a/lib/view_model/node_list/node_list_view_model.dart +++ b/lib/view_model/node_list/node_list_view_model.dart @@ -58,6 +58,9 @@ abstract class NodeListViewModelBase with Store { node = getBitcoinDefaultElectrumServer(nodes: _nodeSource)!; } break; + case WalletType.lightning: + node = getBitcoinDefaultElectrumServer(nodes: _nodeSource)!; + break; case WalletType.monero: node = getMoneroDefaultNode(nodes: _nodeSource); break; diff --git a/lib/view_model/restore/restore_from_qr_vm.dart b/lib/view_model/restore/restore_from_qr_vm.dart index f5938911b..7df329d91 100644 --- a/lib/view_model/restore/restore_from_qr_vm.dart +++ b/lib/view_model/restore/restore_from_qr_vm.dart @@ -85,6 +85,7 @@ abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store spendKey: restoreWallet.spendKey ?? '', height: restoreWallet.height ?? 0); case WalletType.bitcoin: + case WalletType.lightning: case WalletType.litecoin: return bitcoin!.createBitcoinRestoreWalletFromWIFCredentials( name: name, password: password, wif: wif); diff --git a/lib/view_model/restore/wallet_restore_from_qr_code.dart b/lib/view_model/restore/wallet_restore_from_qr_code.dart index 335b1a006..bea3e8132 100644 --- a/lib/view_model/restore/wallet_restore_from_qr_code.dart +++ b/lib/view_model/restore/wallet_restore_from_qr_code.dart @@ -22,6 +22,9 @@ class WalletRestoreFromQRCode { 'bitcoin': WalletType.bitcoin, 'bitcoin-wallet': WalletType.bitcoin, 'bitcoin_wallet': WalletType.bitcoin, + 'lightning': WalletType.lightning, + 'lightning-wallet': WalletType.lightning, + 'lightning_wallet': WalletType.lightning, 'litecoin': WalletType.litecoin, 'litecoin-wallet': WalletType.litecoin, 'litecoin_wallet': WalletType.litecoin, diff --git a/lib/view_model/transaction_details_view_model.dart b/lib/view_model/transaction_details_view_model.dart index 5b7a1a8db..cd9b77f6c 100644 --- a/lib/view_model/transaction_details_view_model.dart +++ b/lib/view_model/transaction_details_view_model.dart @@ -58,6 +58,9 @@ abstract class TransactionDetailsViewModelBase with Store { case WalletType.bitcoinCash: _addElectrumListItems(tx, dateFormat); break; + case WalletType.lightning: + _addLightningListItems(tx, dateFormat); + break; case WalletType.haven: _addHavenListItems(tx, dateFormat); break; @@ -100,14 +103,16 @@ abstract class TransactionDetailsViewModelBase with Store { final type = wallet.type; - items.add(BlockExplorerListItem( - title: S.current.view_in_block_explorer, - value: _explorerDescription(type), - onTap: () { - try { - launch(_explorerUrl(type, tx.id)); - } catch (e) {} - })); + if (_explorerDescription(type) != '') { + items.add(BlockExplorerListItem( + title: S.current.view_in_block_explorer, + value: _explorerDescription(type), + onTap: () { + try { + launch(_explorerUrl(type, tx.id)); + } catch (e) {} + })); + } final description = transactionDescriptionBox.values.firstWhere( (val) => val.id == transactionInfo.id, @@ -262,6 +267,19 @@ abstract class TransactionDetailsViewModelBase with Store { items.addAll(_items); } + void _addLightningListItems(TransactionInfo tx, DateFormat dateFormat) { + final _items = [ + StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.id), + StandartListItem( + title: S.current.transaction_details_date, value: dateFormat.format(tx.date)), + StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()), + if (tx.feeFormatted()?.isNotEmpty ?? false) + StandartListItem(title: S.current.transaction_details_fee, value: tx.feeFormatted()!), + ]; + + items.addAll(_items); + } + void _addHavenListItems(TransactionInfo tx, DateFormat dateFormat) { items.addAll([ StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.id), diff --git a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart index dd7f02407..8cff5da24 100644 --- a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart +++ b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart @@ -83,6 +83,16 @@ class BitcoinURI extends PaymentURI { } } +class LightningURI extends PaymentURI { + LightningURI({required String amount, required String address}) + : super(amount: amount, address: address); + + @override + String toString() { + throw Exception('N/A for lightning wallets (need to make a bolt11 invoice).'); + } +} + class LitecoinURI extends PaymentURI { LitecoinURI({required String amount, required String address}) : super(amount: amount, address: address); @@ -282,6 +292,10 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo return BitcoinURI(amount: amount, address: address.address); } + if (wallet.type == WalletType.lightning) { + return LightningURI(amount: amount, address: address.address); + } + if (wallet.type == WalletType.litecoin) { return LitecoinURI(amount: amount, address: address.address); } diff --git a/lib/view_model/wallet_creation_vm.dart b/lib/view_model/wallet_creation_vm.dart index e659b50f7..2763daac2 100644 --- a/lib/view_model/wallet_creation_vm.dart +++ b/lib/view_model/wallet_creation_vm.dart @@ -104,6 +104,7 @@ abstract class WalletCreationVMBase with Store { derivationType: DerivationType.nano, ); case WalletType.bitcoin: + case WalletType.lightning: case WalletType.litecoin: return bitcoin!.getElectrumDerivations()[DerivationType.electrum]!.first; default: @@ -118,6 +119,7 @@ abstract class WalletCreationVMBase with Store { derivationType: DerivationType.nano, ); case WalletType.bitcoin: + case WalletType.lightning: return DerivationInfo( derivationType: DerivationType.bip39, derivationPath: "m/84'/0'/0'/0", diff --git a/lib/view_model/wallet_keys_view_model.dart b/lib/view_model/wallet_keys_view_model.dart index 5102ba8eb..a6868f906 100644 --- a/lib/view_model/wallet_keys_view_model.dart +++ b/lib/view_model/wallet_keys_view_model.dart @@ -21,6 +21,7 @@ class WalletKeysViewModel = WalletKeysViewModelBase with _$WalletKeysViewModel; abstract class WalletKeysViewModelBase with Store { WalletKeysViewModelBase(this._appStore) : title = _appStore.wallet!.type == WalletType.bitcoin || + _appStore.wallet!.type == WalletType.lightning || _appStore.wallet!.type == WalletType.litecoin || _appStore.wallet!.type == WalletType.bitcoinCash ? S.current.wallet_seed @@ -163,6 +164,7 @@ abstract class WalletKeysViewModelBase with Store { } if (_appStore.wallet!.type == WalletType.bitcoin || + _appStore.wallet!.type == WalletType.lightning || _appStore.wallet!.type == WalletType.litecoin || _appStore.wallet!.type == WalletType.bitcoinCash) { // final keys = bitcoin!.getWalletKeys(_appStore.wallet!); @@ -247,6 +249,8 @@ abstract class WalletKeysViewModelBase with Store { return 'banano-wallet'; case WalletType.polygon: return 'polygon-wallet'; + case WalletType.lightning: + return 'lightning-wallet'; case WalletType.solana: return 'solana-wallet'; case WalletType.tron: diff --git a/lib/view_model/wallet_new_vm.dart b/lib/view_model/wallet_new_vm.dart index 4729a38b2..4aa3f5166 100644 --- a/lib/view_model/wallet_new_vm.dart +++ b/lib/view_model/wallet_new_vm.dart @@ -1,5 +1,6 @@ import 'package:cake_wallet/ethereum/ethereum.dart'; import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart'; +import 'package:cake_wallet/lightning/lightning.dart'; import 'package:cake_wallet/solana/solana.dart'; import 'package:cake_wallet/tron/tron.dart'; import 'package:cake_wallet/wownero/wownero.dart'; @@ -69,6 +70,8 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store { name: name, language: options!.first as String, isPolyseed: options.last as bool); case WalletType.bitcoin: return bitcoin!.createBitcoinNewWalletCredentials(name: name); + case WalletType.lightning: + return bitcoin!.createBitcoinNewWalletCredentials(name: name); case WalletType.litecoin: return bitcoin!.createBitcoinNewWalletCredentials(name: name); case WalletType.haven: diff --git a/lib/view_model/wallet_restore_view_model.dart b/lib/view_model/wallet_restore_view_model.dart index 25a555b44..7e23ae070 100644 --- a/lib/view_model/wallet_restore_view_model.dart +++ b/lib/view_model/wallet_restore_view_model.dart @@ -108,6 +108,14 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { derivationType: derivationInfo!.derivationType!, derivationPath: derivationInfo.derivationPath!, ); + case WalletType.lightning: + return bitcoin!.createBitcoinRestoreWalletFromSeedCredentials( + name: name, + mnemonic: seed, + password: password, + derivationType: derivationInfo!.derivationType!, + derivationPath: derivationInfo.derivationPath!, + ); case WalletType.haven: return haven!.createHavenRestoreWalletFromSeedCredentials( name: name, height: height, mnemonic: seed, password: password); diff --git a/model_generator.sh b/model_generator.sh index 58ce9b5d0..33e184bff 100755 --- a/model_generator.sh +++ b/model_generator.sh @@ -2,6 +2,7 @@ cd cw_core; flutter pub get; flutter packages pub run build_runner build --delet cd cw_evm; flutter pub get; flutter packages pub run build_runner build --delete-conflicting-outputs; cd .. cd cw_monero; flutter pub get; flutter packages pub run build_runner build --delete-conflicting-outputs; cd .. cd cw_bitcoin; flutter pub get; flutter packages pub run build_runner build --delete-conflicting-outputs; cd .. +cd cw_lightning && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_haven; flutter pub get; flutter packages pub run build_runner build --delete-conflicting-outputs; cd .. cd cw_nano; flutter pub get; flutter packages pub run build_runner build --delete-conflicting-outputs; cd .. cd cw_bitcoin_cash; flutter pub get; flutter packages pub run build_runner build --delete-conflicting-outputs; cd .. diff --git a/pubspec_base.yaml b/pubspec_base.yaml index 0ce331d57..3723d4f22 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -12,7 +12,7 @@ dependencies: version: 4.0.2 shared_preferences: ^2.0.15 # provider: ^6.0.3 - rxdart: ^0.27.4 + rxdart: ^0.28.0 yaml: ^3.1.1 #barcode_scan: any barcode_scan2: ^4.2.1 @@ -107,14 +107,20 @@ dependencies: ref: cake-update-v3 ledger_flutter: ^1.0.1 + breez_sdk: + git: + url: https://github.com/breez/breez-sdk-flutter.git + ref: v0.4.3-rc1 + + dev_dependencies: flutter_test: sdk: flutter - build_runner: ^2.3.3 + build_runner: ^2.4.7 logging: ^1.2.0 mobx_codegen: ^2.1.1 build_resolvers: ^2.0.9 - hive_generator: ^1.1.3 + hive_generator: ^2.0.1 # flutter_launcher_icons: ^0.11.0 # check flutter_launcher_icons for usage pedantic: ^1.8.0 @@ -128,6 +134,8 @@ dependency_overrides: bech32: git: url: https://github.com/cake-tech/bech32.git + flutter_rust_bridge: ^1.82.6 + uuid: ^4.1.0 ledger_flutter: git: url: https://github.com/cake-tech/ledger-flutter.git diff --git a/res/values/strings_ar.arb b/res/values/strings_ar.arb index db6a4cae2..80f2b1f1d 100644 --- a/res/values/strings_ar.arb +++ b/res/values/strings_ar.arb @@ -81,6 +81,8 @@ "bitcoin_payments_require_1_confirmation": "تتطلب مدفوعات Bitcoin تأكيدًا واحدًا ، والذي قد يستغرق 20 دقيقة أو أكثر. شكرا لصبرك! سيتم إرسال بريد إلكتروني إليك عند تأكيد الدفع.", "block_remaining": "1 كتلة متبقية", "Blocks_remaining": "بلوك متبقي ${status}", + "breez_warning_disruption": "خدمة Breez تواجه حاليًا قضايا. الرجاء معاودة المحاولة في وقت لاحق.", + "breez_warning_maintenance": "تمر خدمة Breez حاليًا بالصيانة. الرجاء معاودة المحاولة في وقت لاحق.", "bluetooth": "بلوتوث", "bright_theme": "مشرق", "bump_fee": "رسوم عثرة", @@ -337,6 +339,8 @@ "inputs": "المدخلات", "introducing_cake_pay": "نقدم لكم Cake Pay!", "invalid_input": "مدخل غير صالح", + "invoice": "فاتورة", + "invoice_created": "الفاتورة التي تم إنشاؤها", "invoice_details": "تفاصيل الفاتورة", "is_percentage": "يكون", "last_30_days": "آخر 30 يومًا", @@ -347,6 +351,10 @@ "ledger_error_wrong_app": "يرجى التأكد", "ledger_please_enable_bluetooth": "يرجى تمكين البلوتوث للكشف عن دفتر الأستاذ الخاص بك", "light_theme": "فاتح", + "lightning_invoice_min": "<سيتم تطبيق رسوم الإعداد ${feePercent} ٪ مع ما لا يقل عن ${min} SATs عند تلقي هذه الفاتورة", + "lightning_invoice_min_max": "سيتم تطبيق رسوم إعداد ${feePercent} ٪ مع ما لا يقل عن ${min} SATs لتلقي أكثر من ${max} SATs", + "lightning_invoice_warning": "يجب أن تبقي التطبيق مفتوحًا حتى يتم الانتهاء من الدفع أو تفشل المعاملة", + "lightning_receive_limits": "أرسل أكثر من ${min} SATs وحتى ${max} SATs إلى هذا العنوان. سيتم تطبيق رسوم إعداد ${feePercent} ٪ مع ما لا يقل عن ${fee} SATs عند تلقي هذه الفاتورة. سيؤدي ذلك إلى تحويل أي Bitcoin المستلم إلى Lightning. سيتم تطبيق رسوم على السلسلة. لا يمكن استخدام هذا العنوان إلا مرة واحدة.", "load_more": "تحميل المزيد", "loading_your_wallet": "يتم تحميل محفظتك", "login": "تسجيل الدخول", @@ -360,6 +368,7 @@ "market_place": "منصة التجارة", "matrix_green_dark_theme": "موضوع ماتريكس الأخضر الداكن", "max_amount": "الحد الأقصى: ${value}", + "max_receivable": "ماكس القبض", "max_value": "الحد الأقصى: ${value} ${currency}", "memo": "مذكرة:", "message": "ﺔﻟﺎﺳﺭ", diff --git a/res/values/strings_bg.arb b/res/values/strings_bg.arb index 06132c244..158f56084 100644 --- a/res/values/strings_bg.arb +++ b/res/values/strings_bg.arb @@ -81,6 +81,8 @@ "bitcoin_payments_require_1_confirmation": "Плащанията с Bitcoin изискват потвърждение, което може да отнеме 20 минути или повече. Благодарим за търпението! Ще получите имейл, когато плащането е потвърдено.", "block_remaining": "1 блок останал", "Blocks_remaining": "${status} оставащи блока", + "breez_warning_disruption": "В момента услугата Breez изпитва проблеми. Моля, опитайте отново по-късно.", + "breez_warning_maintenance": "Понастоящем услугата Breez се подлага на поддръжка. Моля, опитайте отново по-късно.", "bluetooth": "Bluetooth", "bright_theme": "Ярко", "bump_fee": "Такса за бум", @@ -337,6 +339,8 @@ "inputs": "Входове", "introducing_cake_pay": "Запознайте се с Cake Pay!", "invalid_input": "Невалиден вход", + "invoice": "Фактура", + "invoice_created": "Създадена фактура", "invoice_details": "IДанни за фактура", "is_percentage": "е", "last_30_days": "Последните 30 дни", @@ -347,6 +351,10 @@ "ledger_error_wrong_app": "Моля, уверете се, че сте отворили правилното приложение на вашата книга", "ledger_please_enable_bluetooth": "Моля, активирайте Bluetooth да открие вашата книга", "light_theme": "Светло", + "lightning_invoice_min": "Такса за настройка от ${feePercent}% с минимум ${min} SATS ще бъде приложена при получаване на тази фактура", + "lightning_invoice_min_max": "Такса за настройка от ${feePercent}% с минимум ${min} SATS ще бъде приложена за получаване на повече от ${max} SATS", + "lightning_invoice_warning": "Трябва да държите приложението отворено, докато плащането не приключи или транзакцията ще се провали", + "lightning_receive_limits": "Изпратете повече от ${min} SATs и до ${max} SATS на този адрес. Такса за настройка от ${feePercent}% с минимум ${fee} SATS ще бъде приложена при получаване на тази фактура. Това ще преобразува всеки получен биткойн в мълния. Ще бъде приложена такса на веригата. Този адрес може да се използва само веднъж.", "load_more": "Зареди още", "loading_your_wallet": "Зареждане на портфейл", "login": "Влизане", @@ -360,6 +368,7 @@ "market_place": "Магазин", "matrix_green_dark_theme": "Зелена тъмна тема Matrix", "max_amount": "Макс: ${value}", + "max_receivable": "Макс вземания", "max_value": "Макс: ${value} ${currency}", "memo": "Мемо:", "message": "Съобщение", diff --git a/res/values/strings_cs.arb b/res/values/strings_cs.arb index 589f89fd7..f3da8ca7f 100644 --- a/res/values/strings_cs.arb +++ b/res/values/strings_cs.arb @@ -81,6 +81,8 @@ "bitcoin_payments_require_1_confirmation": "U plateb Bitcoinem je vyžadováno alespoň 1 potvrzení, což může trvat 20 minut i déle. Děkujeme za vaši trpělivost! Až bude platba potvrzena, budete informováni e-mailem.", "block_remaining": "1 blok zbývající", "Blocks_remaining": "Zbývá ${status} bloků", + "breez_warning_disruption": "Služba Breez v současné době má problémy. Prosím zkuste to znovu později.", + "breez_warning_maintenance": "Služba Breez v současné době prochází údržbou. Prosím zkuste to znovu později.", "bluetooth": "Bluetooth", "bright_theme": "Jasný", "bump_fee": "Bump Fee", @@ -337,6 +339,8 @@ "inputs": "Vstupy", "introducing_cake_pay": "Představujeme Cake Pay!", "invalid_input": "Neplatný vstup", + "invoice": "Faktura", + "invoice_created": "Faktura vytvořená", "invoice_details": "detaily faktury", "is_percentage": "je", "last_30_days": "Posledních 30 dnů", @@ -347,6 +351,10 @@ "ledger_error_wrong_app": "Ujistěte se, že se na své knize otevřete správnou aplikaci", "ledger_please_enable_bluetooth": "Umožněte prosím Bluetooth detekovat vaši knihu", "light_theme": "Světlý", + "lightning_invoice_min": "Po obdržení této faktury se použije poplatek za nastavení ${feePercent}% s minimem ${min} SATS", + "lightning_invoice_min_max": "Poplatek za nastavení ve výši ${feePercent}% s minimem ${min} SATS bude použit pro obdržení více než ${max} Sats", + "lightning_invoice_warning": "Aplikaci musíte udržovat otevřenou, dokud nebude platba dokončena nebo transakce nezklame", + "lightning_receive_limits": "Pošlete více než ${min} sats a až do ${max} sats na tuto adresu. Po obdržení této faktury se použije poplatek za nastavení ${feePercent}% s minimem ${fee} SATS. Tím se převedou jakýkoli přijatý bitcoin na blesk. Bude použito poplatek za řetěz. Tuto adresu lze použít pouze jednou.", "load_more": "Načíst další", "loading_your_wallet": "Načítám peněženku", "login": "Login", @@ -360,6 +368,7 @@ "market_place": "Obchod", "matrix_green_dark_theme": "Tmavé téma Matrix Green", "max_amount": "Max: ${value}", + "max_receivable": "Max pohledávka", "max_value": "Max: ${value} ${currency}", "memo": "Memo:", "message": "Zpráva", diff --git a/res/values/strings_de.arb b/res/values/strings_de.arb index a4b816f5b..4775d3198 100644 --- a/res/values/strings_de.arb +++ b/res/values/strings_de.arb @@ -81,6 +81,8 @@ "bitcoin_payments_require_1_confirmation": "Bitcoin-Zahlungen erfordern 1 Bestätigung, was 20 Minuten oder länger dauern kann. Danke für Ihre Geduld! Sie erhalten eine E-Mail, wenn die Zahlung bestätigt ist.", "block_remaining": "1 Block verbleibend", "Blocks_remaining": "${status} verbleibende Blöcke", + "breez_warning_disruption": "Der Breez -Service hat derzeit Probleme. Bitte versuchen Sie es später erneut.", + "breez_warning_maintenance": "Der Breez -Service wird derzeit gewartet. Bitte versuchen Sie es später erneut.", "bluetooth": "Bluetooth", "bright_theme": "Strahlend hell", "bump_fee": "Beulengebühr", @@ -337,6 +339,8 @@ "inputs": "Eingänge", "introducing_cake_pay": "Einführung von Cake Pay!", "invalid_input": "Ungültige Eingabe", + "invoice": "Rechnung", + "invoice_created": "Rechnung erstellt", "invoice_details": "Rechnungs-Details", "is_percentage": "ist", "last_30_days": "Letzte 30 Tage", @@ -347,6 +351,10 @@ "ledger_error_wrong_app": "Bitte stellen Sie sicher, dass Sie die richtige App auf Ihrem Ledger geöffnet haben", "ledger_please_enable_bluetooth": "Bitte aktivieren Sie Bluetooth um sich mit Ihren Ledger zu verbinden.", "light_theme": "Hell", + "lightning_invoice_min": "Eine Einrichtungsgebühr von ${feePercent}% mit einem Minimum von ${min} SATs wird nach Erhalt dieser Rechnung angewendet", + "lightning_invoice_min_max": "Eine Einrichtungsgebühr von ${feePercent}% mit einem Minimum von ${min} SATs wird für den Empfang von mehr als ${max} SATs angewendet", + "lightning_invoice_warning": "Sie müssen die App offen halten, bis die Zahlung abgeschlossen ist oder die Transaktion fehlschlägt", + "lightning_receive_limits": "Senden Sie mehr als ${min} SATs und bis zu ${max} SATs an diese Adresse. Eine Einrichtungsgebühr von ${feePercent}% mit mindestens ${fee} SATs wird nach Erhalt dieser Rechnung angewendet. Dadurch werden alle empfangenen Bitcoin in Blitz umgewandelt. Es wird eine Gebühr für Ketten angewendet. Diese Adresse kann nur einmal verwendet werden.", "load_more": "Mehr laden", "loading_your_wallet": "Wallet wird geladen", "login": "Einloggen", @@ -360,6 +368,7 @@ "market_place": "Marktplatz", "matrix_green_dark_theme": "Matrix Green Dark Theme", "max_amount": "Max: ${value}", + "max_receivable": "Max. Forderung", "max_value": "Max: ${value} ${currency}", "memo": "Memo:", "message": "Nachricht", diff --git a/res/values/strings_en.arb b/res/values/strings_en.arb index ac82c9e0f..468030f37 100644 --- a/res/values/strings_en.arb +++ b/res/values/strings_en.arb @@ -81,6 +81,8 @@ "bitcoin_payments_require_1_confirmation": "Bitcoin payments require 1 confirmation, which can take 20 minutes or longer. Thanks for your patience! You will be emailed when the payment is confirmed.", "block_remaining": "1 Block Remaining", "Blocks_remaining": "${status} Blocks Remaining", + "breez_warning_disruption": "The breez service is currently experiencing issues. Please try again later.", + "breez_warning_maintenance": "The breez service is currently undergoing maintenance. Please try again later.", "bluetooth": "Bluetooth", "bright_theme": "Bright", "bump_fee": "Bump fee", @@ -337,6 +339,8 @@ "inputs": "Inputs", "introducing_cake_pay": "Introducing Cake Pay!", "invalid_input": "Invalid input", + "invoice": "Invoice", + "invoice_created": "Invoice created", "invoice_details": "Invoice details", "is_percentage": "is", "last_30_days": "Last 30 days", @@ -347,6 +351,10 @@ "ledger_error_wrong_app": "Please make sure you opend the right app on your ledger", "ledger_please_enable_bluetooth": "Please enable Bluetooth to detect your Ledger", "light_theme": "Light", + "lightning_invoice_min": "A setup fee of ${feePercent}% with a minimum of ${min} sats will be applied upon receiving this invoice", + "lightning_invoice_min_max": "A setup fee of ${feePercent}% with a minimum of ${min} sats will be applied for receiving more than ${max} sats", + "lightning_invoice_warning": "You must keep the app open until the payment is completed or the transaction will fail", + "lightning_receive_limits": "Send more than ${min} sats and up to ${max} sats to this address. A setup fee of ${feePercent}% with a minimum of ${fee} sats will be applied upon receiving this invoice. This will convert any received Bitcoin into Lightning. An on-chain fee will be applied. This address can only be used once.", "load_more": "Load more", "loading_your_wallet": "Loading your wallet", "login": "Login", @@ -360,6 +368,7 @@ "market_place": "Marketplace", "matrix_green_dark_theme": "Matrix Green Dark Theme", "max_amount": "Max: ${value}", + "max_receivable": "Max Receivable", "max_value": "Max: ${value} ${currency}", "memo": "Memo:", "message": "Message", diff --git a/res/values/strings_es.arb b/res/values/strings_es.arb index 5fba46830..a8c211375 100644 --- a/res/values/strings_es.arb +++ b/res/values/strings_es.arb @@ -81,6 +81,8 @@ "bitcoin_payments_require_1_confirmation": "Los pagos de Bitcoin requieren 1 confirmación, que puede demorar 20 minutos o más. ¡Gracias por su paciencia! Se le enviará un correo electrónico cuando se confirme el pago.", "block_remaining": "1 bloqueo restante", "Blocks_remaining": "${status} Bloques restantes", + "breez_warning_disruption": "El servicio Breez está experimentando problemas. Por favor, inténtelo de nuevo más tarde.", + "breez_warning_maintenance": "El servicio Breez está actualmente en mantenimiento. Por favor, inténtelo de nuevo más tarde.", "bluetooth": "Bluetooth", "bright_theme": "Brillante", "bump_fee": "Tarifa", @@ -337,6 +339,8 @@ "inputs": "Entradas", "introducing_cake_pay": "¡Presentamos Cake Pay!", "invalid_input": "Entrada inválida", + "invoice": "Factura", + "invoice_created": "Factura creada", "invoice_details": "Detalles de la factura", "is_percentage": "es", "last_30_days": "Últimos 30 días", @@ -347,6 +351,10 @@ "ledger_error_wrong_app": "Por favor, asegúrese de abrir la aplicación correcta en su libro mayor.", "ledger_please_enable_bluetooth": "Habilite Bluetooth para detectar su libro mayor", "light_theme": "Ligera", + "lightning_invoice_min": "Se aplicará una tarifa de configuración de ${feePercent}% con un mínimo de ${min} SATS al recibir esta factura", + "lightning_invoice_min_max": "Se aplicará una tarifa de configuración de ${feePercent}% con un mínimo de ${min} SATS para recibir más de ${max} SATS", + "lightning_invoice_warning": "Debe mantener la aplicación abierta hasta que se complete el pago o la transacción fallará", + "lightning_receive_limits": "Envíe más de ${min} SATS y hasta ${max} SATS a esta dirección. Se aplicará una tarifa de configuración de ${feePercent}% con un mínimo de ${fee} SATS al recibir esta factura. Esto convertirá cualquier bitcoin recibido en rayos. Se aplicará una tarifa en la cadena. Esta dirección solo se puede usar una vez.", "load_more": "Carga más", "loading_your_wallet": "Cargando tu billetera", "login": "Iniciar sesión", @@ -360,6 +368,7 @@ "market_place": "Mercado", "matrix_green_dark_theme": "Matrix verde oscuro tema", "max_amount": "Máx: ${value}", + "max_receivable": "Max por cobrar", "max_value": "Max: ${value} ${currency}", "memo": "Memorándum:", "message": "Mensaje", diff --git a/res/values/strings_fr.arb b/res/values/strings_fr.arb index 7289b7511..231ee63ec 100644 --- a/res/values/strings_fr.arb +++ b/res/values/strings_fr.arb @@ -81,6 +81,8 @@ "bitcoin_payments_require_1_confirmation": "Les paiements Bitcoin nécessitent 1 confirmation, ce qui peut prendre 20 minutes ou plus. Merci pour votre patience ! Vous serez averti par e-mail lorsque le paiement sera confirmé.", "block_remaining": "1 bloc restant", "Blocks_remaining": "Blocs Restants : ${status}", + "breez_warning_disruption": "Le service Breez connaît actuellement des problèmes. Veuillez réessayer plus tard.", + "breez_warning_maintenance": "Le service Breez est actuellement en maintenance. Veuillez réessayer plus tard.", "bluetooth": "Bluetooth", "bright_theme": "Vif", "bump_fee": "Frais de bosse", @@ -337,6 +339,8 @@ "inputs": "Contributions", "introducing_cake_pay": "Présentation de Cake Pay !", "invalid_input": "Entrée invalide", + "invoice": "Facture", + "invoice_created": "Facture créée", "invoice_details": "Détails de la facture", "is_percentage": "est", "last_30_days": "30 derniers jours", @@ -347,6 +351,10 @@ "ledger_error_wrong_app": "Veuillez vous assurer d'ouvrir la bonne application sur votre grand livre", "ledger_please_enable_bluetooth": "Veuillez activer Bluetooth pour détecter votre grand livre", "light_theme": "Clair", + "lightning_invoice_min": "Des frais de configuration de ${feePercent}% avec un minimum de ${min} SATS seront appliqués sur la réception de cette facture", + "lightning_invoice_min_max": "Des frais de configuration de ${feePercent}% avec un minimum de ${min} SATS seront appliqués pour recevoir plus de ${max} SATS", + "lightning_invoice_warning": "Vous devez garder l'application ouverte jusqu'à la fin du paiement ou que la transaction échouera", + "lightning_receive_limits": "Envoyez plus que ${min} SATS et jusqu'à ${max} SATS à cette adresse. Des frais de configuration de ${feePercent}% avec un minimum de ${fee} SATS seront appliqués sur la réception de cette facture. Cela convertira tout bitcoin reçu en foudre. Des frais de chaîne seront appliqués. Cette adresse ne peut être utilisée qu'une seule fois.", "load_more": "Charger plus", "loading_your_wallet": "Chargement de votre portefeuille (wallet)", "login": "Utilisateur", @@ -360,6 +368,7 @@ "market_place": "Place de marché", "matrix_green_dark_theme": "Thème Matrix Green Dark", "max_amount": "Max : ${value}", + "max_receivable": "Max à recevoir", "max_value": "Max: ${value} ${currency}", "memo": "Mémo :", "message": "Message", diff --git a/res/values/strings_ha.arb b/res/values/strings_ha.arb index c96568a76..4d44df012 100644 --- a/res/values/strings_ha.arb +++ b/res/values/strings_ha.arb @@ -81,6 +81,8 @@ "bitcoin_payments_require_1_confirmation": "Akwatin Bitcoin na buɗe 1 sambumbu, da yake za ta samu mintuna 20 ko yawa. Ina kira ga sabuwar lafiya! Zaka sanarwa ta email lokacin da aka samu akwatin samun lambar waya.", "block_remaining": "1 toshe ragowar", "Blocks_remaining": "${status} Katanga ya rage", + "breez_warning_disruption": "A halin yanzu sabis na Breez yana fuskantar al'amura. Da fatan za a sake gwadawa nan gaba.", + "breez_warning_maintenance": "A halin yanzu ana samun sabis na BREEZ a halin yanzu. Da fatan za a sake gwadawa nan gaba.", "bluetooth": "Bluetooth", "bright_theme": "Mai haske", "bump_fee": "Buin", @@ -337,6 +339,8 @@ "inputs": "Abubuwan da ke ciki", "introducing_cake_pay": "Gabatar da Cake Pay!", "invalid_input": "Shigar da ba daidai ba", + "invoice": "Daftari", + "invoice_created": "Invoice ya kirkira", "invoice_details": "Bayanin wadannan", "is_percentage": "shine", "last_30_days": "Kwanaki 30 na ƙarshe", @@ -347,6 +351,10 @@ "ledger_error_wrong_app": "Da fatan za a tabbata kun yi amfani da app ɗin dama akan dillalarku", "ledger_please_enable_bluetooth": "Da fatan za a kunna Bluetooth don gano Ledger ɗinku", "light_theme": "Haske", + "lightning_invoice_min": "Kudin saiti na ${feePercent}% tare da mafi karancin ${min} Ana amfani da shi a kan karbar wannan daftari", + "lightning_invoice_min_max": "Kudin saiti na ${feePercent}% tare da mafi ƙarancin ${min} Ana amfani da shi don karɓar fiye da ${max} Has", + "lightning_invoice_warning": "Dole ne ku kiyaye app din har sai an gama biyan ko ma'amala ba zai gaza ba", + "lightning_receive_limits": "Aika sama da ${min} Haske kuma har zuwa ${max} yana cikin wannan adireshin. Kudin saiti na ${feePercent}% tare da mafi karancin ${fee} Ana amfani da shi a kan karbar wannan daftari. Wannan zai sauya wani karuwa da aka samu cikin walƙiya. Za a yi amfani da kudin kan sarkar. Wannan adireshin za a iya amfani da wannan adireshin sau ɗaya kawai.", "load_more": "Like more", "loading_your_wallet": "Ana loda walat ɗin ku", "login": "Shiga", @@ -360,6 +368,7 @@ "market_place": "Kasuwa", "matrix_green_dark_theme": "Matrix Green Dark Jigo", "max_amount": "Max: ${value}", + "max_receivable": "Max mai karba", "max_value": "Max: ${value} ${currency}", "memo": "Memo:", "message": "Sako", diff --git a/res/values/strings_hi.arb b/res/values/strings_hi.arb index ef740b4d6..2feea1c3b 100644 --- a/res/values/strings_hi.arb +++ b/res/values/strings_hi.arb @@ -81,6 +81,8 @@ "bitcoin_payments_require_1_confirmation": "बिटकॉइन भुगतान के लिए 1 पुष्टिकरण की आवश्यकता होती है, जिसमें 20 मिनट या अधिक समय लग सकता है। आपके धैर्य के लिए धन्यवाद! भुगतान की पुष्टि होने पर आपको ईमेल किया जाएगा।", "block_remaining": "1 ब्लॉक शेष", "Blocks_remaining": "${status} शेष रहते हैं", + "breez_warning_disruption": "ब्रीज़ सेवा वर्तमान में मुद्दों का अनुभव कर रही है। कृपया बाद में पुन: प्रयास करें।", + "breez_warning_maintenance": "ब्रीज़ सेवा वर्तमान में रखरखाव से गुजर रही है। कृपया बाद में पुन: प्रयास करें।", "bluetooth": "ब्लूटूथ", "bright_theme": "उज्ज्वल", "bump_fee": "बम्प फीस", @@ -337,6 +339,8 @@ "inputs": "इनपुट", "introducing_cake_pay": "परिचय Cake Pay!", "invalid_input": "अमान्य निवेश", + "invoice": "चालान", + "invoice_created": "चालान बनाया गया", "invoice_details": "चालान विवरण", "is_percentage": "है", "last_30_days": "पिछले 30 दिन", @@ -347,6 +351,10 @@ "ledger_error_wrong_app": "कृपया सुनिश्चित करें कि आप अपने लेजर पर सही ऐप को खोलते हैं", "ledger_please_enable_bluetooth": "कृपया अपने बहीखाने का पता लगाने के लिए ब्लूटूथ को सक्षम करें", "light_theme": "रोशनी", + "lightning_invoice_min": "इस चालान को प्राप्त करने पर न्यूनतम ${min} SATs के साथ ${feePercent}% का सेटअप शुल्क लागू किया जाएगा", + "lightning_invoice_min_max": "न्यूनतम ${min} SATs के साथ ${feePercent}% का सेटअप शुल्क ${max} SATs से अधिक प्राप्त करने के लिए लागू किया जाएगा", + "lightning_invoice_warning": "भुगतान पूरा होने तक आपको ऐप को खुला रखना चाहिए या लेनदेन विफल हो जाएगा", + "lightning_receive_limits": "इस पते पर ${min} से अधिक और ${max} से अधिक सीटें भेजें। यह चालान प्राप्त होने पर न्यूनतम ${fee} सैट के साथ ${feePercent}% का सेटअप शुल्क लागू किया जाएगा। यह किसी भी प्राप्त बिटकॉइन को लाइटनिंग में बदल देगा। ऑन-चेन शुल्क लागू किया जाएगा. इस पते का उपयोग केवल एक बार किया जा सकता है.", "load_more": "और लोड करें", "loading_your_wallet": "अपना बटुआ लोड कर रहा है", "login": "लॉग इन करें", @@ -360,6 +368,7 @@ "market_place": "मार्केटप्लेस", "matrix_green_dark_theme": "मैट्रिक्स ग्रीन डार्क थीम", "max_amount": "अधिकतम: ${value}", + "max_receivable": "अधिकतम प्राप्य", "max_value": "मैक्स: ${value} ${currency}", "memo": "ज्ञापन:", "message": "संदेश", diff --git a/res/values/strings_hr.arb b/res/values/strings_hr.arb index 295a01165..08f437c31 100644 --- a/res/values/strings_hr.arb +++ b/res/values/strings_hr.arb @@ -81,6 +81,8 @@ "bitcoin_payments_require_1_confirmation": "Bitcoin plaćanja zahtijevaju 1 potvrdu, što može potrajati 20 minuta ili dulje. Hvala na Vašem strpljenju! Dobit ćete e-poruku kada plaćanje bude potvrđeno.", "block_remaining": "Preostalo 1 blok", "Blocks_remaining": "${status} preostalih blokova", + "breez_warning_disruption": "Breez usluga trenutno ima problema. Molimo pokušajte ponovo kasnije.", + "breez_warning_maintenance": "Breez usluga trenutno je u održavanju. Molimo pokušajte ponovo kasnije.", "bluetooth": "Bluetooth", "bright_theme": "Jarka", "bump_fee": "Naplata", @@ -337,6 +339,8 @@ "inputs": "Unosi", "introducing_cake_pay": "Predstavljamo Cake Pay!", "invalid_input": "Pogrešan unos", + "invoice": "Dostavnica", + "invoice_created": "Račun stvoren", "invoice_details": "Podaci o fakturi", "is_percentage": "je", "last_30_days": "Zadnjih 30 dana", @@ -347,6 +351,10 @@ "ledger_error_wrong_app": "Obavezno obavezno otvorite pravu aplikaciju na knjizi", "ledger_please_enable_bluetooth": "Omogućite Bluetooth da otkrije svoju knjigu", "light_theme": "Svijetla", + "lightning_invoice_min": "Naknada za postavljanje ${feePercent}% s najmanje ${min} SAT -om primijenit će se nakon primanja ove fakture", + "lightning_invoice_min_max": "Naknada za postavljanje ${feePercent}% s najmanje ${min} SAT -om bit će primijenjena za primanje više od ${max} SATS", + "lightning_invoice_warning": "Aplikaciju morate držati otvorenom dok se plaćanje ne dovrši ili transakcija neće uspjeti", + "lightning_receive_limits": "Na ovu adresu pošaljite više od ${min} SAT -a i do ${max} SAT -a. Naknada za postavljanje ${feePercent}% s najmanje ${fee} SAT -om primijenit će se nakon primanja ove fakture. To će pretvoriti bilo koji primljeni bitcoin u munje. Primjenjivat će se naknada na lancu. Ova se adresa može koristiti samo jednom.", "load_more": "Učitaj više", "loading_your_wallet": "Novčanik se učitava", "login": "Prijava", @@ -360,6 +368,7 @@ "market_place": "Tržnica", "matrix_green_dark_theme": "Matrix Green Dark Theme", "max_amount": "Maksimum: ${value}", + "max_receivable": "Maksimalno potraživanje", "max_value": "Maks.: ${value} ${currency}", "memo": "Memo:", "message": "Poruka", diff --git a/res/values/strings_id.arb b/res/values/strings_id.arb index 8b28e928f..d89344707 100644 --- a/res/values/strings_id.arb +++ b/res/values/strings_id.arb @@ -81,6 +81,8 @@ "bitcoin_payments_require_1_confirmation": "Pembayaran Bitcoin memerlukan 1 konfirmasi, yang bisa memakan waktu 20 menit atau lebih. Terima kasih atas kesabaran Anda! Anda akan diemail saat pembayaran dikonfirmasi.", "block_remaining": "1 blok tersisa", "Blocks_remaining": "${status} Blok Tersisa", + "breez_warning_disruption": "Layanan Breez saat ini mengalami masalah. Silakan coba lagi nanti.", + "breez_warning_maintenance": "Layanan Breez saat ini sedang menjalani pemeliharaan. Silakan coba lagi nanti.", "bluetooth": "Bluetooth", "bright_theme": "Cerah", "bump_fee": "Biaya benjolan", @@ -337,6 +339,8 @@ "inputs": "Input", "introducing_cake_pay": "Perkenalkan Cake Pay!", "invalid_input": "Masukan tidak valid", + "invoice": "Faktur", + "invoice_created": "Faktur dibuat", "invoice_details": "Detail faktur", "is_percentage": "adalah", "last_30_days": "30 hari terakhir", @@ -347,6 +351,10 @@ "ledger_error_wrong_app": "Pastikan Anda membuka aplikasi yang tepat di buku besar Anda", "ledger_please_enable_bluetooth": "Harap aktifkan Bluetooth untuk mendeteksi buku besar Anda", "light_theme": "Terang", + "lightning_invoice_min": "Biaya Pengaturan ${feePercent}% dengan minimum ${min} SAT akan diterapkan setelah menerima faktur ini", + "lightning_invoice_min_max": "Biaya pengaturan ${feePercent}% dengan minimum ${min} SAT akan diterapkan untuk menerima lebih dari ${max} SATS", + "lightning_invoice_warning": "Anda harus menjaga aplikasi tetap terbuka sampai pembayaran selesai atau transaksi akan gagal", + "lightning_receive_limits": "Kirim lebih dari ${min} sat dan hingga ${max} SAT ke alamat ini. Biaya pengaturan ${feePercent}% dengan minimum ${fee} SAT akan diterapkan setelah menerima faktur ini. Ini akan mengubah bitcoin yang diterima menjadi kilat. Biaya rantai akan diterapkan. Alamat ini hanya dapat digunakan sekali.", "load_more": "Muat lebih banyak", "loading_your_wallet": "Memuat dompet Anda", "login": "Masuk", @@ -360,6 +368,7 @@ "market_place": "Pasar", "matrix_green_dark_theme": "Tema Matrix Green Dark", "max_amount": "Max: ${value}", + "max_receivable": "Piutang maksimum", "max_value": "Max: ${value} ${currency}", "memo": "Memo:", "message": "Pesan", diff --git a/res/values/strings_it.arb b/res/values/strings_it.arb index b1bd7cd6d..384245d48 100644 --- a/res/values/strings_it.arb +++ b/res/values/strings_it.arb @@ -81,6 +81,8 @@ "bitcoin_payments_require_1_confirmation": "I pagamenti in bitcoin richiedono 1 conferma, che può richiedere 20 minuti o più. Grazie per la vostra pazienza! Riceverai un'e-mail quando il pagamento sarà confermato.", "block_remaining": "1 blocco rimanente", "Blocks_remaining": "${status} Blocchi Rimanenti", + "breez_warning_disruption": "Il servizio Breez sta attualmente riscontrando problemi. Per favore riprova più tardi.", + "breez_warning_maintenance": "Il servizio Breez è attualmente in fase di manutenzione. Per favore riprova più tardi.", "bluetooth": "Bluetooth", "bright_theme": "Colorato", "bump_fee": "Commissione per bump", @@ -338,6 +340,8 @@ "inputs": "Input", "introducing_cake_pay": "Presentazione di Cake Pay!", "invalid_input": "Inserimento non valido", + "invoice": "Fattura", + "invoice_created": "Fattura creata", "invoice_details": "Dettagli della fattura", "is_percentage": "è", "last_30_days": "Ultimi 30 giorni", @@ -348,6 +352,10 @@ "ledger_error_wrong_app": "Assicurati di aprire l'app giusta sul libro mastro", "ledger_please_enable_bluetooth": "Si prega di consentire al Bluetooth di rilevare il libro mastro", "light_theme": "Bianco", + "lightning_invoice_min": "Verrà applicata una commissione di configurazione di ${feePercent}% con un minimo di ${min} SATS al momento della ricezione di questa fattura", + "lightning_invoice_min_max": "Verrà applicata una commissione di configurazione di ${feePercent}% con un minimo di ${min} SATS per ricevere più di ${max} SAT", + "lightning_invoice_warning": "È necessario tenere aperta l'app fino al completamento del pagamento o la transazione fallirà", + "lightning_receive_limits": "Invia più di ${min} SAT e fino a ${max} SAT a questo indirizzo. Una commissione di configurazione di ${feePercent}% con un minimo di ${fee} SAT sarà applicata al momento della ricezione di questa fattura. Ciò convertirà qualsiasi bitcoin ricevuto in fulmini. Verrà applicata una tassa sulla catena. Questo indirizzo può essere usato solo una volta.", "load_more": "Carica di più", "loading_your_wallet": "Caricamento portafoglio", "login": "Accedi", @@ -361,6 +369,7 @@ "market_place": "Mercato", "matrix_green_dark_theme": "Tema Matrix verde scuro", "max_amount": "Max: ${value}", + "max_receivable": "Ricevibile massimo", "max_value": "Max: ${value} ${currency}", "memo": "Memo:", "message": "Messaggio", diff --git a/res/values/strings_ja.arb b/res/values/strings_ja.arb index 5803b86d5..e2e8bebe1 100644 --- a/res/values/strings_ja.arb +++ b/res/values/strings_ja.arb @@ -81,6 +81,8 @@ "bitcoin_payments_require_1_confirmation": "ビットコインの支払いには 1 回の確認が必要で、これには 20 分以上かかる場合があります。お待ち頂きまして、ありがとうございます!支払いが確認されると、メールが送信されます。", "block_remaining": "残り1ブロック", "Blocks_remaining": "${status} 残りのブロック", + "breez_warning_disruption": "Breezサービスは現在、問題が発生しています。後でもう一度やり直してください。", + "breez_warning_maintenance": "Breezサービスは現在、メンテナンスを受けています。後でもう一度やり直してください。", "bluetooth": "ブルートゥース", "bright_theme": "明るい", "bump_fee": "バンプ料金", @@ -338,6 +340,8 @@ "inputs": "入力", "introducing_cake_pay": "序章Cake Pay!", "invalid_input": "無効入力", + "invoice": "請求書", + "invoice_created": "作成された請求書", "invoice_details": "請求の詳細", "is_percentage": "is", "last_30_days": "過去30日", @@ -348,6 +352,10 @@ "ledger_error_wrong_app": "元帳に適切なアプリを開始するようにしてください", "ledger_please_enable_bluetooth": "Bluetoothが元帳を検出できるようにしてください", "light_theme": "光", + "lightning_invoice_min": "この請求書を受け取ると、最低${min} SATの${feePercent}%のセットアップ料金が適用されます", + "lightning_invoice_min_max": "最低${min} SATの${feePercent}%のセットアップ料金は、${max} SAT以上を受け取るために適用されます", + "lightning_invoice_warning": "支払いが完了するか、トランザクションが失敗するまでアプリを開いたままにしておく必要があります", + "lightning_receive_limits": "このアドレスに ${min} 個以上、最大 ${max} 個のSat を送信します。 この請求書の受け取り時に、${feePercent}% のセットアップ料金 (最低 ${fee} サット) が適用されます。 これにより、受け取ったビットコインがライトニングに変換されます。 オンチェーン料金が適用されます。 このアドレスは 1 回のみ使用できます。", "load_more": "もっと読み込む", "loading_your_wallet": "ウォレットをロードしています", "login": "ログイン", @@ -361,6 +369,7 @@ "market_place": "Marketplace", "matrix_green_dark_theme": "マトリックスグリーンダークテーマ", "max_amount": "最大: ${value}", + "max_receivable": "最大売掛金", "max_value": "マックス: ${value} ${currency}", "memo": "メモ:", "message": "メッセージ", diff --git a/res/values/strings_ko.arb b/res/values/strings_ko.arb index 874ec2c27..e2f376765 100644 --- a/res/values/strings_ko.arb +++ b/res/values/strings_ko.arb @@ -81,6 +81,8 @@ "bitcoin_payments_require_1_confirmation": "비트코인 결제는 1번의 확인이 필요하며 20분 이상이 소요될 수 있습니다. 기다려 주셔서 감사합니다! 결제가 확인되면 이메일이 전송됩니다.", "block_remaining": "남은 블록 1 개", "Blocks_remaining": "${status} 남은 블록", + "breez_warning_disruption": "Breez 서비스는 현재 문제가 발생하고 있습니다. 나중에 다시 시도 해주십시오.", + "breez_warning_maintenance": "Breez 서비스는 현재 유지 보수를 받고 있습니다. 나중에 다시 시도 해주십시오.", "bluetooth": "블루투스", "bright_theme": "선명한", "bump_fee": "범프 요금", @@ -337,6 +339,8 @@ "inputs": "입력", "introducing_cake_pay": "소개 Cake Pay!", "invalid_input": "잘못된 입력", + "invoice": "송장", + "invoice_created": "송장 생성", "invoice_details": "인보이스 세부정보", "is_percentage": "이다", "last_30_days": "지난 30일", @@ -347,6 +351,10 @@ "ledger_error_wrong_app": "원장에서 올바른 앱을 반대하는지 확인하십시오.", "ledger_please_enable_bluetooth": "Bluetooth가 원장을 감지 할 수 있도록하십시오", "light_theme": "빛", + "lightning_invoice_min": "이 송장을 받으면 최소 ${min} SAT가있는 ${feePercent}%의 설정 수수료가 적용됩니다.", + "lightning_invoice_min_max": "최소 ${min} SAT의 ${feePercent}%의 설정 수수료는 ${max} sats 이상을 받기 위해 적용됩니다.", + "lightning_invoice_warning": "결제가 완료되거나 거래가 실패 할 때까지 앱을 열어야합니다.", + "lightning_receive_limits": "${min} sats 이상을 보내고이 주소로 ${max} SATS를 보내십시오. 이 송장을 받으면 최소 ${fee} SAT의 ${feePercent}%의 설정 수수료가 적용됩니다. 이것은 수신 된 비트 코인을 번개로 변환합니다. 온쇄 수수료가 적용됩니다. 이 주소는 한 번만 사용할 수 있습니다.", "load_more": "더로드하십시오", "loading_your_wallet": "지갑 넣기", "login": "로그인", @@ -360,6 +368,7 @@ "market_place": "마켓플레이스", "matrix_green_dark_theme": "매트릭스 그린 다크 테마", "max_amount": "최대: ${value}", + "max_receivable": "MAX 미수금", "max_value": "맥스: ${value} ${currency}", "memo": "메모:", "message": "메시지", diff --git a/res/values/strings_my.arb b/res/values/strings_my.arb index 890362fcd..cec506ad9 100644 --- a/res/values/strings_my.arb +++ b/res/values/strings_my.arb @@ -81,6 +81,8 @@ "bitcoin_payments_require_1_confirmation": "Bitcoin ငွေပေးချေမှုများသည် မိနစ် 20 သို့မဟုတ် ထို့ထက်ပိုကြာနိုင်သည် 1 အတည်ပြုချက် လိုအပ်သည်။ မင်းရဲ့စိတ်ရှည်မှုအတွက် ကျေးဇူးတင်ပါတယ်။ ငွေပေးချေမှုကို အတည်ပြုပြီးသောအခါ သင့်ထံ အီးမေးလ်ပို့ပါမည်။", "block_remaining": "ကျန်ရှိနေသေးသော block", "Blocks_remaining": "${status} ဘလောက်များ ကျန်နေပါသည်။", + "breez_warning_disruption": "Breez 0 န်ဆောင်မှုသည်ပြ issues နာများကိုကြုံတွေ့နေရသည်။ ကျေးဇူးပြု. နောက်မှထပ်ကြိုးစားပါ", + "breez_warning_maintenance": "Breez 0 န်ဆောင်မှုသည်လက်ရှိတွင်ပြုပြင်ထိန်းသိမ်းမှုကိုခံနေရသည်။ ကျေးဇူးပြု. နောက်မှထပ်ကြိုးစားပါ", "bluetooth": "ဘလူးတုသ်", "bright_theme": "တောက်ပ", "bump_fee": "ဝင်ငွေ", @@ -337,6 +339,8 @@ "inputs": "သွင်းငေှ", "introducing_cake_pay": "Cake Pay ကို မိတ်ဆက်ခြင်း။", "invalid_input": "ထည့်သွင်းမှု မမှန်ကန်ပါ။", + "invoice": "ဝယ်ကုန်စာရင်း", + "invoice_created": "ငွေတောင်းခံလွှာကိုဖန်တီးခဲ့သည်", "invoice_details": "ပြေစာအသေးစိတ်", "is_percentage": "သည်", "last_30_days": "လွန်ခဲ့သော ရက် 30", @@ -347,6 +351,10 @@ "ledger_error_wrong_app": "ကျေးဇူးပြု. သင့်လက်ျာအက်ပ်ကိုသင်၏ Ledger တွင်ဖွင့်ရန်သေချာစေပါ", "ledger_please_enable_bluetooth": "သင်၏ Ledger ကိုရှာဖွေရန် Bluetooth ကိုဖွင့်ပါ", "light_theme": "အလင်း", + "lightning_invoice_min": "ဤငွေတောင်းခံလွှာကိုလက်ခံရရှိသည့်အခါ ${feePercent}% ဖြင့် ${min}% ဖြင့် Setup အခကြေးငွေပေးဆောင်ရမည်", + "lightning_invoice_min_max": "${max} sats ထက်ပို၍ လက်ခံရရှိရန်အတွက် အနည်းဆုံး ${min} sats နှင့် အနည်းဆုံး ${feePercent}% ဖြင့် တပ်ဆင်ခ ကောက်ခံပါမည်", + "lightning_invoice_warning": "ငွေပေးချေမှုပြီးဆုံးသည်အထိ app ကိုဖွင့်ထားရမည်သို့မဟုတ်ငွေပေးငွေယူပျက်ကွက်လိမ့်မည်", + "lightning_receive_limits": "${min} sats နှင့် ${max} sats အထိ ဤလိပ်စာသို့ ပို့ပါ။ ဤငွေတောင်းခံလွှာကို လက်ခံရရှိသည့်အခါ အနည်းဆုံး ${fee} ကြိမ်ဖြင့် ${feePercent}% ဖြင့် တပ်ဆင်ခကို ကောက်ခံပါမည်။ ၎င်းသည် လက်ခံရရှိထားသော Bitcoin ကို Lightning အဖြစ်သို့ ပြောင်းလဲပေးမည်ဖြစ်သည်။ ကွင်းဆက်ကြေးကောက်ခံပါမည်။ ဤလိပ်စာကို တစ်ကြိမ်သာ အသုံးပြုနိုင်သည်။", "load_more": "ပိုပြီး load", "loading_your_wallet": "သင့်ပိုက်ဆံအိတ်ကို ဖွင့်နေသည်။", "login": "လော့ဂ်အင်", @@ -360,6 +368,7 @@ "market_place": "ဈေး", "matrix_green_dark_theme": "Matrix Green Dark အပြင်အဆင်", "max_amount": "အများဆုံး- ${value}", + "max_receivable": "max ကိုလက်ခံရရှိ", "max_value": "အများဆုံး- ${value} ${currency}", "memo": "မှတ်စုတို:", "message": "မက်ဆေ့ချ်", diff --git a/res/values/strings_nl.arb b/res/values/strings_nl.arb index 25b7f6b3a..a990cc7bc 100644 --- a/res/values/strings_nl.arb +++ b/res/values/strings_nl.arb @@ -81,6 +81,8 @@ "bitcoin_payments_require_1_confirmation": "Bitcoin-betalingen vereisen 1 bevestiging, wat 20 minuten of langer kan duren. Dank voor uw geduld! U ontvangt een e-mail wanneer de betaling is bevestigd.", "block_remaining": "1 blok resterend", "Blocks_remaining": "${status} Resterende blokken", + "breez_warning_disruption": "De Breez -service ondervindt momenteel problemen. Probeer het later opnieuw.", + "breez_warning_maintenance": "De Breez -service ondergaat momenteel onderhoud. Probeer het later opnieuw.", "bluetooth": "Bluetooth", "bright_theme": "Helder", "bump_fee": "Bult fee", @@ -337,6 +339,8 @@ "inputs": "Invoer", "introducing_cake_pay": "Introductie van Cake Pay!", "invalid_input": "Ongeldige invoer", + "invoice": "Factuur", + "invoice_created": "Factuur gemaakt", "invoice_details": "Factuurgegevens", "is_percentage": "is", "last_30_days": "Laatste 30 dagen", @@ -347,6 +351,10 @@ "ledger_error_wrong_app": "Zorg ervoor dat u de juiste app op uw grootboek opent", "ledger_please_enable_bluetooth": "Schakel Bluetooth in staat om uw grootboek te detecteren", "light_theme": "Licht", + "lightning_invoice_min": "Een installatiekosten van ${feePercent}% met een minimum van ${min} SAT's wordt toegepast bij het ontvangen van deze factuur", + "lightning_invoice_min_max": "Een installatiekosten van ${feePercent}% met een minimum van ${min} SAT's zal worden toegepast voor het ontvangen van meer dan ${max} sats", + "lightning_invoice_warning": "U moet de app open houden totdat de betaling is voltooid of de transactie mislukt", + "lightning_receive_limits": "Stuur meer dan ${min} sats en tot ${max} sats naar dit adres. Een installatiekosten van ${feePercent}% met een minimum van ${fee} SAT's wordt toegepast bij het ontvangen van deze factuur. Dit zal elke ontvangen bitcoin omzetten in bliksem. Een on-chain-vergoeding wordt toegepast. Dit adres kan slechts eenmaal worden gebruikt.", "load_more": "Meer laden", "loading_your_wallet": "Uw portemonnee laden", "login": "Log in", @@ -360,6 +368,7 @@ "market_place": "Marktplaats", "matrix_green_dark_theme": "Matrix groen donker thema", "max_amount": "Max: ${value}", + "max_receivable": "Max -vordering", "max_value": "Max: ${value} ${currency}", "memo": "Memo:", "message": "Bericht", diff --git a/res/values/strings_pl.arb b/res/values/strings_pl.arb index 1567a0841..01c202feb 100644 --- a/res/values/strings_pl.arb +++ b/res/values/strings_pl.arb @@ -81,6 +81,8 @@ "bitcoin_payments_require_1_confirmation": "Płatności Bitcoin wymagają 1 potwierdzenia, co może zająć 20 minut lub dłużej. Dziękuję za cierpliwość! Otrzymasz wiadomość e-mail, gdy płatność zostanie potwierdzona.", "block_remaining": "1 blok pozostałym", "Blocks_remaining": "Pozostało ${status} bloków", + "breez_warning_disruption": "Usługa Breeza ma obecnie problemy. Spróbuj ponownie później.", + "breez_warning_maintenance": "Usługa Breeza przechodzi obecnie konserwację. Spróbuj ponownie później.", "bluetooth": "Bluetooth", "bright_theme": "Biały", "bump_fee": "Opłata za nierówność", @@ -337,6 +339,8 @@ "inputs": "Wejścia", "introducing_cake_pay": "Przedstawiamy Cake Pay!", "invalid_input": "Nieprawidłowe dane wejściowe", + "invoice": "Faktura", + "invoice_created": "Utworzona faktura", "invoice_details": "Dane do faktury", "is_percentage": "jest", "last_30_days": "Ostatnie 30 dni", @@ -347,6 +351,10 @@ "ledger_error_wrong_app": "Upewnij się, że opisz odpowiednią aplikację na swojej księdze", "ledger_please_enable_bluetooth": "Włącz Bluetooth wykrywanie księgi", "light_theme": "Jasny", + "lightning_invoice_min": "Opłata konfiguracyjna w wysokości ${feePercent}% z minimum ${min} SAT zostanie zastosowana po otrzymaniu tej faktury", + "lightning_invoice_min_max": "Opłata konfiguracyjna w wysokości ${feePercent}% z minimum ${min} SATS zostanie zastosowana do otrzymania więcej niż ${max} SATS", + "lightning_invoice_warning": "Musisz utrzymać aplikację otwartą do momentu zakończenia płatności lub transakcji ulegnie awarii", + "lightning_receive_limits": "Wyślij więcej niż ${min} SAT i do ${max} SATS na ten adres. Opłata konfiguracyjna w wysokości ${feePercent}% przy minimum ${fee} SAT zostanie zastosowana po otrzymaniu tej faktury. To przekonwertuje każdy otrzymany bitcoin w błyskawicę. Zostanie zastosowana opłata w łańcuchu. Ten adres można użyć tylko raz.", "load_more": "Załaduj więcej", "loading_your_wallet": "Ładowanie portfela", "login": "Login", @@ -360,6 +368,7 @@ "market_place": "Rynek", "matrix_green_dark_theme": "Matrix Zielony ciemny motyw", "max_amount": "Max: ${value}", + "max_receivable": "MAX NECTIVET", "max_value": "Max: ${value} ${currency}", "memo": "Notatka:", "message": "Wiadomość", diff --git a/res/values/strings_pt.arb b/res/values/strings_pt.arb index 272c0862e..88156bcf9 100644 --- a/res/values/strings_pt.arb +++ b/res/values/strings_pt.arb @@ -81,6 +81,8 @@ "bitcoin_payments_require_1_confirmation": "Os pagamentos em Bitcoin exigem 1 confirmação, o que pode levar 20 minutos ou mais. Obrigado pela sua paciência! Você receberá um e-mail quando o pagamento for confirmado.", "block_remaining": "1 bloco restante", "Blocks_remaining": "${status} blocos restantes", + "breez_warning_disruption": "O serviço Breez está atualmente enfrentando problemas. Por favor, tente novamente mais tarde.", + "breez_warning_maintenance": "O serviço Breez está atualmente em manutenção. Por favor, tente novamente mais tarde.", "bluetooth": "Bluetooth", "bright_theme": "Brilhante", "bump_fee": "Taxa de aumento", @@ -337,6 +339,8 @@ "inputs": "Entradas", "introducing_cake_pay": "Apresentando o Cake Pay!", "invalid_input": "Entrada inválida", + "invoice": "Fatura", + "invoice_created": "Fatura criada", "invoice_details": "Detalhes da fatura", "is_percentage": "é", "last_30_days": "Últimos 30 dias", @@ -347,6 +351,10 @@ "ledger_error_wrong_app": "Por favor, certifique -se de optar pelo aplicativo certo no seu livro", "ledger_please_enable_bluetooth": "Ative o Bluetooth para detectar seu livro", "light_theme": "Luz", + "lightning_invoice_min": "Uma taxa de configuração ${feePercent}% com um mínimo de ${min} SATS será aplicada ao receber esta fatura", + "lightning_invoice_min_max": "Uma taxa de configuração ${feePercent}% com um mínimo de ${min} SATS será aplicada para receber mais de ${max} SATs", + "lightning_invoice_warning": "Você deve manter o aplicativo aberto até que o pagamento seja concluído ou a transação falhará", + "lightning_receive_limits": "Envie mais do que ${min} SATs e até ${max} SATS para este endereço. Uma taxa de configuração ${feePercent}% com um mínimo de ${fee} SATS será aplicada ao receber esta fatura. Isso converterá qualquer bitcoin recebido em raios. Uma taxa na cadeia será aplicada. Este endereço só pode ser usado uma vez.", "load_more": "Carregue mais", "loading_your_wallet": "Abrindo sua carteira", "login": "Login", @@ -361,6 +369,7 @@ "market_place": "Mercado", "matrix_green_dark_theme": "Tema escuro verde matrix", "max_amount": "Máx.: ${valor}", + "max_receivable": "Max a receber", "max_value": "Máx: ${value} ${currency}", "memo": "Memorando:", "message": "Mensagem", diff --git a/res/values/strings_ru.arb b/res/values/strings_ru.arb index a8943db97..ef4bda8f5 100644 --- a/res/values/strings_ru.arb +++ b/res/values/strings_ru.arb @@ -81,6 +81,8 @@ "bitcoin_payments_require_1_confirmation": "Биткойн-платежи требуют 1 подтверждения, что может занять 20 минут или дольше. Спасибо тебе за твое терпение! Вы получите электронное письмо, когда платеж будет подтвержден.", "block_remaining": "1 Блок остался", "Blocks_remaining": "${status} Осталось блоков", + "breez_warning_disruption": "Служба Breez в настоящее время испытывает проблемы. Пожалуйста, повторите попытку позже.", + "breez_warning_maintenance": "Служба Breez в настоящее время проходит обслуживание. Пожалуйста, повторите попытку позже.", "bluetooth": "Bluetooth", "bright_theme": "Яркая", "bump_fee": "Повысить комиссию", @@ -337,6 +339,8 @@ "inputs": "Входы", "introducing_cake_pay": "Представляем Cake Pay!", "invalid_input": "Неверный Ввод", + "invoice": "Счет", + "invoice_created": "Счет -фактор создан", "invoice_details": "Детали счета", "is_percentage": "есть", "last_30_days": "Последние 30 дней", @@ -347,6 +351,10 @@ "ledger_error_wrong_app": "Пожалуйста, убедитесь, что вы предлагаете правильное приложение в своей бухгалтерской книге", "ledger_please_enable_bluetooth": "Пожалуйста, включите Bluetooth обнаружить вашу бухгалтерскую книгу", "light_theme": "Светлая", + "lightning_invoice_min": "Плата за настройку ${feePercent}% с минимумом ${min} SAT будет применено при получении этого счета.", + "lightning_invoice_min_max": "Плата за установку ${feePercent}% с минимумом ${min} SAT будет применяться для получения более ${max} SATS", + "lightning_invoice_warning": "Вы должны держать приложение открытым до завершения платежа, или транзакция не пройдет", + "lightning_receive_limits": "Отправьте больше, чем ${min} SAT и до ${max} SAT на этот адрес. Плата за настройку ${feePercent}% с минимумом ${fee} SAT будет применяться при получении этого счета. Это преобразует любой полученный биткойн в молнию. Будет применяться плата за цепь. Этот адрес можно использовать только один раз.", "load_more": "Загрузи больше", "loading_your_wallet": "Загрузка кошелька", "login": "Логин", @@ -360,6 +368,7 @@ "market_place": "Торговая площадка", "matrix_green_dark_theme": "Матрица Зеленая Темная Тема", "max_amount": "Макс.: ${value}", + "max_receivable": "Максимальная дебиторская задолженность", "max_value": "Макс: ${value} ${currency}", "memo": "Памятка:", "message": "Сообщение", diff --git a/res/values/strings_th.arb b/res/values/strings_th.arb index c4d282761..8141439ce 100644 --- a/res/values/strings_th.arb +++ b/res/values/strings_th.arb @@ -81,6 +81,8 @@ "bitcoin_payments_require_1_confirmation": "การชำระเงินด้วย Bitcoin ต้องการการยืนยัน 1 ครั้ง ซึ่งอาจใช้เวลา 20 นาทีหรือนานกว่านั้น ขอบคุณสำหรับความอดทนของคุณ! คุณจะได้รับอีเมลเมื่อการชำระเงินได้รับการยืนยัน", "block_remaining": "เหลือ 1 บล็อก", "Blocks_remaining": "${status} บล็อกที่เหลืออยู่", + "breez_warning_disruption": "บริการ Breez กำลังประสบปัญหา กรุณาลองใหม่อีกครั้งในภายหลัง.", + "breez_warning_maintenance": "บริการ Breez กำลังอยู่ระหว่างการบำรุงรักษา กรุณาลองใหม่อีกครั้งในภายหลัง.", "bluetooth": "บลูทู ธ", "bright_theme": "สดใส", "bump_fee": "ค่าธรรมเนียมชน", @@ -337,6 +339,8 @@ "inputs": "อินพุต", "introducing_cake_pay": "ยินดีต้อนรับสู่ Cake Pay!", "invalid_input": "อินพุตไม่ถูกต้อง", + "invoice": "ใบแจ้งหนี้", + "invoice_created": "สร้างใบแจ้งหนี้", "invoice_details": "รายละเอียดใบแจ้งหนี้", "is_percentage": "เป็น", "last_30_days": "30 วันล่าสุด", @@ -347,6 +351,10 @@ "ledger_error_wrong_app": "โปรดตรวจสอบให้แน่ใจว่าคุณเปิดแอพที่เหมาะสมในบัญชีแยกประเภทของคุณ", "ledger_please_enable_bluetooth": "โปรดเปิดใช้งานบลูทู ธ ในการตรวจจับบัญชีแยกประเภทของคุณ", "light_theme": "สว่าง", + "lightning_invoice_min": "ค่าธรรมเนียมการตั้งค่า ${feePercent}% ที่มีค่าน้อยที่สุด ${min} SAT จะถูกนำไปใช้เมื่อได้รับใบแจ้งหนี้นี้", + "lightning_invoice_min_max": "ค่าธรรมเนียมการตั้งค่า ${feePercent}% ที่มีค่าน้อยที่สุด ${min} SAT จะถูกนำไปใช้เพื่อรับมากกว่า ${max} SATS", + "lightning_invoice_warning": "คุณต้องเปิดแอปไว้จนกว่าการชำระเงินจะเสร็จสมบูรณ์หรือการทำธุรกรรมจะล้มเหลว", + "lightning_receive_limits": "ส่งมากกว่า ${min} sats และสูงสุด ${max} sats ไปยังที่อยู่นี้ ค่าธรรมเนียมการตั้งค่า ${feePercent}% ที่มีค่าน้อยที่สุด ${fee} SAT จะถูกนำไปใช้เมื่อได้รับใบแจ้งหนี้นี้ สิ่งนี้จะแปลง bitcoin ที่ได้รับเป็นฟ้าผ่า จะมีการใช้ค่าธรรมเนียมในห่วงโซ่ ที่อยู่นี้สามารถใช้ได้เพียงครั้งเดียว", "load_more": "โหลดมากขึ้น", "loading_your_wallet": "กำลังโหลดกระเป๋าของคุณ", "login": "เข้าสู่ระบบ", @@ -360,6 +368,7 @@ "market_place": "ตลาดพื้นที่", "matrix_green_dark_theme": "ธีมเมทริกซ์สีเขียวเข้ม", "max_amount": "จำนวนสูงสุด: ${value}", + "max_receivable": "ลูกหนี้สูงสุด", "max_value": "ขั้นสูง: ${value} ${currency}", "memo": "หมายเหตุ:", "message": "ข้อความ", diff --git a/res/values/strings_tl.arb b/res/values/strings_tl.arb index 775ab4c3d..efc4959b1 100644 --- a/res/values/strings_tl.arb +++ b/res/values/strings_tl.arb @@ -81,6 +81,8 @@ "bitcoin_payments_require_1_confirmation": "Ang mga pagbabayad sa Bitcoin ay nangangailangan ng 1 kumpirmasyon, na maaaring tumagal ng 20 minuto o mas mahaba. Salamat sa iyong pasensya! Mag -email ka kapag nakumpirma ang pagbabayad.", "block_remaining": "1 bloke ang natitira", "Blocks_remaining": "Ang natitirang ${status} ay natitira", + "breez_warning_disruption": "Ang serbisyo ng Breez ay kasalukuyang nakakaranas ng mga isyu. Subukang muli mamaya.", + "breez_warning_maintenance": "Ang serbisyo ng Breez ay kasalukuyang sumasailalim sa pagpapanatili. Subukang muli mamaya.", "bluetooth": "Bluetooth", "bright_theme": "Maliwanag", "bump_fee": "Bayad sa paga", @@ -337,6 +339,8 @@ "inputs": "Mga input", "introducing_cake_pay": "Ipinakikilala ang cake pay!", "invalid_input": "Di -wastong input", + "invoice": "Invoice", + "invoice_created": "Nilikha ang Invoice", "invoice_details": "Mga detalye ng invoice", "is_percentage": "ay", "last_30_days": "Huling 30 araw", @@ -347,6 +351,10 @@ "ledger_error_wrong_app": "Mangyaring tiyaking pinipili mo ang tamang app sa iyong ledger", "ledger_please_enable_bluetooth": "Mangyaring paganahin ang Bluetooth upang makita ang iyong ledger", "light_theme": "Ilaw", + "lightning_invoice_min": "Isang bayad sa pag -setup ng ${feePercent}% na may isang minimum na ${min} sats ay ilalapat sa pagtanggap ng invoice na ito", + "lightning_invoice_min_max": "Isang bayad sa pag -setup ng ${feePercent}% na may isang minimum na ${min} sats ay ilalapat para sa pagtanggap ng higit sa ${max} sats", + "lightning_invoice_warning": "Dapat mong panatilihing bukas ang app hanggang makumpleto ang pagbabayad o mabibigo ang transaksyon", + "lightning_receive_limits": "Magpadala ng higit sa ${min} sats at hanggang sa ${max} sats sa address na ito. Ang isang setup fee ng ${feePercent}% na may isang minimum na ${fee} sats ay ilalapat sa pagtanggap ng invoice na ito. Ito ay i -convert ang anumang natanggap na Bitcoin sa kidlat. Ang isang on-chain fee ay ilalapat. Ang address na ito ay maaari lamang magamit nang isang beses.", "load_more": "Mag -load pa", "loading_your_wallet": "Naglo -load ng iyong pitaka", "login": "Mag log in", @@ -360,6 +368,7 @@ "market_place": "Marketplace", "matrix_green_dark_theme": "Matrix Green Madilim na Tema", "max_amount": "Max: ${value}", + "max_receivable": "MAX RECEIVABLE", "max_value": "Max: ${value} ${currency}", "memo": "Memo:", "message": "Mensahe", diff --git a/res/values/strings_tr.arb b/res/values/strings_tr.arb index 239a1aa2e..5e9d3338d 100644 --- a/res/values/strings_tr.arb +++ b/res/values/strings_tr.arb @@ -81,6 +81,8 @@ "bitcoin_payments_require_1_confirmation": "Bitcoin ödemeleri, 20 dakika veya daha uzun sürebilen 1 onay gerektirir. Sabrınız için teşekkürler! Ödeme onaylandığında e-posta ile bilgilendirileceksiniz.", "block_remaining": "Kalan 1 blok", "Blocks_remaining": "${status} Blok Kaldı", + "breez_warning_disruption": "Breez hizmeti şu anda sorunlar yaşıyor. Lütfen daha sonra tekrar deneyiniz.", + "breez_warning_maintenance": "Breez hizmeti şu anda bakım görmektedir. Lütfen daha sonra tekrar deneyiniz.", "bluetooth": "Bluetooth", "bright_theme": "Parlak", "bump_fee": "Çarpma ücreti", @@ -337,6 +339,8 @@ "inputs": "Girişler", "introducing_cake_pay": "Cake Pay ile tanışın!", "invalid_input": "Geçersiz Giriş", + "invoice": "Fatura", + "invoice_created": "Fatura Oluşturuldu", "invoice_details": "fatura detayları", "is_percentage": "is", "last_30_days": "Son 30 gün", @@ -347,6 +351,10 @@ "ledger_error_wrong_app": "Lütfen defterinizde doğru uygulamayı açtığınızdan emin olun", "ledger_please_enable_bluetooth": "Defterinizi algılamak için lütfen Bluetooth'u etkinleştirin", "light_theme": "Aydınlık", + "lightning_invoice_min": "Bu faturayı aldıktan sonra en az ${min} sats ile% ${feePercent} kurulum ücreti uygulanacak", + "lightning_invoice_min_max": "${max} SATS'den fazlasını almak için minimum ${min} SAT ile% ${feePercent} kurulum ücreti uygulanacak", + "lightning_invoice_warning": "Ödeme tamamlanana veya işlem başarısız olana kadar uygulamayı açık tutmalısınız", + "lightning_receive_limits": "Bu adrese ${min} SATS'den fazla ve ${max} SAT'ları gönderin. Bu faturayı aldıktan sonra en az ${fee} SAT ile% ${feePercent} kurulum ücreti uygulanacaktır. Bu, alınan bitcoin'i yıldırım haline getirecektir. Zincir üstü bir ücret uygulanacaktır. Bu adres yalnızca bir kez kullanılabilir.", "load_more": "Daha fazla yükle", "loading_your_wallet": "Cüzdanın yükleniyor", "login": "Login", @@ -360,6 +368,7 @@ "market_place": "Pazar Alanı", "matrix_green_dark_theme": "Matrix Yeşil Koyu Tema", "max_amount": "Maks: ${value}", + "max_receivable": "Maksimum alacak", "max_value": "En fazla: ${value} ${currency}", "memo": "Memo:", "message": "İleti", diff --git a/res/values/strings_uk.arb b/res/values/strings_uk.arb index 3a45752d6..25c98160b 100644 --- a/res/values/strings_uk.arb +++ b/res/values/strings_uk.arb @@ -81,6 +81,8 @@ "bitcoin_payments_require_1_confirmation": "Платежі Bitcoin потребують 1 підтвердження, яке може зайняти 20 хвилин або більше. Дякую за Ваше терпіння! Ви отримаєте електронний лист, коли платіж буде підтверджено.", "block_remaining": "1 блок, що залишився", "Blocks_remaining": "${status} Залишилось блоків", + "breez_warning_disruption": "Наразі послуга Breez переживає проблеми. Будь-ласка спробуйте пізніше.", + "breez_warning_maintenance": "Наразі послуга Breez проходить технічне обслуговування. Будь-ласка спробуйте пізніше.", "bluetooth": "Блюдот", "bright_theme": "Яскрава", "bump_fee": "Підвищити комісію", @@ -337,6 +339,8 @@ "inputs": "Вхoди", "introducing_cake_pay": "Представляємо Cake Pay!", "invalid_input": "Неправильні дані", + "invoice": "Рахунок -фактура", + "invoice_created": "Створений рахунок -фактури", "invoice_details": "Реквізити рахунку-фактури", "is_percentage": "є", "last_30_days": "Останні 30 днів", @@ -347,6 +351,10 @@ "ledger_error_wrong_app": "Будь ласка, переконайтеся, що ви відкриваєте потрібну програму на своїй книзі", "ledger_please_enable_bluetooth": "Будь ласка, ввімкніть Bluetooth виявити свою книгу", "light_theme": "Світла", + "lightning_invoice_min": "Плата за налаштування ${feePercent}% з мінімум ${min} САТ буде застосовуватися після отримання цього рахунку -фактури", + "lightning_invoice_min_max": "Плата за налаштування ${feePercent}% з мінімум ${min} SATS буде застосовуватися для отримання більше ${max} SATS", + "lightning_invoice_warning": "Ви повинні тримати додаток відкритим до тих пір", + "lightning_receive_limits": "Надішліть більше ${min} SAT та до ${max} SATS на цю адресу. Плата за налаштування в розмірі ${feePercent}% з мінімум ${fee} САТ буде застосовуватися після отримання цього рахунку -фактури. Це перетворить будь -який отриманий біткойн у блискавку. Буде застосовуватися плата за ланцюг. Цю адресу можна використовувати лише один раз.", "load_more": "Завантажити ще", "loading_your_wallet": "Завантаження гаманця", "login": "Логін", @@ -360,6 +368,7 @@ "market_place": "Ринок", "matrix_green_dark_theme": "Зелена темна тема Matrix", "max_amount": "Макс: ${value}", + "max_receivable": "Максимальна дебіторська заборгованість", "max_value": "Макс: ${value} ${currency}", "memo": "Пам’ятка:", "message": "повідомлення", diff --git a/res/values/strings_ur.arb b/res/values/strings_ur.arb index 2ab1f927b..8a29f5075 100644 --- a/res/values/strings_ur.arb +++ b/res/values/strings_ur.arb @@ -81,6 +81,8 @@ "bitcoin_payments_require_1_confirmation": "بٹ کوائن کی ادائیگی میں 1 تصدیق کی ضرورت ہوتی ہے ، جس میں 20 منٹ یا اس سے زیادہ وقت لگ سکتا ہے۔ آپ کے صبر کا شکریہ! ادائیگی کی تصدیق ہونے پر آپ کو ای میل کیا جائے گا۔", "block_remaining": "1 بلاک باقی", "Blocks_remaining": "${status} بلاکس باقی ہیں۔", + "breez_warning_disruption": "بریز سروس فی الحال مسائل کا سامنا کر رہی ہے۔ براہ کرم کچھ دیر بعد کوشش کریں.", + "breez_warning_maintenance": "بریز سروس اس وقت دیکھ بھال کر رہی ہے۔ براہ کرم کچھ دیر بعد کوشش کریں.", "bluetooth": "بلوٹوتھ", "bright_theme": "روشن", "bump_fee": "بمپ فیس", @@ -337,6 +339,8 @@ "inputs": "آدانوں", "introducing_cake_pay": "Cake پے کا تعارف!", "invalid_input": "غلط ان پٹ", + "invoice": "انوائس", + "invoice_created": "انوائس تخلیق کیا گیا", "invoice_details": "رسید کی تفصیلات", "is_percentage": "ہے", "last_30_days": "آخری 30 دن", @@ -347,6 +351,10 @@ "ledger_error_wrong_app": "براہ کرم یقینی بنائیں کہ آپ اپنے لیجر پر صحیح ایپ کو کھولتے ہیں", "ledger_please_enable_bluetooth": "براہ کرم بلوٹوتھ کو اپنے لیجر کا پتہ لگانے کے لئے اہل بنائیں", "light_theme": "روشنی", + "lightning_invoice_min": "یہ انوائس موصول ہونے پر کم از کم ${min} سیٹوں کے ساتھ ${feePercent}% کی سیٹ اپ فیس لاگو کی جائے گی۔", + "lightning_invoice_min_max": "${feePercent} ٪ کی ایک سیٹ اپ فیس ${min} ایس اے ٹی ایس کے ساتھ ${max} ایس اے ٹی ایس سے زیادہ وصول کرنے کے لئے درخواست دی جائے گی", + "lightning_invoice_warning": "ادائیگی مکمل ہونے تک آپ کو ایپ کو کھلا رکھنا چاہئے یا لین دین ناکام ہوجائے گا", + "lightning_receive_limits": "اس پتے پر ${min} SATS اور ${max} SATS سے زیادہ بھیجیں۔ اس انوائس کو حاصل کرنے پر کم از کم ${fee} ایس اے ٹی کے ساتھ ${feePercent} ٪ کی سیٹ اپ فیس کا اطلاق ہوگا۔ یہ کسی بھی موصولہ بٹ کوائن کو بجلی میں تبدیل کردے گا۔ آن چین فیس کا اطلاق ہوگا۔ یہ پتہ صرف ایک بار استعمال کیا جاسکتا ہے۔", "load_more": "مزید لوڈ کریں", "loading_your_wallet": "آپ کا بٹوہ لوڈ ہو رہا ہے۔", "login": "لاگ ان کریں", @@ -360,6 +368,7 @@ "market_place": "بازار", "matrix_green_dark_theme": "میٹرکس گرین ڈارک تھیم", "max_amount": "زیادہ سے زیادہ: ${value}", + "max_receivable": "زیادہ سے زیادہ قابل وصول", "max_value": "زیادہ سے زیادہ: ${value} ${currency}", "memo": "میمو:", "message": "ﻡﺎﻐﯿﭘ", diff --git a/res/values/strings_yo.arb b/res/values/strings_yo.arb index f9751f9f1..605545d31 100644 --- a/res/values/strings_yo.arb +++ b/res/values/strings_yo.arb @@ -81,6 +81,8 @@ "bitcoin_payments_require_1_confirmation": "Àwọn àránṣẹ́ Bitcoin nílò ìjẹ́rìísí kan. Ó lè lo ìṣéjú ogun tàbí ìṣéjú jù. A dúpẹ́ fún sùúrù yín! Ẹ máa gba ímeèlì t'ó bá jẹ́rìísí àránṣẹ́ náà.", "block_remaining": "1 bulọọki to ku", "Blocks_remaining": "Àkójọpọ̀ ${status} kikù", + "breez_warning_disruption": "Iṣẹ Breez n jẹ iriri iriri lọwọlọwọ. Jọwọ gbiyanju lẹẹkansi nigbamii.", + "breez_warning_maintenance": "Iṣẹ Breez n ṣe itọju Lọwọlọwọ lọwọlọwọ. Jọwọ gbiyanju lẹẹkansi nigbamii.", "bluetooth": "Bluetooth", "bright_theme": "Funfun", "bump_fee": "Ọya ija", @@ -338,6 +340,8 @@ "inputs": "Igbewọle", "introducing_cake_pay": "Ẹ bá Cake Pay!", "invalid_input": "Iṣawọle ti ko tọ", + "invoice": "Iye-owo ọja", + "invoice_created": "Risiti da", "invoice_details": "Iru awọn ẹya ọrọ", "is_percentage": "jẹ́", "last_30_days": "Ọ̀jọ̀ mọ́gbọ̀n tó kọjà", @@ -348,6 +352,10 @@ "ledger_error_wrong_app": "Jọwọ rii daju pe iwọ yoo sọ app ti o tọ loju omi rẹ", "ledger_please_enable_bluetooth": "Jọwọ jẹ ki Bluetooth lati rii iṣupọ rẹ", "light_theme": "Funfun bí eérú", + "lightning_invoice_min": "A oto kan ọya ti ${feePercent}% pẹlu o kere ju ${min} Awọn Sahs yoo lo lori gbigba iwe-ẹkọ yii", + "lightning_invoice_min_max": "Owo oso kan ti ${feePercent}% pẹlu o kere ju ${min} Awọn ororo yoo lo diẹ sii ju ${max} Satu", + "lightning_invoice_warning": "O gbọdọ pa app naa titi ti isanwo ti pari tabi idunadura naa yoo kuna", + "lightning_receive_limits": "Firanṣẹ diẹ sii ju ${min} osù ati si to ${max} O si só si adirẹsi yii. Owo oso kan ti ${feePercent}% pẹlu o kere ju ${fee} Sahs yoo wa ni lilo lori gbigba ikowe yii. Eyi yoo ṣe iyipada eyikeyi ti o gba bitcoin sinu monomono. Owo lori-pq yoo lo. Adirẹsi yii le ṣee lo lẹẹkan.", "load_more": "Ẹru diẹ sii", "loading_your_wallet": "A ń ṣí àpamọ́wọ́ yín", "login": "Orúkọ", @@ -361,6 +369,7 @@ "market_place": "Ọjà", "matrix_green_dark_theme": "Matrix Green Dark Akori", "max_amount": "kò tóbi ju: ${value}", + "max_receivable": "Max gba", "max_value": "kò gbọ́dọ̀ tóbi ju ${value} ${currency}", "memo": "Àkọsílẹ̀:", "message": "Ifiranṣẹ", diff --git a/res/values/strings_zh.arb b/res/values/strings_zh.arb index f3b8ee176..88a1e7a5b 100644 --- a/res/values/strings_zh.arb +++ b/res/values/strings_zh.arb @@ -81,6 +81,8 @@ "bitcoin_payments_require_1_confirmation": "比特币支付需要 1 次确认,这可能需要 20 分钟或更长时间。谢谢你的耐心!确认付款后,您将收到电子邮件。", "block_remaining": "剩下1个块", "Blocks_remaining": "${status} 剩余的块", + "breez_warning_disruption": "Breez服务目前正在遇到问题。请稍后再试。", + "breez_warning_maintenance": "Breez服务目前正在进行维护。请稍后再试。", "bluetooth": "蓝牙", "bright_theme": "明亮", "bump_fee": "撞费", @@ -337,6 +339,8 @@ "inputs": "输入", "introducing_cake_pay": "介绍 Cake Pay!", "invalid_input": "输入无效", + "invoice": "发票", + "invoice_created": "创建了发票", "invoice_details": "发票明细", "is_percentage": "是", "last_30_days": "过去 30 天", @@ -347,6 +351,10 @@ "ledger_error_wrong_app": "请确保您在分类帐中操作正确的应用程序", "ledger_please_enable_bluetooth": "请启用蓝牙来检测您的分类帐", "light_theme": "艳丽", + "lightning_invoice_min": "收到此发票时,将收取至少${min} SAT的设置费为${feePercent}%", + "lightning_invoice_min_max": "最少${min} SAT的设置费为${feePercent}%,将用于接收超过${max} SATS", + "lightning_invoice_warning": "您必须保持应用程序打开,直到付款完成或交易将失败", + "lightning_receive_limits": "将超过 ${min} 个 sat 且最多 ${max} 个 sat 发送到此地址。 收到此发票后,将收取 ${feePercent}% 的安装费,至少 ${fee} sat。 这会将任何收到的比特币转换为闪电网络。 将收取链上费用。 该地址只能使用一次。", "load_more": "装载更多", "loading_your_wallet": "加载您的钱包", "login": "登录", @@ -360,6 +368,7 @@ "market_place": "市场", "matrix_green_dark_theme": "矩阵绿暗主题", "max_amount": "最大值: ${value}", + "max_receivable": "最大应收", "max_value": "最大: ${value} ${currency}", "memo": "备忘录:", "message": "信息", diff --git a/scripts/android/pubspec_gen.sh b/scripts/android/pubspec_gen.sh index 468f548f3..5a3d9eb80 100755 --- a/scripts/android/pubspec_gen.sh +++ b/scripts/android/pubspec_gen.sh @@ -10,7 +10,7 @@ case $APP_ANDROID_TYPE in CONFIG_ARGS="--monero" ;; $CAKEWALLET) - CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero" + CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --lightning" if [ "$CW_WITH_HAVEN" = true ];then CONFIG_ARGS="$CONFIG_ARGS --haven" fi diff --git a/scripts/ios/app_config.sh b/scripts/ios/app_config.sh index cae9688e8..7c615a90c 100755 --- a/scripts/ios/app_config.sh +++ b/scripts/ios/app_config.sh @@ -30,7 +30,7 @@ case $APP_IOS_TYPE in CONFIG_ARGS="--monero" ;; $CAKEWALLET) - CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero" + CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --lightning" if [ "$CW_WITH_HAVEN" = true ];then CONFIG_ARGS="$CONFIG_ARGS --haven" fi diff --git a/scripts/macos/app_config.sh b/scripts/macos/app_config.sh index b8785a9be..54739e653 100755 --- a/scripts/macos/app_config.sh +++ b/scripts/macos/app_config.sh @@ -35,7 +35,7 @@ case $APP_MACOS_TYPE in $MONERO_COM) CONFIG_ARGS="--monero";; $CAKEWALLET) - CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero";; #--haven + CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --lightning";; #--haven esac cp -rf pubspec_description.yaml pubspec.yaml diff --git a/tool/append_translation.dart b/tool/append_translation.dart index ac114bf3c..8ba07cc40 100644 --- a/tool/append_translation.dart +++ b/tool/append_translation.dart @@ -3,13 +3,14 @@ import 'utils/translation/translation_constants.dart'; import 'utils/translation/translation_utils.dart'; void main(List args) async { - if (args.length != 2) { + if (args.length < 2 || args.length > 3) { throw Exception( - 'Insufficient arguments!\n\nTry to run `./append_translation.dart greetings "Hello World!"`'); + 'Insufficient arguments!\n\nTry to run `./append_translation.dart greetings "Hello World!" [--force]`'); } - final name = args.first; - final text = args.last; + final name = args[0]; + final text = args[1]; + final force = args.length == 3 && args[2] == '--force'; print('Appending "$name": "$text"'); @@ -18,7 +19,7 @@ void main(List args) async { final fileName = getArbFileName(lang); final translation = await getTranslation(text, lang); - appendStringToArbFile(fileName, name, translation); + appendStringToArbFile(fileName, name, translation, force: force); } print('Alphabetizing all files...'); diff --git a/tool/configure.dart b/tool/configure.dart index fcfd676dc..0879e3833 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -7,6 +7,7 @@ const ethereumOutputPath = 'lib/ethereum/ethereum.dart'; const bitcoinCashOutputPath = 'lib/bitcoin_cash/bitcoin_cash.dart'; const nanoOutputPath = 'lib/nano/nano.dart'; const polygonOutputPath = 'lib/polygon/polygon.dart'; +const lightningOutputPath = 'lib/lightning/lightning.dart'; const solanaOutputPath = 'lib/solana/solana.dart'; const tronOutputPath = 'lib/tron/tron.dart'; const wowneroOutputPath = 'lib/wownero/wownero.dart'; @@ -25,6 +26,7 @@ Future main(List args) async { final hasNano = args.contains('${prefix}nano'); final hasBanano = args.contains('${prefix}banano'); final hasPolygon = args.contains('${prefix}polygon'); + final hasLightning = args.contains('${prefix}lightning'); final hasSolana = args.contains('${prefix}solana'); final hasTron = args.contains('${prefix}tron'); final hasWownero = args.contains('${prefix}wownero'); @@ -37,6 +39,7 @@ Future main(List args) async { await generateBitcoinCash(hasBitcoinCash); await generateNano(hasNano); await generatePolygon(hasPolygon); + await generateLightning(hasLightning); await generateSolana(hasSolana); await generateTron(hasTron); await generateWownero(hasWownero); @@ -52,6 +55,7 @@ Future main(List args) async { hasBitcoinCash: hasBitcoinCash, hasFlutterSecureStorage: !excludeFlutterSecureStorage, hasPolygon: hasPolygon, + hasLightning: hasLightning, hasSolana: hasSolana, hasTron: hasTron, hasWownero: hasWownero, @@ -65,6 +69,7 @@ Future main(List args) async { hasBanano: hasBanano, hasBitcoinCash: hasBitcoinCash, hasPolygon: hasPolygon, + hasLightning: hasLightning, hasSolana: hasSolana, hasTron: hasTron, hasWownero: hasWownero, @@ -989,6 +994,73 @@ abstract class Polygon { await outputFile.writeAsString(output); } +Future generateLightning(bool hasImplementation) async { + final outputFile = File(lightningOutputPath); + const lightningCommonHeaders = """ +import 'package:cake_wallet/bitcoin/bitcoin.dart'; +import 'package:cw_core/unspent_transaction_output.dart'; +import 'package:cw_core/wallet_credentials.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/transaction_priority.dart'; +import 'package:cw_core/output_info.dart'; +import 'package:cw_core/unspent_coins_info.dart'; +import 'package:cw_core/wallet_service.dart'; +import 'package:cw_core/receive_page_option.dart'; +import 'package:cw_core/crypto_amount_format.dart'; +import 'package:cake_wallet/view_model/send/output.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:hive/hive.dart'; +import 'package:mobx/mobx.dart'; +import 'package:intl/intl.dart'; +"""; + const lightningCWHeaders = """ +import 'package:cw_bitcoin/electrum_wallet.dart'; +import 'package:cw_bitcoin/bitcoin_unspent.dart'; +import 'package:cw_bitcoin/bitcoin_mnemonic.dart'; +import 'package:cw_bitcoin/bitcoin_transaction_priority.dart'; +import 'package:cw_bitcoin/bitcoin_wallet_creation_credentials.dart'; +import 'package:cw_bitcoin/bitcoin_amount_format.dart'; +import 'package:cw_bitcoin/bitcoin_address_record.dart'; +import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart'; +import 'package:cw_lightning/lightning_wallet_service.dart'; +import 'package:cw_lightning/lightning_receive_page_option.dart'; +"""; + const lightningCwPart = "part 'cw_lightning.dart';"; + const lightningContent = """ +abstract class Lightning { + String formatterLightningAmountToString({required int amount}); + double formatterLightningAmountToDouble({required int amount}); + int formatterStringDoubleToLightningAmount(String amount); + WalletService createLightningWalletService( + Box walletInfoSource, Box unspentCoinSource); + List getLightningReceivePageOptions(); + String satsToLightningString(int sats); + ReceivePageOption getOptionInvoice(); + ReceivePageOption getOptionOnchain(); + String bitcoinAmountToLightningString({required int amount}); + int bitcoinAmountToLightningAmount({required int amount}); + double bitcoinDoubleToLightningDouble({required double amount}); + double lightningDoubleToBitcoinDouble({required double amount}); +} + """; + + const lightningEmptyDefinition = 'Lightning? lightning;\n'; + const lightningCWDefinition = 'Lightning? lightning = CWLightning();\n'; + + final output = '$lightningCommonHeaders\n' + + (hasImplementation ? '$lightningCWHeaders\n' : '\n') + + (hasImplementation ? '$lightningCwPart\n\n' : '\n') + + (hasImplementation ? lightningCWDefinition : lightningEmptyDefinition) + + '\n' + + lightningContent; + + if (outputFile.existsSync()) { + await outputFile.delete(); + } + + await outputFile.writeAsString(output); +} + Future generateBitcoinCash(bool hasImplementation) async { final outputFile = File(bitcoinCashOutputPath); const bitcoinCashCommonHeaders = """ @@ -1355,6 +1427,7 @@ Future generatePubspec({ required bool hasSolana, required bool hasTron, required bool hasWownero, + required bool hasLightning, }) async { const cwCore = """ cw_core: @@ -1411,7 +1484,7 @@ Future generatePubspec({ const cwEVM = """ cw_evm: path: ./cw_evm - """; + """; const cwTron = """ cw_tron: path: ./cw_tron @@ -1420,6 +1493,10 @@ Future generatePubspec({ cw_wownero: path: ./cw_wownero """; + const cwLightning = """ + cw_lightning: + path: ./cw_lightning + """; final inputFile = File(pubspecOutputPath); final inputText = await inputFile.readAsString(); final inputLines = inputText.split('\n'); @@ -1481,6 +1558,10 @@ Future generatePubspec({ output += '\n$cwWownero'; } + if (hasLightning) { + output += '\n$cwLightning'; + } + final outputLines = output.split('\n'); inputLines.insertAll(dependenciesIndex + 1, outputLines); final outputContent = inputLines.join('\n'); @@ -1505,6 +1586,7 @@ Future generateWalletTypes({ required bool hasSolana, required bool hasTron, required bool hasWownero, + required bool hasLightning, }) async { final walletTypesFile = File(walletTypesPath); @@ -1524,6 +1606,10 @@ Future generateWalletTypes({ outputContent += '\tWalletType.bitcoin,\n'; } + if (hasLightning) { + outputContent += '\tWalletType.lightning,\n'; + } + if (hasEthereum) { outputContent += '\tWalletType.ethereum,\n'; } diff --git a/tool/generate_secrets_config.dart b/tool/generate_secrets_config.dart index cab41ca69..58d5b08e7 100644 --- a/tool/generate_secrets_config.dart +++ b/tool/generate_secrets_config.dart @@ -5,6 +5,7 @@ import 'utils/utils.dart'; const configPath = 'tool/.secrets-config.json'; const evmChainsConfigPath = 'tool/.evm-secrets-config.json'; +const bitcoinConfigPath = 'tool/.bitcoin-secrets-config.json'; const solanaConfigPath = 'tool/.solana-secrets-config.json'; const nanoConfigPath = 'tool/.nano-secrets-config.json'; const tronConfigPath = 'tool/.tron-secrets-config.json'; @@ -21,6 +22,7 @@ Future generateSecretsConfig(List args) async { final configFile = File(configPath); final evmChainsConfigFile = File(evmChainsConfigPath); + final bitcoinConfigFile = File(bitcoinConfigPath); final solanaConfigFile = File(solanaConfigPath); final nanoConfigFile = File(nanoConfigPath); final tronConfigFile = File(tronConfigPath); @@ -66,6 +68,17 @@ Future generateSecretsConfig(List args) async { await evmChainsConfigFile.writeAsString(secretsJson); secrets.clear(); + // btc / lightning: + SecretKey.bitcoinSecrets.forEach((sec) { + if (secrets[sec.name] != null) { + return; + } + secrets[sec.name] = sec.generate(); + }); + secretsJson = JsonEncoder.withIndent(' ').convert(secrets); + await bitcoinConfigFile.writeAsString(secretsJson); + secrets.clear(); + // solana: SecretKey.solanaSecrets.forEach((sec) { if (secrets[sec.name] != null) { diff --git a/tool/utils/secret_key.dart b/tool/utils/secret_key.dart index 7261478a6..943fec549 100644 --- a/tool/utils/secret_key.dart +++ b/tool/utils/secret_key.dart @@ -51,6 +51,12 @@ class SecretKey { SecretKey('moralisApiKey', () => ''), ]; + static final bitcoinSecrets = [ + SecretKey('breezApiKey', () => ''), + SecretKey('greenlightCert', () => ''), + SecretKey('greenlightKey', () => ''), + ]; + static final solanaSecrets = [ SecretKey('ankrApiKey', () => ''), ]; diff --git a/tool/utils/translation/arb_file_utils.dart b/tool/utils/translation/arb_file_utils.dart index b54dab423..284e64ddc 100644 --- a/tool/utils/translation/arb_file_utils.dart +++ b/tool/utils/translation/arb_file_utils.dart @@ -1,12 +1,12 @@ import 'dart:convert'; import 'dart:io'; -void appendStringToArbFile(String fileName, String name, String text) { +void appendStringToArbFile(String fileName, String name, String text, {bool force = false}) { final file = File(fileName); final arbObj = readArbFile(file); - if (arbObj.containsKey(name)) { - print("String $name already exists in $fileName!"); + if (arbObj.containsKey(name) && !force) { + print("String $name already exists in $fileName! Use --force to overwrite!"); return; }