Merge branch 'breez' of https://github.com/cake-tech/cake_wallet into wownero_breez

 Conflicts:
	configure_cake_wallet.sh
	cw_core/lib/amount_converter.dart
	cw_core/lib/wallet_type.dart
	cw_monero/lib/monero_wallet.dart
	lib/view_model/unspent_coins/unspent_coins_list_view_model.dart
	scripts/android/pubspec_gen.sh
	scripts/ios/app_config.sh
	scripts/macos/app_config.sh
	tool/configure.dart
This commit is contained in:
OmarHatem 2024-06-30 05:11:38 +03:00
commit 90508b8726
133 changed files with 4714 additions and 459 deletions

View file

@ -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

1
.gitignore vendored
View file

@ -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

View file

@ -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')

View file

@ -9,6 +9,8 @@
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<!--bibo01 : hardware option-->
@ -69,6 +71,9 @@
<data android:scheme="bitcoin" />
<data android:scheme="bitcoin-wallet" />
<data android:scheme="bitcoin_wallet" />
<data android:scheme="lightning" />
<data android:scheme="lightning-wallet" />
<data android:scheme="lightning_wallet" />
<data android:scheme="monero" />
<data android:scheme="monero-wallet" />
<data android:scheme="monero_wallet" />
@ -115,6 +120,24 @@
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
<!-- service to handle messaging events -->
<!-- commented out until we decide what to do -->
<!-- <service
android:name=".NotificationFcmService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service> -->
<!-- service to handle the foreground service -->
<service
android:name=".NotificationForegroundService"
android:foregroundServiceType="shortService"
android:exported="false"
android:stopWithTask="false">
</service>
</application>
<queries>

View file

@ -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"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

View file

@ -0,0 +1,10 @@
<?xml version="1.0" standalone="no"?>
<!-- Generator: Adobe Fireworks 10, Export SVG Extension by Aaron Beall (http://fireworks.abeall.com) . Version: 0.6.1 -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg id="bitcoin_lightning_icon.fw-Page%201" viewBox="0 0 280 280" style="background-color:#ffffff00" version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve"
x="0px" y="0px" width="280px" height="280px"
>
<path id="Ellipse" d="M 7 140.5 C 7 66.769 66.769 7 140.5 7 C 214.231 7 274 66.769 274 140.5 C 274 214.231 214.231 274 140.5 274 C 66.769 274 7 214.231 7 140.5 Z" fill="#f7931a"/>
<path d="M 161.1943 51.5 C 153.2349 72.1607 145.2756 94.4107 135.7244 116.6607 C 135.7244 116.6607 135.7244 119.8393 138.9081 119.8393 L 204.1747 119.8393 C 204.1747 119.8393 204.1747 121.4286 205.7667 123.0179 L 110.2545 229.5 C 108.6626 227.9107 108.6626 226.3214 108.6626 224.7321 L 142.0919 153.2143 L 142.0919 146.8571 L 75.2333 146.8571 L 75.2333 140.5 L 156.4187 51.5 L 161.1943 51.5 Z" fill="#ffffff"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
assets/images/warning.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View file

@ -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
./model_generator.sh

View file

@ -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);
}

View file

@ -73,6 +73,7 @@ abstract class ElectrumWalletBase
getAccountHDWallet(currency, networkType, seedBytes, xpub, walletInfo.derivationInfo),
syncStatus = NotConnectedSyncStatus(),
_password = password,
_mnemonic = mnemonic,
_feeRates = <int>[],
_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<void> close() async {
Future<void> close({bool? switchingToSameWalletType}) async {
try {
await electrumClient.close();
} catch (_) {}

View file

@ -33,15 +33,16 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
Map<String, int>? initialRegularAddressIndex,
Map<String, int>? 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,

View file

@ -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

View file

@ -35,15 +35,16 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
Map<String, int>? initialRegularAddressIndex,
Map<String, int>? 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,

View file

@ -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

View file

@ -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));
}

View file

@ -103,6 +103,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
CryptoCurrency.kaspa,
CryptoCurrency.digibyte,
CryptoCurrency.usdtSol,
CryptoCurrency.btcln,
CryptoCurrency.usdcTrc20,
CryptoCurrency.tbtc,
CryptoCurrency.wow,
@ -190,7 +191,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> 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<int> with Serializable<int> 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<int, CryptoCurrency> _rawCurrencyMap =
[...all, ...havenCurrencies].fold<Map<int, CryptoCurrency>>(<int, CryptoCurrency>{}, (acc, item) {
acc.addAll({item.raw: item});

View file

@ -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:

View file

@ -80,7 +80,7 @@ abstract class WalletBase<BalanceType extends Balance, HistoryType extends Trans
Future<void> rescan({required int height});
void close();
void close({bool? switchingToSameWalletType});
Future<void> changePassword(String password);

View file

@ -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:

View file

@ -191,7 +191,7 @@ abstract class EVMChainWalletBase
}
@override
void close() {
void close({bool? switchingToSameWalletType}) {
_client.stop();
_transactionsUpdateTimer?.cancel();
}

View file

@ -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:

View file

@ -106,7 +106,7 @@ abstract class HavenWalletBase
Future<void>? updateBalance() => null;
@override
void close() {
void close({bool? switchingToSameWalletType}) {
_listener?.stop();
_onAccountChangeReaction?.reaction.dispose();
_autoSaveTimer?.cancel();

View file

@ -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

74
cw_lightning/.gitignore vendored Normal file
View file

@ -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

10
cw_lightning/.metadata Normal file
View file

@ -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

View file

@ -0,0 +1,3 @@
## [0.0.1] - TODO: Add release date.
* TODO: Describe initial release.

1
cw_lightning/LICENSE Normal file
View file

@ -0,0 +1 @@
TODO: Add your license here.

14
cw_lightning/README.md Normal file
View file

@ -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.

View file

@ -0,0 +1,7 @@
library cw_lightning;
/// A Calculator.
class Calculator {
/// Returns [value] plus 1.
int addOne(int value) => value + 1;
}

View file

@ -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});
}

View file

@ -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
];
}

View file

@ -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<String, dynamic> 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<String, dynamic> toJson() {
final m = <String, dynamic>{};
m['id'] = id;
m['amount'] = amount;
m['direction'] = direction.index;
m['date'] = date.millisecondsSinceEpoch;
m['isPending'] = isPending;
m['fee'] = fee;
return m;
}
}

View file

@ -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> unspentCoinsInfo,
required Uint8List seedBytes,
String? addressPageType,
List<BitcoinAddressRecord>? initialAddresses,
LightningBalance? initialBalance,
Map<String, int>? initialRegularAddressIndex,
Map<String, int>? initialChangeAddressIndex,
}) : _isTransactionUpdating = false,
syncStatus = NotConnectedSyncStatus(),
_balance = ObservableMap<CryptoCurrency, LightningBalance>(),
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<CryptoCurrency, LightningBalance> _balance;
StreamSubscription<List<Payment>>? _paymentsSub;
StreamSubscription<NodeState?>? _nodeStateSub;
StreamSubscription<LogEntry>? _logStream;
@override
@computed
ObservableMap<CryptoCurrency, LightningBalance> get balance => _balance;
static Future<LightningWallet> create(
{required String mnemonic,
required String password,
required WalletInfo walletInfo,
required Box<UnspentCoinsInfo> unspentCoinsInfo,
String? addressPageType,
List<BitcoinAddressRecord>? initialAddresses,
LightningBalance? initialBalance,
Map<String, int>? initialRegularAddressIndex,
Map<String, int>? 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<LightningWallet> open({
required String name,
required WalletInfo walletInfo,
required Box<UnspentCoinsInfo> 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<void> _handleNodeState(NodeState? nodeState) async {
if (nodeState == null) return;
_balance[CryptoCurrency.btcln] = LightningBalance(
confirmed: nodeState.maxPayableMsat ~/ 1000,
unconfirmed: nodeState.maxReceivableMsat ~/ 1000,
frozen: 0,
);
}
Future<void> _handlePayments(List<Payment> payments) async {
_isTransactionUpdating = true;
final txs = convertToTxInfo(payments);
transactionHistory.addMany(txs);
_isTransactionUpdating = false;
}
@override
Future<void> 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<void> 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<Payment> payments) {
_handlePayments(payments);
});
await _handlePayments(await sdk.listPayments(req: ListPaymentsRequest()));
print("initialized breez: ${(await sdk.isInitialized())}");
}
Future<void> 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<void> startSync() async {
try {
syncStatus = AttemptingSyncStatus();
await updateTransactions();
syncStatus = SyncedSyncStatus();
} catch (e) {
print(e);
syncStatus = FailedSyncStatus();
rethrow;
}
}
@override
Future<void> changePassword(String password) {
throw UnimplementedError("changePassword");
}
@action
@override
Future<void> connectToNode({required Node node}) async {
try {
syncStatus = ConnectingSyncStatus();
await updateTransactions();
syncStatus = ConnectedSyncStatus();
} catch (e) {
print(e);
syncStatus = FailedSyncStatus();
}
}
@override
Future<PendingTransaction> createTransaction(Object credentials) async {
throw UnimplementedError("createTransaction");
}
Future<bool> 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<String, LightningTransactionInfo> convertToTxInfo(List<Payment> payments) {
Map<String, LightningTransactionInfo> 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<Map<String, LightningTransactionInfo>> fetchTransactions() async {
final sdk = await BreezSDK();
final payments = await sdk.listPayments(req: ListPaymentsRequest());
final transactions = convertToTxInfo(payments);
return transactions;
}
@override
Future<void> rescan({
required int height,
int? chainTip,
ScanData? scanData,
bool? doSingleScan,
bool? usingElectrs,
}) async {
updateTransactions();
}
Future<void> 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<void> updateBalance() async {
// balance is updated automatically
}
@override
String mnemonic;
@override
String get seed => mnemonic;
Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type);
@override
Future<void> 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");
}
}
}

View file

@ -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<WalletInfo> walletInfoSource;
final Box<UnspentCoinsInfo> unspentCoinsInfoSource;
@override
WalletType getType() => WalletType.lightning;
@override
Future<LightningWallet> 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<bool> isWalletExit(String name) async =>
File(await pathForWallet(name: name, type: getType())).existsSync();
@override
Future<LightningWallet> 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<LightningWallet> restoreFromHardwareWallet(BitcoinRestoreWalletFromHardware credentials,
{bool? isTestnet}) {
throw UnimplementedError(
"Restoring a Lightning wallet from a hardware wallet is not yet supported!");
}
@override
Future<void> 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<void> 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<LightningWallet> restoreFromKeys(BitcoinRestoreWalletFromWIFCredentials credentials,
{bool? isTestnet}) async =>
throw UnimplementedError();
@override
Future<LightningWallet> 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;
}
}

920
cw_lightning/pubspec.lock Normal file
View file

@ -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"

88
cw_lightning/pubspec.yaml Normal file
View file

@ -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

View file

@ -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);
});
}

View file

@ -161,7 +161,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
Future<void>? updateBalance() => null;
@override
void close() async {
void close({bool? switchingToSameWalletType}) {
_listener?.stop();
_onAccountChangeReaction?.reaction.dispose();
_autoSaveTimer?.cancel();

View file

@ -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

View file

@ -138,7 +138,7 @@ abstract class NanoWalletBase
}
@override
void close() {
void close({bool? switchingToSameWalletType}) {
_client.stop();
_receiveTimer?.cancel();
}

View file

@ -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

View file

@ -165,7 +165,7 @@ abstract class SolanaWalletBase
Future<void> changePassword(String password) => throw UnimplementedError("changePassword");
@override
void close() {
void close({bool? switchingToSameWalletType}) {
_client.stop();
_transactionsUpdateTimer?.cancel();
}

View file

@ -186,7 +186,7 @@ abstract class TronWalletBase
}
@override
void close() {
void close({bool? switchingToSameWalletType}) {
_transactionsUpdateTimer?.cancel();
}

View file

@ -60,6 +60,26 @@
<string>bitcoin-wallet</string>
</array>
</dict>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>lightning</string>
<key>CFBundleURLSchemes</key>
<array>
<string>lightning</string>
</array>
</dict>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLName</key>
<string>lightning-wallet</string>
<key>CFBundleURLSchemes</key>
<array>
<string>lightning-wallet</string>
</array>
</dict>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>

View file

@ -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();
}

View file

@ -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]|\$)'

View file

@ -24,7 +24,7 @@ class SeedValidator extends Validator<MnemonicItem> {
static List<String> 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:

View file

@ -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:

View file

@ -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<void> setup({
fiatConvertationStore: getIt.get<FiatConversionStore>()));
getIt.registerFactory(() => DashboardViewModel(
balanceViewModel: getIt.get<BalanceViewModel>(),
appStore: getIt.get<AppStore>(),
tradesStore: getIt.get<TradesStore>(),
tradeFilterStore: getIt.get<TradeFilterStore>(),
transactionFilterStore: getIt.get<TransactionFilterStore>(),
settingsStore: settingsStore,
yatStore: getIt.get<YatStore>(),
ordersStore: getIt.get<OrdersStore>(),
anonpayTransactionsStore: getIt.get<AnonpayTransactionsStore>(),
sharedPreferences: getIt.get<SharedPreferences>(),
keyService: getIt.get<KeyService>()));
balanceViewModel: getIt.get<BalanceViewModel>(),
appStore: getIt.get<AppStore>(),
tradesStore: getIt.get<TradesStore>(),
tradeFilterStore: getIt.get<TradeFilterStore>(),
transactionFilterStore: getIt.get<TransactionFilterStore>(),
settingsStore: settingsStore,
yatStore: getIt.get<YatStore>(),
ordersStore: getIt.get<OrdersStore>(),
anonpayTransactionsStore: getIt.get<AnonpayTransactionsStore>(),
sharedPreferences: getIt.get<SharedPreferences>(),
keyService: getIt.get<KeyService>(),
lightningViewModel: getIt.get<LightningViewModel>(),
));
getIt.registerFactory<AuthService>(
() => AuthService(
@ -624,7 +636,6 @@ Future<void> setup({
authService: getIt.get<AuthService>(),
initialPaymentRequest: initialPaymentRequest,
));
getIt.registerFactory(
() => SendTemplatePage(sendTemplateViewModel: getIt.get<SendTemplateViewModel>()));
@ -905,6 +916,8 @@ Future<void> 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<void> setup({
getIt.registerFactory(() => NFTViewModel(appStore, getIt.get<BottomSheetService>()));
getIt.registerFactory<TorPage>(() => TorPage(getIt.get<AppStore>()));
getIt.registerFactory<LightningViewModel>(
() => LightningViewModel(),
);
getIt.registerFactory<LightningSendViewModel>(
() => LightningSendViewModel(
settingsStore: getIt.get<SettingsStore>(),
fiatConversionStore: getIt.get<FiatConversionStore>(),
),
);
getIt.registerFactoryParam<LightningInvoicePageViewModel, void, void>((_, __) {
return LightningInvoicePageViewModel(
getIt.get<SettingsStore>(),
getIt.get<AppStore>().wallet!,
getIt.get<SharedPreferences>(),
getIt.get<LightningViewModel>(),
);
});
getIt.registerFactoryParam<LightningReceiveOnchainPage, void, void>((_, __) {
return LightningReceiveOnchainPage(
addressListViewModel: getIt.get<WalletAddressListViewModel>(),
lightningViewModel: getIt.get<LightningViewModel>(),
receiveOptionViewModel:
getIt.get<ReceiveOptionViewModel>(param1: lightning!.getOptionOnchain()),
);
});
getIt.registerFactoryParam<LightningInvoicePage, void, void>((_, __) {
return LightningInvoicePage(
lightningInvoicePageViewModel: getIt.get<LightningInvoicePageViewModel>(),
receiveOptionViewModel:
getIt.get<ReceiveOptionViewModel>(param1: lightning!.getOptionInvoice()),
);
});
getIt.registerFactory<LightningSendPage>(() {
return LightningSendPage(
output: Output(
getIt.get<AppStore>().wallet!,
getIt.get<SettingsStore>(),
getIt.get<FiatConversionStore>(),
() => CryptoCurrency.btcln,
),
authService: getIt.get<AuthService>(),
lightningSendViewModel: getIt.get<LightningSendViewModel>(),
);
});
getIt.registerFactoryParam<LightningSendConfirmPage, LNInvoice, void>((LNInvoice invoice, _) {
return LightningSendConfirmPage(
invoice: invoice,
lightningSendViewModel: getIt.get<LightningSendViewModel>(),
);
});
_isSetupFinished = true;
}

View file

@ -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<void> Function(
BuildContext context, DashboardViewModel viewModel) onTap;
final Future<void> 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 {
},
);
}
}
}

View file

@ -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:

View file

@ -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<String, dynamic>;
// 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 = <String, String>{
'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();
}

View file

@ -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<WalletInfo> walletInfoSource, Box<UnspentCoinsInfo> unspentCoinSource) {
return LightningWalletService(walletInfoSource, unspentCoinSource);
}
@override
List<LightningReceivePageOption> 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;
}
}

View file

@ -30,46 +30,49 @@ Future<void> 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<CryptoCurrency>? currencies;
if (appStore.wallet!.type == WalletType.ethereum) {
currencies =
ethereum!.getERC20Currencies(appStore.wallet!).where((element) => element.enabled);
Iterable<CryptoCurrency>? 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);
}

View file

@ -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);
}

View file

@ -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<dynamic> createRoute(RouteSettings settings) {
case Routes.torPage:
return MaterialPageRoute<void>(builder: (_) => getIt.get<TorPage>());
case Routes.lightningSend:
return CupertinoPageRoute<void>(
fullscreenDialog: true, builder: (_) => getIt.get<LightningSendPage>());
case Routes.lightningSendConfirm:
return CupertinoPageRoute<void>(
fullscreenDialog: true,
builder: (_) => getIt.get<LightningSendConfirmPage>(param1: settings.arguments));
case Routes.lightningReceiveOnchain:
final args = settings.arguments as List;
return CupertinoPageRoute<void>(
fullscreenDialog: true,
builder: (_) => getIt.get<LightningReceiveOnchainPage>(param1: args));
case Routes.lightningInvoice:
return CupertinoPageRoute<void>(
fullscreenDialog: true, builder: (_) => getIt.get<LightningInvoicePage>());
case Routes.connectDevices:
final params = settings.arguments as ConnectDevicePageParams;
return MaterialPageRoute<void>(

View file

@ -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';
}

View file

@ -30,6 +30,7 @@ class DesktopWalletSelectionDropDown extends StatefulWidget {
class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionDropDown> {
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<DesktopWalletSelectionD
return tBitcoinIcon;
}
return bitcoinIcon;
case WalletType.lightning:
return lightningIcon;
case WalletType.monero:
return moneroIcon;
case WalletType.litecoin:

View file

@ -186,6 +186,20 @@ class CryptoBalanceWidget extends StatelessWidget {
),
],
)),
Observer(builder: (_) {
final serviceMessage = dashboardViewModel.serviceMessage;
if (serviceMessage.isEmpty) {
return const SizedBox();
}
return Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 8),
child: DashBoardRoundedCardWidget(
onTap: () => null,
title: S.of(context).warning,
subTitle: serviceMessage,
),
);
}),
Observer(
builder: (_) {
if (dashboardViewModel.balanceViewModel.isShowCard &&

View file

@ -28,6 +28,7 @@ class MenuWidgetState extends State<MenuWidget> {
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<MenuWidget> {
Image moneroIcon;
Image bitcoinIcon;
Image lightningIcon;
Image litecoinIcon;
Image havenIcon;
Image ethereumIcon;
@ -103,6 +105,7 @@ class MenuWidgetState extends State<MenuWidget> {
color: Theme.of(context).extension<CakeMenuTheme>()!.iconColor);
bitcoinIcon = Image.asset('assets/images/bitcoin_menu.png',
color: Theme.of(context).extension<CakeMenuTheme>()!.iconColor);
lightningIcon = Image.asset('assets/images/lightning_logo.png');
return Row(
mainAxisSize: MainAxisSize.max,
@ -220,6 +223,8 @@ class MenuWidgetState extends State<MenuWidget> {
return moneroIcon;
case WalletType.bitcoin:
return bitcoinIcon;
case WalletType.lightning:
return lightningIcon;
case WalletType.litecoin:
return litecoinIcon;
case WalletType.haven:

View file

@ -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) {

View file

@ -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<ExchangeCard> {
color: Theme.of(context)
.extension<ExchangePageTheme>()!
.hintTextColor),
validator: _isAmountEditable
? widget.currencyValueValidator
: null),
validator: _isAmountEditable ? widget.currencyValueValidator : null),
),
),
if (widget.hasAllAmount)
@ -330,138 +330,142 @@ class ExchangeCardState extends State<ExchangeCard> {
: 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<ExchangePageTheme>()!.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<ExchangePageTheme>()!.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<ExchangePageTheme>()!.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: <Widget>[
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: <Widget>[
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<ExchangePageTheme>()!.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: <Widget>[
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: <Widget>[
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<SendPageTheme>()!
.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<SendPageTheme>()!
.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<void>(
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<void>(
context, S.of(context).copied_to_clipboard);
},
child: Container(
padding: EdgeInsets.fromLTRB(8, 8, 0, 8),
color: Colors.transparent,
child: copyImage),
),
)))
])))
])),
),
],
]),
);
}

View file

@ -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<void>(
context: context,
builder: (_) => InformationPage(information: information));
showPopUp<void>(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<ExchangeTradeForm> {
style: TextStyle(
fontSize: 14.0,
fontWeight: FontWeight.w500,
color: Theme.of(context).extension<TransactionTradeTheme>()!.detailsTitlesColor),
color: Theme.of(context)
.extension<TransactionTradeTheme>()!
.detailsTitlesColor),
),
if (trade.expiredAt != null)
TimerWidget(trade.expiredAt!,
@ -159,9 +155,9 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
decoration: BoxDecoration(
border: Border.all(
width: 3,
color: Theme.of(context).extension<ExchangePageTheme>()!.qrCodeColor
)
),
color: Theme.of(context)
.extension<ExchangePageTheme>()!
.qrCodeColor)),
child: QrImage(data: trade.inputAddress ?? fetchingLabel),
)))),
Spacer(flex: 3)
@ -192,10 +188,8 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
? Builder(
builder: (context) => GestureDetector(
onTap: () {
Clipboard.setData(
ClipboardData(text: value));
showBar<void>(context,
S.of(context).copied_to_clipboard);
Clipboard.setData(ClipboardData(text: value));
showBar<void>(context, S.of(context).copied_to_clipboard);
},
child: content,
))
@ -209,17 +203,14 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
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<ExchangeTradeForm> {
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<ExchangeTradeForm> {
void transactionStatePopup() {
if (this.mounted) {
showPopUp<void>(
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: <Widget>[
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<CakeTextTheme>()!.titleColor,
decoration: TextDecoration.none,
),
if (state is TransactionCommitted) {
return Stack(
children: <Widget>[
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: <Widget>[
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<ExchangeTradeForm> {
),
),
),
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: <Widget>[
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<CakeTextTheme>()!.titleColor,
decoration: TextDecoration.none,
),
),
),
),
),
)
],
);
});
});
});
}
}
}

View file

@ -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<FormState>();
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<bool> _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<KeyboardTheme>()!.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<ExchangePageTheme>()!
.firstGradientTopPanelColor,
Theme.of(context)
.extension<ExchangePageTheme>()!
.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: <Widget>[
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<DashboardPageTheme>()!.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<void>(
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<DashboardPageTheme>()!.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<DashboardPageTheme>()!.textColor,
),
),
),
),
],
),
),
],
),
rightButtonText: S.of(context).ok,
actionRightButton: () => Navigator.of(context).pop(),
actionLeftButton: () async {
Clipboard.setData(ClipboardData(text: state.payload as String));
showBar<void>(context, S.of(context).copied_to_clipboard);
},
leftButtonText: S.of(context).copy,
);
});
}
if (state is FailureState) {
showPopUp<void>(
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;
}
}

View file

@ -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<FormState>() {
_amountController.addListener(() {
if (_formKey.currentState!.validate()) {
addressListViewModel.changeAmount(_amountController.text);
}
});
}
final WalletAddressListViewModel addressListViewModel;
final ReceiveOptionViewModel receiveOptionViewModel;
final LightningViewModel lightningViewModel;
final TextEditingController _amountController;
final GlobalKey<FormState> _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<QRCodeTheme>()!.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<DashboardPageTheme>()!.textColor),
),
),
Row(
children: <Widget>[
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<DashboardPageTheme>()!
.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<void>(context, S.of(context).copied_to_clipboard);
},
child: Row(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Expanded(
child: Text(
results.bitcoinAddress,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.w500,
color: Theme.of(context)
.extension<DashboardPageTheme>()!
.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<DashboardPageTheme>()!.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;
}
}

View file

@ -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<ExchangePageTheme>()!.textFieldBorderBottomPanelColor,
color: Theme.of(context)
.extension<ExchangePageTheme>()!
.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: <Widget>[
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: <Widget>[
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<SendPageTheme>()!.textFieldButtonColor,
color: Theme.of(context)
.extension<SendPageTheme>()!
.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<SendPageTheme>()!.textFieldButtonIconColor,
color: Theme.of(context)
.extension<SendPageTheme>()!
.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<ExchangePageTheme>()!.hintTextColor,
color:
Theme.of(context).extension<ExchangePageTheme>()!.hintTextColor,
),
validator: null,
),
@ -131,19 +146,23 @@ class AnonpayCurrencyInputField extends StatelessWidget {
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Text(
S.of(context).min_value(minAmount, selectedCurrency.toString()),
style: TextStyle(
fontSize: 10,
height: 1.2,
color: Theme.of(context).extension<ExchangePageTheme>()!.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<ExchangePageTheme>()!.hintTextColor)),
color: Theme.of(context).extension<ExchangePageTheme>()!.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<ExchangePageTheme>()!.hintTextColor))
],
],
),
)

View file

@ -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<FormState> formKey;
@override
Widget build(BuildContext context) {
return Form(
key: formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
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<ExchangePageTheme>()!.textFieldBorderTopPanelColor,
suffixIcon: SizedBox(width: 36),
hintText: S.of(context).optional_description,
placeholderTextStyle: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Theme.of(context).extension<ExchangePageTheme>()!.hintTextColor,
),
textStyle: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Colors.white),
validator: null,
),
SizedBox(
height: 52,
),
],
));
}
}

View file

@ -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,

View file

@ -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;
}

View file

@ -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<FormState>() {
initialSatAmount = ((invoice.amountMsat ?? 0) ~/ 1000);
_amountController = TextEditingController();
_fiatAmountController = TextEditingController();
_amountController.text = initialSatAmount.toString();
_fiatAmountController.text = lightningSendViewModel.formattedFiatAmount(initialSatAmount);
}
final GlobalKey<FormState> _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<KeyboardTheme>()!.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<ExchangePageTheme>()!
.firstGradientTopPanelColor,
Theme.of(context)
.extension<ExchangePageTheme>()!
.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<ExchangePageTheme>()!
.textFieldBorderTopPanelColor,
suffixIcon: SizedBox(width: 36),
initialValue: "${S.of(context).invoice}: ${invoice.bolt11}",
placeholderTextStyle: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Theme.of(context).extension<ExchangePageTheme>()!.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<ExchangePageTheme>()!
.textFieldBorderTopPanelColor,
suffixIcon: SizedBox(width: 36),
initialValue:
"sats: ${lightning!.bitcoinAmountToLightningString(amount: initialSatAmount)}",
placeholderTextStyle: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Theme.of(context).extension<ExchangePageTheme>()!.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<ExchangePageTheme>()!
.textFieldBorderTopPanelColor,
suffixIcon: SizedBox(width: 36),
placeholderTextStyle: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Theme.of(context).extension<ExchangePageTheme>()!.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<ExchangePageTheme>()!
.textFieldBorderTopPanelColor,
suffixIcon: SizedBox(width: 36),
placeholderTextStyle: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Theme.of(context).extension<ExchangePageTheme>()!.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: <Widget>[
LoadingPrimaryButton(
text: S.of(context).send,
onPressed: () async {
try {
await lightningSendViewModel.send(
invoice,
int.parse(_amountController.text)
);
showPopUp<void>(
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<void>(
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<bool> _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;
}
}

View file

@ -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<FormState>();
final Output output;
final AuthService authService;
final LightningSendViewModel lightningSendViewModel;
final GlobalKey<FormState> _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<KeyboardTheme>()!.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<ExchangePageTheme>()!
.firstGradientTopPanelColor,
Theme.of(context)
.extension<ExchangePageTheme>()!
.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<SendPageTheme>()!.textFieldButtonColor,
borderColor:
Theme.of(context).extension<SendPageTheme>()!.textFieldBorderColor,
textStyle:
TextStyle(fontSize: 14, fontWeight: FontWeight.w500, color: Colors.white),
hintStyle: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Theme.of(context).extension<SendPageTheme>()!.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: <Widget>[
PrimaryButton(
text: S.of(context).send,
color: Theme.of(context).primaryColor,
textColor: Colors.white,
onPressed: () => send(context),
),
],
),
),
),
),
);
}
Future<void> send(BuildContext context) async {
try {
await lightningSendViewModel.processInput(context, bolt11Controller.text);
} catch (e) {
showPopUp<void>(
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<bool> _onNavigateBack(BuildContext context) async {
onClose(context);
return false;
}
void _setEffects(BuildContext context) {
if (_effectsInstalled) {
return;
}
_effectsInstalled = true;
}
}

View file

@ -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(

View file

@ -96,6 +96,7 @@ class WalletListBody extends StatefulWidget {
class WalletListBodyState extends State<WalletListBody> {
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<WalletListBody> {
return tBitcoinIcon;
}
return bitcoinIcon;
case WalletType.lightning:
return lightningIcon;
case WalletType.monero:
return moneroIcon;
case WalletType.litecoin:

View file

@ -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);
}
}

View file

@ -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<void> changeCurrentWallet(
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, 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);

View file

@ -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) {

View file

@ -48,6 +48,7 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store {
case WalletType.haven:
case WalletType.nano:
case WalletType.banano:
case WalletType.lightning:
return false;
}
}

View file

@ -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';
}

View file

@ -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<String, List<FilterItem>> 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;

View file

@ -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;

View file

@ -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];

View file

@ -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;

View file

@ -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;

View file

@ -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<Currency> 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<void> 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<void> _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 (_) {}
}
}

View file

@ -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<void> 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<void> 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");
}
}
}

View file

@ -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<ReceiveOnchainResult> 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<String> 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<InvoiceSoftLimitsResult> 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<int> getBalanceSats() async {
try {
final sdk = await BreezSDK();
final nodeState = (await sdk.nodeInfo())!;
return nodeState.channelsBalanceMsat ~/ 1000;
} catch (_) {
return 0;
}
}
Future<BZG.HealthCheckStatus> 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,
});
}

View file

@ -85,6 +85,7 @@ abstract class NodeCreateOrEditViewModelBase with Store {
case WalletType.litecoin:
case WalletType.bitcoinCash:
case WalletType.bitcoin:
case WalletType.lightning:
return false;
}
}

View file

@ -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;

View file

@ -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);

View file

@ -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,

View file

@ -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),

View file

@ -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);
}

View file

@ -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",

View file

@ -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:

View file

@ -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:

View file

@ -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);

View file

@ -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 ..

View file

@ -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

View file

@ -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": "ﺔﻟﺎﺳﺭ",

Some files were not shown because too many files have changed in this diff Show more