mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2024-11-16 09:17:35 +00:00
litecoin mweb support (#1455)
* Fix stub creation * Generate MWEB addresses * Fix mweb address derivation * Use camel-case * Show utxos in tx list * A few fixes * Add spent processing * Update balance * Balance fixes * Update address records * Get rid of debounce hack * Get sending up to the confirmation box * Fee estimation * Stop the daemon if plugin is unloaded * Normal fee for non-mweb txns * Fix fee estimation for send all * Don't hash mweb addresses * More fee fixes * Broadcast mweb * Remove test files * One more * Confirm sent txns * Couple of fixes * Resign inputs after mweb create * Some more fixes * Update balance after sending * Correctly update address records * Update confs * [skip ci] updates * [skip ci] add dep overrides * working * small fix * merge fixes [skip ci] * merge fixes [skip ci] * [skip ci] minor fixes * silent payment fixes [skip ci] * updates [skip ci] * save [skip ci] * use mwebutxos box * [skip ci] lots of fixes, still testing * add rescan from height feature and test workflow build * install go * use sudo * correct package name * move building mweb higher for faster testing * install fixes * install later version of go * go fixes * testing * testing * testing * testing * testing * should workgit add .github/workflows/pr_test_build.yml * ??? * ??? pt.2 * should work, for real this time * fix tx history not persisting + update build_mwebd script * updates * fix some rescan and address gen issues * save [skip ci] * fix unconfirmed balance not updating when receiving * unspent coins / coin control fixes * coin control fixes * address balance and txCount fixes, try/catch electrum call * fix txCount for addresses * save [skip ci] * potential fixes * minor fix * minor fix - 2 * sync status fixes, potential fix for background state issue * workflow and script updates * updates * expirimental optimization * [skip ci] minor enhancements * workflow and script fixes * workflow minor cleanup [skip ci] * minor code cleanup & friendlier error message on failed tx's * balance when sending fix * experimental * more experiments * save * updates * coin control edge cases * remove neutrino.db if no litecoin wallets left after deleting * update translations * updates * minor fix * [skip ci] update translations + minor fixes * state fixes * configure fix * ui updates * translation fixes * [skip ci] addressbook updates * fix popup * fix popup2 * fix litecoin address book * fix ios mwebd build script * fix for building monero.com * minor fix * uncomment fix for state issues * potential mweb sync fix (ios) * remove print [skip ci] * electrum stream potential fix * fix ios build issues [skip ci] * connection reliability updates, update kotlin code to match swift code, minor electrum error handling * dep fixes * minor fix * more merge fixes * bitcoin_flutter removal fixes * [skip ci] fix always scan setting, swift updates * updates * fixes * small fix * small fix * fix * dart:convert != package:convert * change address fixes * update bitcoin_base to fix mweb address program checking * fix ios xcode project [skip ci] * updates * more fixes * more fixes * ensure we don't initialize mweb until we really have to * fix regression * improve mweb reliability * [skip ci] wip adress generation * wip * wip * [skip ci] wip * updates [skip ci] * ios fixes * fix workflows + ios fix * test old mweb version * update go version and mwebd hash * review updates pt.1 * Update cw_bitcoin/lib/litecoin_wallet.dart Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com> * remove non-litecoin address types regex [skip ci] * more minor fixes * remove duplicate [skip ci] * Update lib/store/settings_store.dart Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com> * script updates, swap params on createLitecoinWalletService * topup fix * [skip ci] wip * [skip ci] testing * [skip ci] file didn't get saved * more address generation reliability fixes * [skip ci] minor * minor code cleanup * hopefully prevents send issue * [skip ci] wip address changes * [skip ci] save * save mweb addresses, auto-restart sync process if it gets stuck [skip ci] * address generation issues mostly resolved * more performance fixes * [skip ci] * this should maybe be refactored, pt.1 * separate mweb balances, pt.2 * [skip ci] save * add translations [skip ci] * fix sending with mweb amounts * works for simple mweb-mweb case, further testing needed * found an edge case * [skip ci] make failed broadcast error message less serious * minor * capture all grpc errors and much better error handling overall * [skip ci] minor * prevent transactions with < 6 confirmations from being used + hide mweb balances if mweb is off * fix * merge fixes pt.1 [skip ci] * fix mweb tags * fix * [skip ci] fix tag spacing * fix transaction history not showing up * fix mweb crash on non-fully deleted mweb cache, sync status ETA, other connection fixes * [skip ci] minor code cleanup * [skip ci] minor code cleanup * additional cleanup * silent payments eta fixes and updates * revert sync eta changes into separate pr * [skip ci] minor * [skip ci] minor * revert sync status title * review fixes, additional cleanup * [skip ci] minor * [skip ci] minor * [skip ci] minor * trigger build * review fixes, pt.2 * check if still processing utxos before updating sync status [skip ci] * [skip ci] minor * balance fix * minor * minor * [skip ci] minor * [skip ci] fix test net btc * don't use mwebd for non-mweb tx's * [skip ci] minor cleanup * don't show all 1000+ mweb addresses on receive page * minor cleanup + additional logging --------- Co-authored-by: Hector Chu <hectorchu@gmail.com> Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com> Co-authored-by: Czarek Nakamoto <cyjan@mrcyjanek.net>
This commit is contained in:
parent
fc5878d991
commit
62e0c2a592
120 changed files with 4308 additions and 365 deletions
19
.github/workflows/pr_test_build_android.yml
vendored
19
.github/workflows/pr_test_build_android.yml
vendored
|
@ -96,6 +96,25 @@ jobs:
|
|||
cd /opt/android/cake_wallet
|
||||
flutter pub get
|
||||
|
||||
|
||||
- name: Install go and gomobile
|
||||
run: |
|
||||
# install go > 1.23:
|
||||
wget https://go.dev/dl/go1.23.1.linux-amd64.tar.gz
|
||||
sudo rm -rf /usr/local/go && sudo tar -C /usr/local -xzf go1.23.1.linux-amd64.tar.gz
|
||||
export PATH=$PATH:/usr/local/go/bin
|
||||
export PATH=$PATH:~/go/bin
|
||||
go install golang.org/x/mobile/cmd/gomobile@latest
|
||||
gomobile init
|
||||
|
||||
- name: Build mwebd
|
||||
run: |
|
||||
# paths are reset after each step, so we need to set them again:
|
||||
export PATH=$PATH:/usr/local/go/bin
|
||||
export PATH=$PATH:~/go/bin
|
||||
cd /opt/android/cake_wallet/scripts/android/
|
||||
./build_mwebd.sh --dont-install
|
||||
|
||||
- name: Generate KeyStore
|
||||
run: |
|
||||
cd /opt/android/cake_wallet/android/app
|
||||
|
|
19
.github/workflows/pr_test_build_linux.yml
vendored
19
.github/workflows/pr_test_build_linux.yml
vendored
|
@ -89,6 +89,25 @@ jobs:
|
|||
cd /opt/android/cake_wallet
|
||||
flutter pub get
|
||||
|
||||
- name: Install go and gomobile
|
||||
run: |
|
||||
# install go > 1.23:
|
||||
wget https://go.dev/dl/go1.23.1.linux-amd64.tar.gz
|
||||
sudo rm -rf /usr/local/go && sudo tar -C /usr/local -xzf go1.23.1.linux-amd64.tar.gz
|
||||
export PATH=$PATH:/usr/local/go/bin
|
||||
export PATH=$PATH:~/go/bin
|
||||
go install golang.org/x/mobile/cmd/gomobile@latest
|
||||
gomobile init
|
||||
|
||||
- name: Build mwebd
|
||||
run: |
|
||||
# paths are reset after each step, so we need to set them again:
|
||||
export PATH=$PATH:/usr/local/go/bin
|
||||
export PATH=$PATH:~/go/bin
|
||||
# build mwebd:
|
||||
cd /opt/android/cake_wallet/scripts/android/
|
||||
./build_mwebd.sh --dont-install
|
||||
|
||||
- name: Generate localization
|
||||
run: |
|
||||
cd /opt/android/cake_wallet
|
||||
|
|
BIN
assets/images/mweb_logo.png
Normal file
BIN
assets/images/mweb_logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 19 KiB |
|
@ -8,6 +8,7 @@ import 'package:cw_core/sec_random_native.dart';
|
|||
import 'package:cw_core/utils/text_normalizer.dart';
|
||||
|
||||
const segwit = '100';
|
||||
const mweb = 'eb';
|
||||
final wordlist = englishWordlist;
|
||||
|
||||
double logBase(num x, num base) => log(x) / log(base);
|
||||
|
@ -125,7 +126,7 @@ Future<Uint8List> mnemonicToSeedBytes(String mnemonic,
|
|||
return Uint8List.fromList(bytes);
|
||||
}
|
||||
|
||||
bool matchesAnyPrefix(String mnemonic) => prefixMatches(mnemonic, [segwit]).any((el) => el);
|
||||
bool matchesAnyPrefix(String mnemonic) => prefixMatches(mnemonic, [segwit, mweb]).any((el) => el);
|
||||
|
||||
bool validateMnemonic(String mnemonic, {String prefix = segwit}) {
|
||||
try {
|
||||
|
|
|
@ -7,6 +7,7 @@ class BitcoinReceivePageOption implements ReceivePageOption {
|
|||
static const p2tr = BitcoinReceivePageOption._('Taproot (P2TR)');
|
||||
static const p2wsh = BitcoinReceivePageOption._('Segwit (P2WSH)');
|
||||
static const p2pkh = BitcoinReceivePageOption._('Legacy (P2PKH)');
|
||||
static const mweb = BitcoinReceivePageOption._('MWEB');
|
||||
|
||||
static const silent_payments = BitcoinReceivePageOption._('Silent Payments');
|
||||
|
||||
|
@ -27,6 +28,11 @@ class BitcoinReceivePageOption implements ReceivePageOption {
|
|||
BitcoinReceivePageOption.p2pkh
|
||||
];
|
||||
|
||||
static const allLitecoin = [
|
||||
BitcoinReceivePageOption.p2wpkh,
|
||||
BitcoinReceivePageOption.mweb
|
||||
];
|
||||
|
||||
BitcoinAddressType toType() {
|
||||
switch (this) {
|
||||
case BitcoinReceivePageOption.p2tr:
|
||||
|
@ -39,6 +45,8 @@ class BitcoinReceivePageOption implements ReceivePageOption {
|
|||
return P2shAddressType.p2wpkhInP2sh;
|
||||
case BitcoinReceivePageOption.silent_payments:
|
||||
return SilentPaymentsAddresType.p2sp;
|
||||
case BitcoinReceivePageOption.mweb:
|
||||
return SegwitAddresType.mweb;
|
||||
case BitcoinReceivePageOption.p2wpkh:
|
||||
default:
|
||||
return SegwitAddresType.p2wpkh;
|
||||
|
@ -51,6 +59,8 @@ class BitcoinReceivePageOption implements ReceivePageOption {
|
|||
return BitcoinReceivePageOption.p2tr;
|
||||
case SegwitAddresType.p2wsh:
|
||||
return BitcoinReceivePageOption.p2wsh;
|
||||
case SegwitAddresType.mweb:
|
||||
return BitcoinReceivePageOption.mweb;
|
||||
case P2pkhAddressType.p2pkh:
|
||||
return BitcoinReceivePageOption.p2pkh;
|
||||
case P2shAddressType.p2wpkhInP2sh:
|
||||
|
|
|
@ -87,7 +87,7 @@ class LitecoinTransactionPriority extends BitcoinTransactionPriority {
|
|||
}
|
||||
|
||||
@override
|
||||
String get units => 'Latoshi';
|
||||
String get units => 'Litoshi';
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
|
|
|
@ -506,6 +506,12 @@ class ElectrumClient {
|
|||
|
||||
void _methodHandler({required String method, required Map<String, dynamic> request}) {
|
||||
switch (method) {
|
||||
case 'blockchain.headers.subscribe':
|
||||
final params = request['params'] as List<dynamic>;
|
||||
final id = 'blockchain.headers.subscribe';
|
||||
|
||||
_tasks[id]?.subject?.add(params.last);
|
||||
break;
|
||||
case 'blockchain.scripthash.subscribe':
|
||||
final params = request['params'] as List<dynamic>;
|
||||
final scripthash = params.first as String?;
|
||||
|
|
|
@ -7,7 +7,14 @@ class ElectrumBalance extends Balance {
|
|||
required this.confirmed,
|
||||
required this.unconfirmed,
|
||||
required this.frozen,
|
||||
}) : super(confirmed, unconfirmed);
|
||||
this.secondConfirmed = 0,
|
||||
this.secondUnconfirmed = 0,
|
||||
}) : super(
|
||||
confirmed,
|
||||
unconfirmed,
|
||||
secondAvailable: secondConfirmed,
|
||||
secondAdditional: secondUnconfirmed,
|
||||
);
|
||||
|
||||
static ElectrumBalance? fromJSON(String? jsonSource) {
|
||||
if (jsonSource == null) {
|
||||
|
@ -25,9 +32,12 @@ class ElectrumBalance extends Balance {
|
|||
int confirmed;
|
||||
int unconfirmed;
|
||||
final int frozen;
|
||||
int secondConfirmed = 0;
|
||||
int secondUnconfirmed = 0;
|
||||
|
||||
@override
|
||||
String get formattedAvailableBalance => bitcoinAmountToString(amount: confirmed - frozen);
|
||||
String get formattedAvailableBalance =>
|
||||
bitcoinAmountToString(amount: confirmed - frozen);
|
||||
|
||||
@override
|
||||
String get formattedAdditionalBalance => bitcoinAmountToString(amount: unconfirmed);
|
||||
|
@ -38,6 +48,21 @@ class ElectrumBalance extends Balance {
|
|||
return frozenFormatted == '0.0' ? '' : frozenFormatted;
|
||||
}
|
||||
|
||||
String toJSON() =>
|
||||
json.encode({'confirmed': confirmed, 'unconfirmed': unconfirmed, 'frozen': frozen});
|
||||
@override
|
||||
String get formattedSecondAvailableBalance => bitcoinAmountToString(amount: secondConfirmed);
|
||||
|
||||
@override
|
||||
String get formattedSecondAdditionalBalance => bitcoinAmountToString(amount: secondUnconfirmed);
|
||||
|
||||
@override
|
||||
String get formattedFullAvailableBalance =>
|
||||
bitcoinAmountToString(amount: confirmed + secondConfirmed - frozen);
|
||||
|
||||
String toJSON() => json.encode({
|
||||
'confirmed': confirmed,
|
||||
'unconfirmed': unconfirmed,
|
||||
'frozen': frozen,
|
||||
'secondConfirmed': secondConfirmed,
|
||||
'secondUnconfirmed': secondUnconfirmed
|
||||
});
|
||||
}
|
||||
|
|
|
@ -2,9 +2,9 @@ import 'dart:async';
|
|||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||
import 'package:cw_bitcoin/litecoin_wallet_addresses.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:cw_core/encryption_file_utils.dart';
|
||||
import 'package:blockchain_utils/blockchain_utils.dart';
|
||||
|
@ -23,6 +23,7 @@ import 'package:cw_bitcoin/electrum_transaction_history.dart';
|
|||
import 'package:cw_bitcoin/electrum_transaction_info.dart';
|
||||
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
|
||||
import 'package:cw_bitcoin/exceptions.dart';
|
||||
import 'package:cw_bitcoin/litecoin_wallet.dart';
|
||||
import 'package:cw_bitcoin/pending_bitcoin_transaction.dart';
|
||||
import 'package:cw_bitcoin/utils.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
|
@ -112,11 +113,18 @@ abstract class ElectrumWalletBase
|
|||
}
|
||||
|
||||
if (seedBytes != null) {
|
||||
return currency == CryptoCurrency.bch
|
||||
? bitcoinCashHDWallet(seedBytes)
|
||||
: Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath(
|
||||
switch (currency) {
|
||||
case CryptoCurrency.btc:
|
||||
case CryptoCurrency.ltc:
|
||||
case CryptoCurrency.tbtc:
|
||||
return Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath(
|
||||
_hardenedDerivationPath(derivationInfo?.derivationPath ?? electrum_path))
|
||||
as Bip32Slip10Secp256k1;
|
||||
case CryptoCurrency.bch:
|
||||
return bitcoinCashHDWallet(seedBytes);
|
||||
default:
|
||||
throw Exception("Unsupported currency");
|
||||
}
|
||||
}
|
||||
|
||||
return Bip32Slip10Secp256k1.fromExtendedKey(xpub!);
|
||||
|
@ -163,11 +171,13 @@ abstract class ElectrumWalletBase
|
|||
Set<String> get addressesSet => walletAddresses.allAddresses.map((addr) => addr.address).toSet();
|
||||
|
||||
List<String> get scriptHashes => walletAddresses.addressesByReceiveType
|
||||
.where((addr) => RegexUtils.addressTypeFromStr(addr.address, network) is! MwebAddress)
|
||||
.map((addr) => (addr as BitcoinAddressRecord).getScriptHash(network))
|
||||
.toList();
|
||||
|
||||
List<String> get publicScriptHashes => walletAddresses.allAddresses
|
||||
.where((addr) => !addr.isHidden)
|
||||
.where((addr) => RegexUtils.addressTypeFromStr(addr.address, network) is! MwebAddress)
|
||||
.map((addr) => addr.getScriptHash(network))
|
||||
.toList();
|
||||
|
||||
|
@ -274,6 +284,7 @@ abstract class ElectrumWalletBase
|
|||
|
||||
void Function(FlutterErrorDetails)? _onError;
|
||||
Timer? _autoSaveTimer;
|
||||
StreamSubscription<dynamic>? _receiveStream;
|
||||
Timer? _updateFeeRateTimer;
|
||||
static const int _autoSaveInterval = 1;
|
||||
|
||||
|
@ -324,7 +335,8 @@ abstract class ElectrumWalletBase
|
|||
isSingleScan: doSingleScan ?? false,
|
||||
));
|
||||
|
||||
await for (var message in receivePort) {
|
||||
_receiveStream?.cancel();
|
||||
_receiveStream = receivePort.listen((var message) async {
|
||||
if (message is Map<String, ElectrumTransactionInfo>) {
|
||||
for (final map in message.entries) {
|
||||
final txid = map.key;
|
||||
|
@ -387,10 +399,16 @@ abstract class ElectrumWalletBase
|
|||
nodeSupportsSilentPayments = false;
|
||||
}
|
||||
|
||||
if (message.syncStatus is SyncingSyncStatus) {
|
||||
var status = message.syncStatus as SyncingSyncStatus;
|
||||
syncStatus = SyncingSyncStatus(status.blocksLeft, status.ptc);
|
||||
} else {
|
||||
syncStatus = message.syncStatus;
|
||||
}
|
||||
|
||||
await walletInfo.updateRestoreHeight(message.height);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _updateSilentAddressRecord(BitcoinSilentPaymentsUnspent unspent) {
|
||||
|
@ -430,9 +448,9 @@ abstract class ElectrumWalletBase
|
|||
await _setInitialHeight();
|
||||
}
|
||||
|
||||
await _subscribeForUpdates();
|
||||
|
||||
await subscribeForUpdates();
|
||||
await updateTransactions();
|
||||
|
||||
await updateAllUnspents();
|
||||
await updateBalance();
|
||||
await updateFeeRates();
|
||||
|
@ -537,6 +555,7 @@ abstract class ElectrumWalletBase
|
|||
try {
|
||||
syncStatus = ConnectingSyncStatus();
|
||||
|
||||
await _receiveStream?.cancel();
|
||||
await electrumClient.close();
|
||||
|
||||
electrumClient.onConnectionStatusChange = _onConnectionStatusChange;
|
||||
|
@ -682,26 +701,15 @@ abstract class ElectrumWalletBase
|
|||
paysToSilentPayment: hasSilentPayment,
|
||||
);
|
||||
|
||||
int estimatedSize;
|
||||
if (network is BitcoinCashNetwork) {
|
||||
estimatedSize = ForkedTransactionBuilder.estimateTransactionSize(
|
||||
utxos: utxoDetails.utxos,
|
||||
outputs: outputs,
|
||||
network: network as BitcoinCashNetwork,
|
||||
memo: memo,
|
||||
);
|
||||
} else {
|
||||
estimatedSize = BitcoinTransactionBuilder.estimateTransactionSize(
|
||||
int fee = await calcFee(
|
||||
utxos: utxoDetails.utxos,
|
||||
outputs: outputs,
|
||||
network: network,
|
||||
memo: memo,
|
||||
feeRate: feeRate,
|
||||
inputPrivKeyInfos: utxoDetails.inputPrivKeyInfos,
|
||||
vinOutpoints: utxoDetails.vinOutpoints,
|
||||
);
|
||||
}
|
||||
|
||||
int fee = feeAmountWithFeeRate(feeRate, 0, 0, size: estimatedSize);
|
||||
|
||||
if (fee == 0) {
|
||||
throw BitcoinTransactionNoFeeException();
|
||||
|
@ -788,7 +796,10 @@ abstract class ElectrumWalletBase
|
|||
throw BitcoinTransactionWrongBalanceException();
|
||||
}
|
||||
|
||||
final changeAddress = await walletAddresses.getChangeAddress();
|
||||
final changeAddress = await walletAddresses.getChangeAddress(
|
||||
outputs: outputs,
|
||||
utxoDetails: utxoDetails,
|
||||
);
|
||||
final address = RegexUtils.addressTypeFromStr(changeAddress, network);
|
||||
outputs.add(BitcoinOutput(
|
||||
address: address,
|
||||
|
@ -796,26 +807,13 @@ abstract class ElectrumWalletBase
|
|||
isChange: true,
|
||||
));
|
||||
|
||||
int estimatedSize;
|
||||
if (network is BitcoinCashNetwork) {
|
||||
estimatedSize = ForkedTransactionBuilder.estimateTransactionSize(
|
||||
utxos: utxoDetails.utxos,
|
||||
outputs: outputs,
|
||||
network: network as BitcoinCashNetwork,
|
||||
memo: memo,
|
||||
);
|
||||
} else {
|
||||
estimatedSize = BitcoinTransactionBuilder.estimateTransactionSize(
|
||||
int fee = await calcFee(
|
||||
utxos: utxoDetails.utxos,
|
||||
outputs: outputs,
|
||||
network: network,
|
||||
memo: memo,
|
||||
inputPrivKeyInfos: utxoDetails.inputPrivKeyInfos,
|
||||
vinOutpoints: utxoDetails.vinOutpoints,
|
||||
feeRate: feeRate,
|
||||
);
|
||||
}
|
||||
|
||||
int fee = feeAmountWithFeeRate(feeRate, 0, 0, size: estimatedSize);
|
||||
|
||||
if (fee == 0) {
|
||||
throw BitcoinTransactionNoFeeException();
|
||||
|
@ -825,6 +823,8 @@ abstract class ElectrumWalletBase
|
|||
final lastOutput = outputs.last;
|
||||
final amountLeftForChange = amountLeftForChangeAndFee - fee;
|
||||
|
||||
print(amountLeftForChangeAndFee);
|
||||
|
||||
if (!_isBelowDust(amountLeftForChange)) {
|
||||
// Here, lastOutput already is change, return the amount left without the fee to the user's address.
|
||||
outputs[outputs.length - 1] = BitcoinOutput(
|
||||
|
@ -874,7 +874,7 @@ abstract class ElectrumWalletBase
|
|||
|
||||
final totalAmount = amount + fee;
|
||||
|
||||
if (totalAmount > balance[currency]!.confirmed) {
|
||||
if (totalAmount > (balance[currency]!.confirmed + balance[currency]!.secondConfirmed)) {
|
||||
throw BitcoinTransactionWrongBalanceException();
|
||||
}
|
||||
|
||||
|
@ -909,6 +909,37 @@ abstract class ElectrumWalletBase
|
|||
);
|
||||
}
|
||||
|
||||
Future<int> calcFee({
|
||||
required List<UtxoWithAddress> utxos,
|
||||
required List<BitcoinBaseOutput> outputs,
|
||||
required BasedUtxoNetwork network,
|
||||
String? memo,
|
||||
required int feeRate,
|
||||
List<ECPrivateInfo>? inputPrivKeyInfos,
|
||||
List<Outpoint>? vinOutpoints,
|
||||
}) async {
|
||||
int estimatedSize;
|
||||
if (network is BitcoinCashNetwork) {
|
||||
estimatedSize = ForkedTransactionBuilder.estimateTransactionSize(
|
||||
utxos: utxos,
|
||||
outputs: outputs,
|
||||
network: network,
|
||||
memo: memo,
|
||||
);
|
||||
} else {
|
||||
estimatedSize = BitcoinTransactionBuilder.estimateTransactionSize(
|
||||
utxos: utxos,
|
||||
outputs: outputs,
|
||||
network: network,
|
||||
memo: memo,
|
||||
inputPrivKeyInfos: inputPrivKeyInfos,
|
||||
vinOutpoints: vinOutpoints,
|
||||
);
|
||||
}
|
||||
|
||||
return feeAmountWithFeeRate(feeRate, 0, 0, size: estimatedSize);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<PendingTransaction> createTransaction(Object credentials) async {
|
||||
try {
|
||||
|
@ -1134,6 +1165,7 @@ abstract class ElectrumWalletBase
|
|||
'derivationPath': walletInfo.derivationInfo?.derivationPath,
|
||||
'silent_addresses': walletAddresses.silentAddresses.map((addr) => addr.toJSON()).toList(),
|
||||
'silent_address_index': walletAddresses.currentSilentAddressIndex.toString(),
|
||||
'mweb_addresses': walletAddresses.mwebAddresses.map((addr) => addr.toJSON()).toList(),
|
||||
});
|
||||
|
||||
int feeRate(TransactionPriority priority) {
|
||||
|
@ -1253,6 +1285,7 @@ abstract class ElectrumWalletBase
|
|||
@override
|
||||
Future<void> close() async {
|
||||
try {
|
||||
await _receiveStream?.cancel();
|
||||
await electrumClient.close();
|
||||
} catch (_) {}
|
||||
_autoSaveTimer?.cancel();
|
||||
|
@ -1283,41 +1316,19 @@ abstract class ElectrumWalletBase
|
|||
|
||||
unspentCoins = updatedUnspentCoins;
|
||||
|
||||
if (unspentCoinsInfo.isEmpty) {
|
||||
unspentCoins.forEach((coin) => _addCoinInfo(coin));
|
||||
if (unspentCoinsInfo.length != updatedUnspentCoins.length) {
|
||||
unspentCoins.forEach((coin) => addCoinInfo(coin));
|
||||
return;
|
||||
}
|
||||
|
||||
if (unspentCoins.isNotEmpty) {
|
||||
unspentCoins.forEach((coin) {
|
||||
final coinInfoList = unspentCoinsInfo.values.where((element) =>
|
||||
element.walletId.contains(id) &&
|
||||
element.hash.contains(coin.hash) &&
|
||||
element.vout == coin.vout);
|
||||
|
||||
if (coinInfoList.isNotEmpty) {
|
||||
final coinInfo = coinInfoList.first;
|
||||
|
||||
coin.isFrozen = coinInfo.isFrozen;
|
||||
coin.isSending = coinInfo.isSending;
|
||||
coin.note = coinInfo.note;
|
||||
if (coin.bitcoinAddressRecord is! BitcoinSilentPaymentAddressRecord)
|
||||
coin.bitcoinAddressRecord.balance += coinInfo.value;
|
||||
} else {
|
||||
_addCoinInfo(coin);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
await updateCoins(unspentCoins);
|
||||
await _refreshUnspentCoinsInfo();
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> updateUnspents(BitcoinAddressRecord address) async {
|
||||
final newUnspentCoins = await fetchUnspent(address);
|
||||
|
||||
if (newUnspentCoins.isNotEmpty) {
|
||||
unspentCoins.addAll(newUnspentCoins);
|
||||
Future<void> updateCoins(List<BitcoinUnspent> newUnspentCoins) async {
|
||||
if (newUnspentCoins.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
newUnspentCoins.forEach((coin) {
|
||||
final coinInfoList = unspentCoinsInfo.values.where(
|
||||
|
@ -1336,18 +1347,24 @@ abstract class ElectrumWalletBase
|
|||
if (coin.bitcoinAddressRecord is! BitcoinSilentPaymentAddressRecord)
|
||||
coin.bitcoinAddressRecord.balance += coinInfo.value;
|
||||
} else {
|
||||
_addCoinInfo(coin);
|
||||
addCoinInfo(coin);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> updateUnspentsForAddress(BitcoinAddressRecord address) async {
|
||||
final newUnspentCoins = await fetchUnspent(address);
|
||||
await updateCoins(newUnspentCoins);
|
||||
}
|
||||
|
||||
@action
|
||||
Future<List<BitcoinUnspent>> fetchUnspent(BitcoinAddressRecord address) async {
|
||||
final unspents = await electrumClient.getListUnspent(address.getScriptHash(network));
|
||||
|
||||
List<Map<String, dynamic>> unspents = [];
|
||||
List<BitcoinUnspent> updatedUnspentCoins = [];
|
||||
|
||||
unspents = await electrumClient.getListUnspent(address.getScriptHash(network));
|
||||
|
||||
await Future.wait(unspents.map((unspent) async {
|
||||
try {
|
||||
final coin = BitcoinUnspent.fromJSON(address, unspent);
|
||||
|
@ -1363,7 +1380,7 @@ abstract class ElectrumWalletBase
|
|||
}
|
||||
|
||||
@action
|
||||
Future<void> _addCoinInfo(BitcoinUnspent coin) async {
|
||||
Future<void> addCoinInfo(BitcoinUnspent coin) async {
|
||||
final newInfo = UnspentCoinsInfo(
|
||||
walletId: id,
|
||||
hash: coin.hash,
|
||||
|
@ -1714,12 +1731,14 @@ abstract class ElectrumWalletBase
|
|||
final Map<String, ElectrumTransactionInfo> historiesWithDetails = {};
|
||||
|
||||
if (type == WalletType.bitcoin) {
|
||||
await Future.wait(ADDRESS_TYPES
|
||||
await Future.wait(BITCOIN_ADDRESS_TYPES
|
||||
.map((type) => fetchTransactionsForAddressType(historiesWithDetails, type)));
|
||||
} else if (type == WalletType.bitcoinCash) {
|
||||
await fetchTransactionsForAddressType(historiesWithDetails, P2pkhAddressType.p2pkh);
|
||||
await Future.wait(BITCOIN_CASH_ADDRESS_TYPES
|
||||
.map((type) => fetchTransactionsForAddressType(historiesWithDetails, type)));
|
||||
} else if (type == WalletType.litecoin) {
|
||||
await fetchTransactionsForAddressType(historiesWithDetails, SegwitAddresType.p2wpkh);
|
||||
await Future.wait(LITECOIN_ADDRESS_TYPES
|
||||
.map((type) => fetchTransactionsForAddressType(historiesWithDetails, type)));
|
||||
}
|
||||
|
||||
transactionHistory.transactions.values.forEach((tx) async {
|
||||
|
@ -1776,7 +1795,7 @@ abstract class ElectrumWalletBase
|
|||
matchedAddresses.toList(),
|
||||
addressRecord.isHidden,
|
||||
(address) async {
|
||||
await _subscribeForUpdates();
|
||||
await subscribeForUpdates();
|
||||
return _fetchAddressHistory(address, await getCurrentChainTip())
|
||||
.then((history) => history.isNotEmpty ? address.address : null);
|
||||
},
|
||||
|
@ -1865,7 +1884,7 @@ abstract class ElectrumWalletBase
|
|||
}
|
||||
}
|
||||
|
||||
Future<void> _subscribeForUpdates() async {
|
||||
Future<void> subscribeForUpdates() async {
|
||||
final unsubscribedScriptHashes = walletAddresses.allAddresses.where(
|
||||
(address) => !_scripthashesUpdateSubject.containsKey(address.getScriptHash(network)),
|
||||
);
|
||||
|
@ -1876,7 +1895,7 @@ abstract class ElectrumWalletBase
|
|||
_scripthashesUpdateSubject[sh] = await electrumClient.scripthashUpdate(sh);
|
||||
_scripthashesUpdateSubject[sh]?.listen((event) async {
|
||||
try {
|
||||
await updateUnspents(address);
|
||||
await updateUnspentsForAddress(address);
|
||||
|
||||
await updateBalance();
|
||||
|
||||
|
@ -1893,8 +1912,10 @@ abstract class ElectrumWalletBase
|
|||
}));
|
||||
}
|
||||
|
||||
Future<ElectrumBalance> _fetchBalances() async {
|
||||
final addresses = walletAddresses.allAddresses.toList();
|
||||
Future<ElectrumBalance> fetchBalances() async {
|
||||
final addresses = walletAddresses.allAddresses
|
||||
.where((address) => RegexUtils.addressTypeFromStr(address.address, network) is! MwebAddress)
|
||||
.toList();
|
||||
final balanceFutures = <Future<Map<String, dynamic>>>[];
|
||||
for (var i = 0; i < addresses.length; i++) {
|
||||
final addressRecord = addresses[i];
|
||||
|
@ -1944,6 +1965,7 @@ abstract class ElectrumWalletBase
|
|||
totalConfirmed += confirmed;
|
||||
totalUnconfirmed += unconfirmed;
|
||||
|
||||
addressRecord.balance = confirmed + unconfirmed;
|
||||
if (confirmed > 0 || unconfirmed > 0) {
|
||||
addressRecord.setAsUsed();
|
||||
}
|
||||
|
@ -1957,22 +1979,10 @@ abstract class ElectrumWalletBase
|
|||
}
|
||||
|
||||
Future<void> updateBalance() async {
|
||||
balance[currency] = await _fetchBalances();
|
||||
balance[currency] = await fetchBalances();
|
||||
await save();
|
||||
}
|
||||
|
||||
String getChangeAddress() {
|
||||
const minCountOfHiddenAddresses = 5;
|
||||
final random = Random();
|
||||
var addresses = walletAddresses.allAddresses.where((addr) => addr.isHidden).toList();
|
||||
|
||||
if (addresses.length < minCountOfHiddenAddresses) {
|
||||
addresses = walletAddresses.allAddresses.toList();
|
||||
}
|
||||
|
||||
return addresses[random.nextInt(addresses.length)].address;
|
||||
}
|
||||
|
||||
@override
|
||||
void setExceptionHandler(void Function(FlutterErrorDetails) onError) => _onError = onError;
|
||||
|
||||
|
@ -2475,6 +2485,8 @@ BitcoinAddressType _getScriptType(BitcoinBaseAddress type) {
|
|||
return SegwitAddresType.p2wsh;
|
||||
} else if (type is P2trAddress) {
|
||||
return SegwitAddresType.p2tr;
|
||||
} else if (type is MwebAddress) {
|
||||
return SegwitAddresType.mweb;
|
||||
} else if (type is SilentPaymentsAddresType) {
|
||||
return SilentPaymentsAddresType.p2sp;
|
||||
} else {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||
import 'package:blockchain_utils/blockchain_utils.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
||||
import 'package:cw_bitcoin/electrum_wallet.dart';
|
||||
import 'package:cw_core/wallet_addresses.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
|
@ -10,7 +11,7 @@ part 'electrum_wallet_addresses.g.dart';
|
|||
|
||||
class ElectrumWalletAddresses = ElectrumWalletAddressesBase with _$ElectrumWalletAddresses;
|
||||
|
||||
const List<BitcoinAddressType> ADDRESS_TYPES = [
|
||||
const List<BitcoinAddressType> BITCOIN_ADDRESS_TYPES = [
|
||||
SegwitAddresType.p2wpkh,
|
||||
P2pkhAddressType.p2pkh,
|
||||
SegwitAddresType.p2tr,
|
||||
|
@ -18,6 +19,15 @@ const List<BitcoinAddressType> ADDRESS_TYPES = [
|
|||
P2shAddressType.p2wpkhInP2sh,
|
||||
];
|
||||
|
||||
const List<BitcoinAddressType> LITECOIN_ADDRESS_TYPES = [
|
||||
SegwitAddresType.p2wpkh,
|
||||
SegwitAddresType.mweb,
|
||||
];
|
||||
|
||||
const List<BitcoinAddressType> BITCOIN_CASH_ADDRESS_TYPES = [
|
||||
P2pkhAddressType.p2pkh,
|
||||
];
|
||||
|
||||
abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
||||
ElectrumWalletAddressesBase(
|
||||
WalletInfo walletInfo, {
|
||||
|
@ -29,6 +39,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
Map<String, int>? initialChangeAddressIndex,
|
||||
List<BitcoinSilentPaymentAddressRecord>? initialSilentAddresses,
|
||||
int initialSilentAddressIndex = 0,
|
||||
List<BitcoinAddressRecord>? initialMwebAddresses,
|
||||
Bip32Slip10Secp256k1? masterHd,
|
||||
BitcoinAddressType? initialAddressPageType,
|
||||
}) : _addresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? []).toSet()),
|
||||
|
@ -49,6 +60,8 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
silentAddresses = ObservableList<BitcoinSilentPaymentAddressRecord>.of(
|
||||
(initialSilentAddresses ?? []).toSet()),
|
||||
currentSilentAddressIndex = initialSilentAddressIndex,
|
||||
mwebAddresses =
|
||||
ObservableList<BitcoinAddressRecord>.of((initialMwebAddresses ?? []).toSet()),
|
||||
super(walletInfo) {
|
||||
if (masterHd != null) {
|
||||
silentAddress = SilentPaymentOwner.fromPrivateKeys(
|
||||
|
@ -91,6 +104,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
final ObservableList<BitcoinAddressRecord> receiveAddresses;
|
||||
final ObservableList<BitcoinAddressRecord> changeAddresses;
|
||||
final ObservableList<BitcoinSilentPaymentAddressRecord> silentAddresses;
|
||||
final ObservableList<BitcoinAddressRecord> mwebAddresses;
|
||||
final BasedUtxoNetwork network;
|
||||
final Bip32Slip10Secp256k1 mainHd;
|
||||
final Bip32Slip10Secp256k1 sideHd;
|
||||
|
@ -213,7 +227,8 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
if (walletInfo.type == WalletType.bitcoinCash) {
|
||||
await _generateInitialAddresses(type: P2pkhAddressType.p2pkh);
|
||||
} else if (walletInfo.type == WalletType.litecoin) {
|
||||
await _generateInitialAddresses();
|
||||
await _generateInitialAddresses(type: SegwitAddresType.p2wpkh);
|
||||
await _generateInitialAddresses(type: SegwitAddresType.mweb);
|
||||
} else if (walletInfo.type == WalletType.bitcoin) {
|
||||
await _generateInitialAddresses();
|
||||
await _generateInitialAddresses(type: P2pkhAddressType.p2pkh);
|
||||
|
@ -221,6 +236,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
await _generateInitialAddresses(type: SegwitAddresType.p2tr);
|
||||
await _generateInitialAddresses(type: SegwitAddresType.p2wsh);
|
||||
}
|
||||
|
||||
updateAddressesByMatch();
|
||||
updateReceiveAddresses();
|
||||
updateChangeAddresses();
|
||||
|
@ -237,7 +253,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
}
|
||||
|
||||
@action
|
||||
Future<String> getChangeAddress() async {
|
||||
Future<String> getChangeAddress({List<BitcoinOutput>? outputs, UtxoDetails? utxoDetails}) async {
|
||||
updateChangeAddresses();
|
||||
|
||||
if (changeAddresses.isEmpty) {
|
||||
|
@ -317,23 +333,21 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
return address;
|
||||
}
|
||||
|
||||
String getAddress(
|
||||
{required int index,
|
||||
String getAddress({
|
||||
required int index,
|
||||
required Bip32Slip10Secp256k1 hd,
|
||||
BitcoinAddressType? addressType}) =>
|
||||
BitcoinAddressType? addressType,
|
||||
}) =>
|
||||
'';
|
||||
|
||||
@override
|
||||
Future<void> updateAddressesInBox() async {
|
||||
try {
|
||||
addressesMap.clear();
|
||||
addressesMap[address] = 'Active';
|
||||
|
||||
allAddressesMap.clear();
|
||||
_addresses.forEach((addressRecord) {
|
||||
allAddressesMap[addressRecord.address] = addressRecord.name;
|
||||
});
|
||||
Future<String> getAddressAsync({
|
||||
required int index,
|
||||
required Bip32Slip10Secp256k1 hd,
|
||||
BitcoinAddressType? addressType,
|
||||
}) async =>
|
||||
getAddress(index: index, hd: hd, addressType: addressType);
|
||||
|
||||
void addBitcoinAddressTypes() {
|
||||
final lastP2wpkh = _addresses
|
||||
.where((addressRecord) =>
|
||||
_isUnusedReceiveAddressByType(addressRecord, SegwitAddresType.p2wpkh))
|
||||
|
@ -390,6 +404,63 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
addressesMap[address] = 'Active - Silent Payments';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void addLitecoinAddressTypes() {
|
||||
final lastP2wpkh = _addresses
|
||||
.where((addressRecord) =>
|
||||
_isUnusedReceiveAddressByType(addressRecord, SegwitAddresType.p2wpkh))
|
||||
.toList()
|
||||
.last;
|
||||
if (lastP2wpkh.address != address) {
|
||||
addressesMap[lastP2wpkh.address] = 'P2WPKH';
|
||||
} else {
|
||||
addressesMap[address] = 'Active - P2WPKH';
|
||||
}
|
||||
|
||||
final lastMweb = _addresses.firstWhere(
|
||||
(addressRecord) => _isUnusedReceiveAddressByType(addressRecord, SegwitAddresType.mweb));
|
||||
if (lastMweb.address != address) {
|
||||
addressesMap[lastMweb.address] = 'MWEB';
|
||||
} else {
|
||||
addressesMap[address] = 'Active - MWEB';
|
||||
}
|
||||
}
|
||||
|
||||
void addBitcoinCashAddressTypes() {
|
||||
final lastP2pkh = _addresses.firstWhere(
|
||||
(addressRecord) => _isUnusedReceiveAddressByType(addressRecord, P2pkhAddressType.p2pkh));
|
||||
if (lastP2pkh.address != address) {
|
||||
addressesMap[lastP2pkh.address] = 'P2PKH';
|
||||
} else {
|
||||
addressesMap[address] = 'Active - P2PKH';
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateAddressesInBox() async {
|
||||
try {
|
||||
addressesMap.clear();
|
||||
addressesMap[address] = 'Active';
|
||||
|
||||
allAddressesMap.clear();
|
||||
_addresses.forEach((addressRecord) {
|
||||
allAddressesMap[addressRecord.address] = addressRecord.name;
|
||||
});
|
||||
|
||||
switch (walletInfo.type) {
|
||||
case WalletType.bitcoin:
|
||||
addBitcoinAddressTypes();
|
||||
break;
|
||||
case WalletType.litecoin:
|
||||
addLitecoinAddressTypes();
|
||||
break;
|
||||
case WalletType.bitcoinCash:
|
||||
addBitcoinCashAddressTypes();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
await saveAddressesInBox();
|
||||
} catch (e) {
|
||||
|
@ -410,6 +481,11 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
foundAddress = addressRecord;
|
||||
}
|
||||
});
|
||||
mwebAddresses.forEach((addressRecord) {
|
||||
if (addressRecord.address == address) {
|
||||
foundAddress = addressRecord;
|
||||
}
|
||||
});
|
||||
|
||||
if (foundAddress != null) {
|
||||
foundAddress!.setNewName(label);
|
||||
|
@ -510,7 +586,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
|
||||
for (var i = startIndex; i < count + startIndex; i++) {
|
||||
final address = BitcoinAddressRecord(
|
||||
getAddress(index: i, hd: _getHd(isHidden), addressType: type ?? addressPageType),
|
||||
await getAddressAsync(index: i, hd: _getHd(isHidden), addressType: type ?? addressPageType),
|
||||
index: i,
|
||||
isHidden: isHidden,
|
||||
type: type ?? addressPageType,
|
||||
|
@ -540,15 +616,28 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
updateAddressesByMatch();
|
||||
}
|
||||
|
||||
@action
|
||||
void addMwebAddresses(Iterable<BitcoinAddressRecord> addresses) {
|
||||
final addressesSet = this.mwebAddresses.toSet();
|
||||
addressesSet.addAll(addresses);
|
||||
this.mwebAddresses.clear();
|
||||
this.mwebAddresses.addAll(addressesSet);
|
||||
updateAddressesByMatch();
|
||||
}
|
||||
|
||||
void _validateAddresses() {
|
||||
_addresses.forEach((element) {
|
||||
_addresses.forEach((element) async {
|
||||
if (element.type == SegwitAddresType.mweb) {
|
||||
// this would add a ton of startup lag for mweb addresses since we have 1000 of them
|
||||
return;
|
||||
}
|
||||
if (!element.isHidden &&
|
||||
element.address !=
|
||||
getAddress(index: element.index, hd: mainHd, addressType: element.type)) {
|
||||
await getAddressAsync(index: element.index, hd: mainHd, addressType: element.type)) {
|
||||
element.isHidden = true;
|
||||
} else if (element.isHidden &&
|
||||
element.address !=
|
||||
getAddress(index: element.index, hd: sideHd, addressType: element.type)) {
|
||||
await getAddressAsync(index: element.index, hd: sideHd, addressType: element.type)) {
|
||||
element.isHidden = false;
|
||||
}
|
||||
});
|
||||
|
|
|
@ -23,6 +23,7 @@ class ElectrumWalletSnapshot {
|
|||
required this.addressPageType,
|
||||
required this.silentAddresses,
|
||||
required this.silentAddressIndex,
|
||||
required this.mwebAddresses,
|
||||
this.passphrase,
|
||||
this.derivationType,
|
||||
this.derivationPath,
|
||||
|
@ -44,6 +45,8 @@ class ElectrumWalletSnapshot {
|
|||
|
||||
List<BitcoinAddressRecord> addresses;
|
||||
List<BitcoinSilentPaymentAddressRecord> silentAddresses;
|
||||
List<BitcoinAddressRecord> mwebAddresses;
|
||||
|
||||
ElectrumBalance balance;
|
||||
Map<String, int> regularAddressIndex;
|
||||
Map<String, int> changeAddressIndex;
|
||||
|
@ -56,10 +59,11 @@ class ElectrumWalletSnapshot {
|
|||
final path = await pathForWallet(name: name, type: type);
|
||||
final jsonSource = await encryptionFileUtils.read(path: path, password: password);
|
||||
final data = json.decode(jsonSource) as Map;
|
||||
final addressesTmp = data['addresses'] as List? ?? <Object>[];
|
||||
final mnemonic = data['mnemonic'] as String?;
|
||||
final xpub = data['xpub'] as String?;
|
||||
final passphrase = data['passphrase'] as String? ?? '';
|
||||
|
||||
final addressesTmp = data['addresses'] as List? ?? <Object>[];
|
||||
final addresses = addressesTmp
|
||||
.whereType<String>()
|
||||
.map((addr) => BitcoinAddressRecord.fromJSON(addr, network: network))
|
||||
|
@ -71,6 +75,12 @@ class ElectrumWalletSnapshot {
|
|||
.map((addr) => BitcoinSilentPaymentAddressRecord.fromJSON(addr, network: network))
|
||||
.toList();
|
||||
|
||||
final mwebAddressTmp = data['mweb_addresses'] as List? ?? <Object>[];
|
||||
final mwebAddresses = mwebAddressTmp
|
||||
.whereType<String>()
|
||||
.map((addr) => BitcoinAddressRecord.fromJSON(addr, network: network))
|
||||
.toList();
|
||||
|
||||
final balance = ElectrumBalance.fromJSON(data['balance'] as String?) ??
|
||||
ElectrumBalance(confirmed: 0, unconfirmed: 0, frozen: 0);
|
||||
var regularAddressIndexByType = {SegwitAddresType.p2wpkh.toString(): 0};
|
||||
|
@ -113,6 +123,7 @@ class ElectrumWalletSnapshot {
|
|||
derivationPath: derivationPath,
|
||||
silentAddresses: silentAddresses,
|
||||
silentAddressIndex: silentAddressIndex,
|
||||
mwebAddresses: mwebAddresses,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,15 +1,31 @@
|
|||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:convert/convert.dart' as convert;
|
||||
import 'dart:math';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:crypto/crypto.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart';
|
||||
import 'package:cw_core/cake_hive.dart';
|
||||
import 'package:cw_core/mweb_utxo.dart';
|
||||
import 'package:cw_mweb/mwebd.pbgrpc.dart';
|
||||
import 'package:fixnum/fixnum.dart';
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||
import 'package:blockchain_utils/blockchain_utils.dart';
|
||||
import 'package:blockchain_utils/signer/ecdsa_signing_key.dart';
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_unspent.dart';
|
||||
import 'package:cw_bitcoin/electrum_transaction_info.dart';
|
||||
import 'package:cw_bitcoin/pending_bitcoin_transaction.dart';
|
||||
import 'package:cw_bitcoin/utils.dart';
|
||||
import 'package:cw_bitcoin/electrum_derivations.dart';
|
||||
import 'package:cw_core/encryption_file_utils.dart';
|
||||
import 'package:cw_core/crypto_currency.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_bitcoin/electrum_balance.dart';
|
||||
import 'package:cw_bitcoin/electrum_wallet.dart';
|
||||
|
@ -19,8 +35,11 @@ import 'package:cw_core/transaction_priority.dart';
|
|||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_keys_file.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:grpc/grpc.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cw_mweb/cw_mweb.dart';
|
||||
import 'package:bitcoin_base/src/crypto/keypair/sign_utils.dart';
|
||||
import 'package:pointycastle/ecc/api.dart';
|
||||
import 'package:pointycastle/ecc/curves/secp256k1.dart';
|
||||
|
@ -40,9 +59,12 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
String? passphrase,
|
||||
String? addressPageType,
|
||||
List<BitcoinAddressRecord>? initialAddresses,
|
||||
List<BitcoinAddressRecord>? initialMwebAddresses,
|
||||
ElectrumBalance? initialBalance,
|
||||
Map<String, int>? initialRegularAddressIndex,
|
||||
Map<String, int>? initialChangeAddressIndex,
|
||||
int? initialMwebHeight,
|
||||
bool? alwaysScan,
|
||||
}) : super(
|
||||
mnemonic: mnemonic,
|
||||
password: password,
|
||||
|
@ -53,21 +75,38 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
initialBalance: initialBalance,
|
||||
seedBytes: seedBytes,
|
||||
encryptionFileUtils: encryptionFileUtils,
|
||||
passphrase: passphrase,
|
||||
currency: CryptoCurrency.ltc) {
|
||||
currency: CryptoCurrency.ltc,
|
||||
) {
|
||||
mwebHd = Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath("m/1000'") as Bip32Slip10Secp256k1;
|
||||
mwebEnabled = alwaysScan ?? false;
|
||||
walletAddresses = LitecoinWalletAddresses(
|
||||
walletInfo,
|
||||
initialAddresses: initialAddresses,
|
||||
initialRegularAddressIndex: initialRegularAddressIndex,
|
||||
initialChangeAddressIndex: initialChangeAddressIndex,
|
||||
initialMwebAddresses: initialMwebAddresses,
|
||||
mainHd: hd,
|
||||
sideHd: accountHD.childKey(Bip32KeyIndex(1)),
|
||||
network: network,
|
||||
mwebHd: mwebHd,
|
||||
mwebEnabled: mwebEnabled,
|
||||
);
|
||||
autorun((_) {
|
||||
this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress;
|
||||
});
|
||||
}
|
||||
late final Bip32Slip10Secp256k1 mwebHd;
|
||||
late final Box<MwebUtxo> mwebUtxosBox;
|
||||
Timer? _syncTimer;
|
||||
Timer? _feeRatesTimer;
|
||||
Timer? _processingTimer;
|
||||
StreamSubscription<Utxo>? _utxoStream;
|
||||
late RpcClient _stub;
|
||||
late bool mwebEnabled;
|
||||
bool processingUtxos = false;
|
||||
|
||||
List<int> get scanSecret => mwebHd.childKey(Bip32KeyIndex(0x80000000)).privateKey.privKey.raw;
|
||||
List<int> get spendSecret => mwebHd.childKey(Bip32KeyIndex(0x80000001)).privateKey.privKey.raw;
|
||||
|
||||
static Future<LitecoinWallet> create(
|
||||
{required String mnemonic,
|
||||
|
@ -78,6 +117,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
String? passphrase,
|
||||
String? addressPageType,
|
||||
List<BitcoinAddressRecord>? initialAddresses,
|
||||
List<BitcoinAddressRecord>? initialMwebAddresses,
|
||||
ElectrumBalance? initialBalance,
|
||||
Map<String, int>? initialRegularAddressIndex,
|
||||
Map<String, int>? initialChangeAddressIndex}) async {
|
||||
|
@ -101,6 +141,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfo,
|
||||
initialAddresses: initialAddresses,
|
||||
initialMwebAddresses: initialMwebAddresses,
|
||||
initialBalance: initialBalance,
|
||||
encryptionFileUtils: encryptionFileUtils,
|
||||
passphrase: passphrase,
|
||||
|
@ -111,12 +152,14 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
);
|
||||
}
|
||||
|
||||
static Future<LitecoinWallet> open(
|
||||
{required String name,
|
||||
static Future<LitecoinWallet> open({
|
||||
required String name,
|
||||
required WalletInfo walletInfo,
|
||||
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||
required String password,
|
||||
required EncryptionFileUtils encryptionFileUtils}) async {
|
||||
required bool alwaysScan,
|
||||
required EncryptionFileUtils encryptionFileUtils,
|
||||
}) async {
|
||||
final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type);
|
||||
|
||||
ElectrumWalletSnapshot? snp = null;
|
||||
|
@ -178,6 +221,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfo,
|
||||
initialAddresses: snp?.addresses,
|
||||
initialMwebAddresses: snp?.mwebAddresses,
|
||||
initialBalance: snp?.balance,
|
||||
seedBytes: seedBytes!,
|
||||
passphrase: passphrase,
|
||||
|
@ -185,6 +229,551 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
initialRegularAddressIndex: snp?.regularAddressIndex,
|
||||
initialChangeAddressIndex: snp?.changeAddressIndex,
|
||||
addressPageType: snp?.addressPageType,
|
||||
alwaysScan: alwaysScan,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> waitForMwebAddresses() async {
|
||||
// ensure that we have the full 1000 mweb addresses generated before continuing:
|
||||
// should no longer be needed, but leaving here just in case
|
||||
final mwebAddrs = (walletAddresses as LitecoinWalletAddresses).mwebAddrs;
|
||||
while (mwebAddrs.length < 1000) {
|
||||
print("waiting for mweb addresses to finish generating...");
|
||||
await Future.delayed(const Duration(milliseconds: 1000));
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
@override
|
||||
Future<void> startSync() async {
|
||||
if (syncStatus is SyncronizingSyncStatus) {
|
||||
return;
|
||||
}
|
||||
print("STARTING SYNC - MWEB ENABLED: $mwebEnabled");
|
||||
try {
|
||||
syncStatus = SyncronizingSyncStatus();
|
||||
await subscribeForUpdates();
|
||||
updateFeeRates();
|
||||
|
||||
_feeRatesTimer?.cancel();
|
||||
_feeRatesTimer =
|
||||
Timer.periodic(const Duration(minutes: 1), (timer) async => await updateFeeRates());
|
||||
|
||||
if (!mwebEnabled) {
|
||||
try {
|
||||
await updateAllUnspents();
|
||||
await updateTransactions();
|
||||
await updateBalance();
|
||||
syncStatus = SyncedSyncStatus();
|
||||
} catch (e, s) {
|
||||
print(e);
|
||||
print(s);
|
||||
syncStatus = FailedSyncStatus();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
await waitForMwebAddresses();
|
||||
await getStub();
|
||||
await processMwebUtxos();
|
||||
await updateTransactions();
|
||||
await updateUnspent();
|
||||
await updateBalance();
|
||||
} catch (e) {
|
||||
print("failed to start mweb sync: $e");
|
||||
syncStatus = FailedSyncStatus();
|
||||
return;
|
||||
}
|
||||
|
||||
_syncTimer?.cancel();
|
||||
_syncTimer = Timer.periodic(const Duration(milliseconds: 1500), (timer) async {
|
||||
if (syncStatus is FailedSyncStatus) return;
|
||||
|
||||
final nodeHeight =
|
||||
await electrumClient.getCurrentBlockChainTip() ?? 0; // current block height of our node
|
||||
final resp = await CwMweb.status(StatusRequest());
|
||||
print("resp.mwebUtxosHeight: ${resp.mwebUtxosHeight}");
|
||||
print("resp.mwebHeaderHeight: ${resp.mwebHeaderHeight}");
|
||||
print("resp.blockHeaderHeight: ${resp.blockHeaderHeight}");
|
||||
|
||||
if (resp.blockHeaderHeight < nodeHeight) {
|
||||
int h = resp.blockHeaderHeight;
|
||||
syncStatus = SyncingSyncStatus(nodeHeight - h, h / nodeHeight);
|
||||
} else if (resp.mwebHeaderHeight < nodeHeight) {
|
||||
int h = resp.mwebHeaderHeight;
|
||||
syncStatus = SyncingSyncStatus(nodeHeight - h, h / nodeHeight);
|
||||
} else if (resp.mwebUtxosHeight < nodeHeight) {
|
||||
syncStatus = SyncingSyncStatus(1, 0.999);
|
||||
} else {
|
||||
if (resp.mwebUtxosHeight > walletInfo.restoreHeight) {
|
||||
await walletInfo.updateRestoreHeight(resp.mwebUtxosHeight);
|
||||
await checkMwebUtxosSpent();
|
||||
// update the confirmations for each transaction:
|
||||
for (final transaction in transactionHistory.transactions.values) {
|
||||
if (transaction.isPending) continue;
|
||||
int txHeight = transaction.height ?? resp.mwebUtxosHeight;
|
||||
final confirmations = (resp.mwebUtxosHeight - txHeight) + 1;
|
||||
if (transaction.confirmations == confirmations) continue;
|
||||
transaction.confirmations = confirmations;
|
||||
transactionHistory.addOne(transaction);
|
||||
}
|
||||
await transactionHistory.save();
|
||||
}
|
||||
|
||||
// prevent unnecessary reaction triggers:
|
||||
if (syncStatus is! SyncedSyncStatus) {
|
||||
// mwebd is synced, but we could still be processing incoming utxos:
|
||||
if (!processingUtxos) {
|
||||
syncStatus = SyncedSyncStatus();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
@override
|
||||
Future<void> stopSync() async {
|
||||
_syncTimer?.cancel();
|
||||
_utxoStream?.cancel();
|
||||
_feeRatesTimer?.cancel();
|
||||
await CwMweb.stop();
|
||||
}
|
||||
|
||||
Future<void> initMwebUtxosBox() async {
|
||||
final boxName = "${walletInfo.name.replaceAll(" ", "_")}_${MwebUtxo.boxName}";
|
||||
|
||||
mwebUtxosBox = await CakeHive.openBox<MwebUtxo>(boxName);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> renameWalletFiles(String newWalletName) async {
|
||||
// rename the hive box:
|
||||
final oldBoxName = "${walletInfo.name.replaceAll(" ", "_")}_${MwebUtxo.boxName}";
|
||||
final newBoxName = "${newWalletName.replaceAll(" ", "_")}_${MwebUtxo.boxName}";
|
||||
|
||||
final oldBox = await CakeHive.openBox<MwebUtxo>(oldBoxName);
|
||||
mwebUtxosBox = await CakeHive.openBox<MwebUtxo>(newBoxName);
|
||||
for (final key in oldBox.keys) {
|
||||
await mwebUtxosBox.put(key, oldBox.get(key)!);
|
||||
}
|
||||
oldBox.deleteFromDisk();
|
||||
|
||||
await super.renameWalletFiles(newWalletName);
|
||||
}
|
||||
|
||||
@action
|
||||
@override
|
||||
Future<void> rescan({
|
||||
required int height,
|
||||
int? chainTip,
|
||||
ScanData? scanData,
|
||||
bool? doSingleScan,
|
||||
bool? usingElectrs,
|
||||
}) async {
|
||||
_syncTimer?.cancel();
|
||||
int oldHeight = walletInfo.restoreHeight;
|
||||
await walletInfo.updateRestoreHeight(height);
|
||||
|
||||
// go through mwebUtxos and clear any that are above the new restore height:
|
||||
if (height == 0) {
|
||||
await mwebUtxosBox.clear();
|
||||
transactionHistory.clear();
|
||||
} else {
|
||||
for (final utxo in mwebUtxosBox.values) {
|
||||
if (utxo.height > height) {
|
||||
await mwebUtxosBox.delete(utxo.outputId);
|
||||
}
|
||||
}
|
||||
// TODO: remove transactions that are above the new restore height!
|
||||
}
|
||||
|
||||
// reset coin balances and txCount to 0:
|
||||
unspentCoins.forEach((coin) {
|
||||
if (coin.bitcoinAddressRecord is! BitcoinSilentPaymentAddressRecord)
|
||||
coin.bitcoinAddressRecord.balance = 0;
|
||||
coin.bitcoinAddressRecord.txCount = 0;
|
||||
});
|
||||
|
||||
for (var addressRecord in walletAddresses.allAddresses) {
|
||||
addressRecord.balance = 0;
|
||||
addressRecord.txCount = 0;
|
||||
}
|
||||
|
||||
await startSync();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> init() async {
|
||||
await super.init();
|
||||
await initMwebUtxosBox();
|
||||
}
|
||||
|
||||
Future<void> handleIncoming(MwebUtxo utxo, RpcClient stub) async {
|
||||
final status = await stub.status(StatusRequest());
|
||||
var date = DateTime.now();
|
||||
var confirmations = 0;
|
||||
if (utxo.height > 0) {
|
||||
date = DateTime.fromMillisecondsSinceEpoch(utxo.blockTime * 1000);
|
||||
confirmations = status.blockHeaderHeight - utxo.height + 1;
|
||||
}
|
||||
var tx = transactionHistory.transactions.values
|
||||
.firstWhereOrNull((tx) => tx.outputAddresses?.contains(utxo.outputId) ?? false);
|
||||
|
||||
if (tx == null) {
|
||||
tx = ElectrumTransactionInfo(
|
||||
WalletType.litecoin,
|
||||
id: utxo.outputId,
|
||||
height: utxo.height,
|
||||
amount: utxo.value.toInt(),
|
||||
fee: 0,
|
||||
direction: TransactionDirection.incoming,
|
||||
isPending: utxo.height == 0,
|
||||
date: date,
|
||||
confirmations: confirmations,
|
||||
inputAddresses: [],
|
||||
outputAddresses: [utxo.outputId],
|
||||
isReplaced: false,
|
||||
);
|
||||
}
|
||||
|
||||
// don't update the confirmations if the tx is updated by electrum:
|
||||
if (tx.confirmations == 0 || utxo.height != 0) {
|
||||
tx.height = utxo.height;
|
||||
tx.isPending = utxo.height == 0;
|
||||
tx.confirmations = confirmations;
|
||||
}
|
||||
|
||||
bool isNew = transactionHistory.transactions[tx.id] == null;
|
||||
|
||||
if (!(tx.outputAddresses?.contains(utxo.address) ?? false)) {
|
||||
tx.outputAddresses?.add(utxo.address);
|
||||
isNew = true;
|
||||
}
|
||||
|
||||
if (isNew) {
|
||||
final addressRecord = walletAddresses.allAddresses
|
||||
.firstWhereOrNull((addressRecord) => addressRecord.address == utxo.address);
|
||||
if (addressRecord == null) {
|
||||
print("we don't have this address in the wallet! ${utxo.address}");
|
||||
return;
|
||||
}
|
||||
|
||||
// update the txCount:
|
||||
addressRecord.txCount++;
|
||||
addressRecord.balance += utxo.value.toInt();
|
||||
addressRecord.setAsUsed();
|
||||
}
|
||||
|
||||
transactionHistory.addOne(tx);
|
||||
|
||||
if (isNew) {
|
||||
// update the unconfirmed balance when a new tx is added:
|
||||
// we do this after adding the tx to the history so that sub address balances are updated correctly
|
||||
// (since that calculation is based on the tx history)
|
||||
await updateBalance();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> processMwebUtxos() async {
|
||||
if (!mwebEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
int restoreHeight = walletInfo.restoreHeight;
|
||||
print("SCANNING FROM HEIGHT: $restoreHeight");
|
||||
final req = UtxosRequest(scanSecret: scanSecret, fromHeight: restoreHeight);
|
||||
|
||||
// process new utxos as they come in:
|
||||
_utxoStream?.cancel();
|
||||
ResponseStream<Utxo>? responseStream = await CwMweb.utxos(req);
|
||||
if (responseStream == null) {
|
||||
throw Exception("failed to get utxos stream!");
|
||||
}
|
||||
_utxoStream = responseStream.listen((Utxo sUtxo) async {
|
||||
// we're processing utxos, so our balance could still be innacurate:
|
||||
if (syncStatus is! SyncronizingSyncStatus && syncStatus is! SyncingSyncStatus) {
|
||||
syncStatus = SyncronizingSyncStatus();
|
||||
processingUtxos = true;
|
||||
_processingTimer?.cancel();
|
||||
_processingTimer = Timer.periodic(const Duration(seconds: 2), (timer) async {
|
||||
processingUtxos = false;
|
||||
timer.cancel();
|
||||
});
|
||||
}
|
||||
|
||||
final utxo = MwebUtxo(
|
||||
address: sUtxo.address,
|
||||
blockTime: sUtxo.blockTime,
|
||||
height: sUtxo.height,
|
||||
outputId: sUtxo.outputId,
|
||||
value: sUtxo.value.toInt(),
|
||||
);
|
||||
|
||||
// if (mwebUtxosBox.containsKey(utxo.outputId)) {
|
||||
// // we've already stored this utxo, skip it:
|
||||
// return;
|
||||
// }
|
||||
|
||||
await updateUnspent();
|
||||
await updateBalance();
|
||||
|
||||
final mwebAddrs = (walletAddresses as LitecoinWalletAddresses).mwebAddrs;
|
||||
|
||||
// don't process utxos with addresses that are not in the mwebAddrs list:
|
||||
if (utxo.address.isNotEmpty && !mwebAddrs.contains(utxo.address)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await mwebUtxosBox.put(utxo.outputId, utxo);
|
||||
|
||||
await handleIncoming(utxo, _stub);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> checkMwebUtxosSpent() async {
|
||||
if (!mwebEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
final pendingOutgoingTransactions = transactionHistory.transactions.values
|
||||
.where((tx) => tx.direction == TransactionDirection.outgoing && tx.isPending);
|
||||
|
||||
// check if any of the pending outgoing transactions are now confirmed:
|
||||
bool updatedAny = false;
|
||||
for (final tx in pendingOutgoingTransactions) {
|
||||
updatedAny = await isConfirmed(tx) || updatedAny;
|
||||
}
|
||||
|
||||
// get output ids of all the mweb utxos that have > 0 height:
|
||||
final outputIds =
|
||||
mwebUtxosBox.values.where((utxo) => utxo.height > 0).map((utxo) => utxo.outputId).toList();
|
||||
|
||||
final resp = await CwMweb.spent(SpentRequest(outputId: outputIds));
|
||||
final spent = resp.outputId;
|
||||
if (spent.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
final status = await CwMweb.status(StatusRequest());
|
||||
final height = await electrumClient.getCurrentBlockChainTip();
|
||||
if (height == null || status.blockHeaderHeight != height) return;
|
||||
if (status.mwebUtxosHeight != height) return; // we aren't synced
|
||||
|
||||
int amount = 0;
|
||||
Set<String> inputAddresses = {};
|
||||
var output = convert.AccumulatorSink<Digest>();
|
||||
var input = sha256.startChunkedConversion(output);
|
||||
|
||||
for (final outputId in spent) {
|
||||
final utxo = mwebUtxosBox.get(outputId);
|
||||
await mwebUtxosBox.delete(outputId);
|
||||
if (utxo == null) continue;
|
||||
final addressRecord = walletAddresses.allAddresses
|
||||
.firstWhere((addressRecord) => addressRecord.address == utxo.address);
|
||||
if (!inputAddresses.contains(utxo.address)) {
|
||||
addressRecord.txCount++;
|
||||
}
|
||||
addressRecord.balance -= utxo.value.toInt();
|
||||
amount += utxo.value.toInt();
|
||||
inputAddresses.add(utxo.address);
|
||||
input.add(hex.decode(outputId));
|
||||
}
|
||||
|
||||
if (inputAddresses.isEmpty) return;
|
||||
input.close();
|
||||
var digest = output.events.single;
|
||||
final tx = ElectrumTransactionInfo(
|
||||
WalletType.litecoin,
|
||||
id: digest.toString(),
|
||||
height: height,
|
||||
amount: amount,
|
||||
fee: 0,
|
||||
direction: TransactionDirection.outgoing,
|
||||
isPending: false,
|
||||
date: DateTime.fromMillisecondsSinceEpoch(status.blockTime * 1000),
|
||||
confirmations: 1,
|
||||
inputAddresses: inputAddresses.toList(),
|
||||
outputAddresses: [],
|
||||
isReplaced: false,
|
||||
);
|
||||
|
||||
transactionHistory.addOne(tx);
|
||||
await transactionHistory.save();
|
||||
|
||||
if (updatedAny) {
|
||||
await updateBalance();
|
||||
}
|
||||
}
|
||||
|
||||
// checks if a pending transaction is now confirmed, and updates the tx info accordingly:
|
||||
Future<bool> isConfirmed(ElectrumTransactionInfo tx) async {
|
||||
if (!mwebEnabled) return false;
|
||||
if (!tx.isPending) return false;
|
||||
|
||||
final outputId = <String>[], target = <String>{};
|
||||
final isHash = RegExp(r'^[a-f0-9]{64}$').hasMatch;
|
||||
final spendingOutputIds = tx.inputAddresses?.where(isHash) ?? [];
|
||||
final payingToOutputIds = tx.outputAddresses?.where(isHash) ?? [];
|
||||
outputId.addAll(spendingOutputIds);
|
||||
outputId.addAll(payingToOutputIds);
|
||||
target.addAll(spendingOutputIds);
|
||||
|
||||
for (final outputId in payingToOutputIds) {
|
||||
final spendingTx = transactionHistory.transactions.values
|
||||
.firstWhereOrNull((tx) => tx.inputAddresses?.contains(outputId) ?? false);
|
||||
if (spendingTx != null && !spendingTx.isPending) {
|
||||
target.add(outputId);
|
||||
}
|
||||
}
|
||||
|
||||
if (outputId.isEmpty) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final resp = await CwMweb.spent(SpentRequest(outputId: outputId));
|
||||
if (!setEquals(resp.outputId.toSet(), target)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final status = await CwMweb.status(StatusRequest());
|
||||
tx.height = status.mwebUtxosHeight;
|
||||
tx.confirmations = 1;
|
||||
tx.isPending = false;
|
||||
await transactionHistory.save();
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<void> updateUnspent() async {
|
||||
await checkMwebUtxosSpent();
|
||||
await updateAllUnspents();
|
||||
}
|
||||
|
||||
@override
|
||||
@action
|
||||
Future<void> updateAllUnspents() async {
|
||||
// get ltc unspents:
|
||||
await super.updateAllUnspents();
|
||||
|
||||
if (!mwebEnabled) {
|
||||
return;
|
||||
}
|
||||
await getStub();
|
||||
|
||||
// add the mweb unspents to the list:
|
||||
List<BitcoinUnspent> mwebUnspentCoins = [];
|
||||
// update mweb unspents:
|
||||
final mwebAddrs = (walletAddresses as LitecoinWalletAddresses).mwebAddrs;
|
||||
mwebUtxosBox.keys.forEach((dynamic oId) {
|
||||
final String outputId = oId as String;
|
||||
final utxo = mwebUtxosBox.get(outputId);
|
||||
if (utxo == null) {
|
||||
return;
|
||||
}
|
||||
if (utxo.address.isEmpty) {
|
||||
// not sure if a bug or a special case but we definitely ignore these
|
||||
return;
|
||||
}
|
||||
final addressRecord = walletAddresses.allAddresses
|
||||
.firstWhereOrNull((addressRecord) => addressRecord.address == utxo.address);
|
||||
|
||||
if (addressRecord == null) {
|
||||
print("utxo contains an address that is not in the wallet: ${utxo.address}");
|
||||
return;
|
||||
}
|
||||
final unspent = BitcoinUnspent(
|
||||
addressRecord,
|
||||
outputId,
|
||||
utxo.value.toInt(),
|
||||
mwebAddrs.indexOf(utxo.address),
|
||||
);
|
||||
if (unspent.vout == 0) {
|
||||
unspent.isChange = true;
|
||||
}
|
||||
mwebUnspentCoins.add(unspent);
|
||||
});
|
||||
unspentCoins.addAll(mwebUnspentCoins);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<ElectrumBalance> fetchBalances() async {
|
||||
final balance = await super.fetchBalances();
|
||||
if (!mwebEnabled) {
|
||||
return balance;
|
||||
}
|
||||
await getStub();
|
||||
|
||||
// update unspent balances:
|
||||
await updateUnspent();
|
||||
|
||||
int confirmed = balance.confirmed;
|
||||
int unconfirmed = balance.unconfirmed;
|
||||
int confirmedMweb = 0;
|
||||
int unconfirmedMweb = 0;
|
||||
try {
|
||||
mwebUtxosBox.values.forEach((utxo) {
|
||||
if (utxo.height > 0) {
|
||||
confirmedMweb += utxo.value.toInt();
|
||||
} else {
|
||||
unconfirmedMweb += utxo.value.toInt();
|
||||
}
|
||||
});
|
||||
if (unconfirmedMweb > 0) {
|
||||
unconfirmedMweb = -1 * (confirmedMweb - unconfirmedMweb);
|
||||
}
|
||||
} catch (_) {}
|
||||
|
||||
for (var addressRecord in walletAddresses.allAddresses) {
|
||||
addressRecord.balance = 0;
|
||||
addressRecord.txCount = 0;
|
||||
}
|
||||
|
||||
unspentCoins.forEach((coin) {
|
||||
final coinInfoList = unspentCoinsInfo.values.where(
|
||||
(element) =>
|
||||
element.walletId.contains(id) &&
|
||||
element.hash.contains(coin.hash) &&
|
||||
element.vout == coin.vout,
|
||||
);
|
||||
|
||||
if (coinInfoList.isNotEmpty) {
|
||||
final coinInfo = coinInfoList.first;
|
||||
|
||||
coin.isFrozen = coinInfo.isFrozen;
|
||||
coin.isSending = coinInfo.isSending;
|
||||
coin.note = coinInfo.note;
|
||||
if (coin.bitcoinAddressRecord is! BitcoinSilentPaymentAddressRecord)
|
||||
coin.bitcoinAddressRecord.balance += coinInfo.value;
|
||||
} else {
|
||||
super.addCoinInfo(coin);
|
||||
}
|
||||
});
|
||||
|
||||
// update the txCount for each address using the tx history, since we can't rely on mwebd
|
||||
// to have an accurate count, we should just keep it in sync with what we know from the tx history:
|
||||
for (final tx in transactionHistory.transactions.values) {
|
||||
// if (tx.isPending) continue;
|
||||
if (tx.inputAddresses == null || tx.outputAddresses == null) {
|
||||
continue;
|
||||
}
|
||||
final txAddresses = tx.inputAddresses! + tx.outputAddresses!;
|
||||
for (final address in txAddresses) {
|
||||
final addressRecord = walletAddresses.allAddresses
|
||||
.firstWhereOrNull((addressRecord) => addressRecord.address == address);
|
||||
if (addressRecord == null) {
|
||||
continue;
|
||||
}
|
||||
addressRecord.txCount++;
|
||||
}
|
||||
}
|
||||
|
||||
return ElectrumBalance(
|
||||
confirmed: confirmed,
|
||||
unconfirmed: unconfirmed,
|
||||
frozen: balance.frozen,
|
||||
secondConfirmed: confirmedMweb,
|
||||
secondUnconfirmed: unconfirmedMweb,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -204,6 +793,227 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
return 0;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<int> calcFee({
|
||||
required List<UtxoWithAddress> utxos,
|
||||
required List<BitcoinBaseOutput> outputs,
|
||||
required BasedUtxoNetwork network,
|
||||
String? memo,
|
||||
required int feeRate,
|
||||
List<ECPrivateInfo>? inputPrivKeyInfos,
|
||||
List<Outpoint>? vinOutpoints,
|
||||
}) async {
|
||||
final spendsMweb = utxos.any((utxo) => utxo.utxo.scriptType == SegwitAddresType.mweb);
|
||||
final paysToMweb = outputs
|
||||
.any((output) => output.toOutput.scriptPubKey.getAddressType() == SegwitAddresType.mweb);
|
||||
if (!spendsMweb && !paysToMweb) {
|
||||
return await super.calcFee(
|
||||
utxos: utxos,
|
||||
outputs: outputs,
|
||||
network: network,
|
||||
memo: memo,
|
||||
feeRate: feeRate,
|
||||
inputPrivKeyInfos: inputPrivKeyInfos,
|
||||
vinOutpoints: vinOutpoints,
|
||||
);
|
||||
}
|
||||
|
||||
if (!mwebEnabled) {
|
||||
throw Exception("MWEB is not enabled! can't calculate fee without starting the mweb server!");
|
||||
}
|
||||
|
||||
if (outputs.length == 1 && outputs[0].toOutput.amount == BigInt.zero) {
|
||||
outputs = [
|
||||
BitcoinScriptOutput(
|
||||
script: outputs[0].toOutput.scriptPubKey, value: utxos.sumOfUtxosValue())
|
||||
];
|
||||
}
|
||||
|
||||
// https://github.com/ltcmweb/mwebd?tab=readme-ov-file#fee-estimation
|
||||
final preOutputSum =
|
||||
outputs.fold<BigInt>(BigInt.zero, (acc, output) => acc + output.toOutput.amount);
|
||||
final fee = utxos.sumOfUtxosValue() - preOutputSum;
|
||||
final txb =
|
||||
BitcoinTransactionBuilder(utxos: utxos, outputs: outputs, fee: fee, network: network);
|
||||
final resp = await CwMweb.create(CreateRequest(
|
||||
rawTx: txb.buildTransaction((a, b, c, d) => '').toBytes(),
|
||||
scanSecret: scanSecret,
|
||||
spendSecret: spendSecret,
|
||||
feeRatePerKb: Int64(feeRate * 1000),
|
||||
dryRun: true));
|
||||
final tx = BtcTransaction.fromRaw(hex.encode(resp.rawTx));
|
||||
final posUtxos = utxos
|
||||
.where((utxo) => tx.inputs
|
||||
.any((input) => input.txId == utxo.utxo.txHash && input.txIndex == utxo.utxo.vout))
|
||||
.toList();
|
||||
final posOutputSum = tx.outputs.fold<int>(0, (acc, output) => acc + output.amount.toInt());
|
||||
final mwebInputSum = utxos.sumOfUtxosValue() - posUtxos.sumOfUtxosValue();
|
||||
final expectedPegin = max(0, (preOutputSum - mwebInputSum).toInt());
|
||||
var feeIncrease = posOutputSum - expectedPegin;
|
||||
if (expectedPegin > 0 && fee == BigInt.zero) {
|
||||
feeIncrease += await super.calcFee(
|
||||
utxos: posUtxos,
|
||||
outputs: tx.outputs
|
||||
.map((output) =>
|
||||
BitcoinScriptOutput(script: output.scriptPubKey, value: output.amount))
|
||||
.toList(),
|
||||
network: network,
|
||||
memo: memo,
|
||||
feeRate: feeRate) +
|
||||
feeRate * 41;
|
||||
}
|
||||
return fee.toInt() + feeIncrease;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<PendingTransaction> createTransaction(Object credentials) async {
|
||||
try {
|
||||
var tx = await super.createTransaction(credentials) as PendingBitcoinTransaction;
|
||||
tx.isMweb = mwebEnabled;
|
||||
|
||||
if (!mwebEnabled) {
|
||||
return tx;
|
||||
}
|
||||
await waitForMwebAddresses();
|
||||
await getStub();
|
||||
|
||||
final resp = await CwMweb.create(CreateRequest(
|
||||
rawTx: hex.decode(tx.hex),
|
||||
scanSecret: scanSecret,
|
||||
spendSecret: spendSecret,
|
||||
feeRatePerKb: Int64.parseInt(tx.feeRate) * 1000,
|
||||
));
|
||||
final tx2 = BtcTransaction.fromRaw(hex.encode(resp.rawTx));
|
||||
|
||||
// check if the transaction doesn't contain any mweb inputs or outputs:
|
||||
final transactionCredentials = credentials as BitcoinTransactionCredentials;
|
||||
|
||||
bool hasMwebInput = false;
|
||||
bool hasMwebOutput = false;
|
||||
|
||||
for (final output in transactionCredentials.outputs) {
|
||||
if (output.extractedAddress?.toLowerCase().contains("mweb") ?? false) {
|
||||
hasMwebOutput = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (tx2.mwebBytes != null && tx2.mwebBytes!.isNotEmpty) {
|
||||
hasMwebInput = true;
|
||||
}
|
||||
|
||||
if (!hasMwebInput && !hasMwebOutput) {
|
||||
return tx;
|
||||
}
|
||||
|
||||
// check if any of the inputs of this transaction are hog-ex:
|
||||
// this list is only non-mweb inputs:
|
||||
tx2.inputs.forEach((txInput) {
|
||||
bool isHogEx = true;
|
||||
|
||||
final utxo = unspentCoins
|
||||
.firstWhere((utxo) => utxo.hash == txInput.txId && utxo.vout == txInput.txIndex);
|
||||
|
||||
// TODO: detect actual hog-ex inputs
|
||||
|
||||
if (!isHogEx) {
|
||||
return;
|
||||
}
|
||||
|
||||
int confirmations = utxo.confirmations ?? 0;
|
||||
if (confirmations < 6) {
|
||||
throw Exception(
|
||||
"A transaction input has less than 6 confirmations, please try again later.");
|
||||
}
|
||||
});
|
||||
|
||||
tx.hexOverride = tx2
|
||||
.copyWith(
|
||||
witnesses: tx2.inputs.asMap().entries.map((e) {
|
||||
final utxo = unspentCoins
|
||||
.firstWhere((utxo) => utxo.hash == e.value.txId && utxo.vout == e.value.txIndex);
|
||||
final key = generateECPrivate(
|
||||
hd: utxo.bitcoinAddressRecord.isHidden
|
||||
? walletAddresses.sideHd
|
||||
: walletAddresses.mainHd,
|
||||
index: utxo.bitcoinAddressRecord.index,
|
||||
network: network);
|
||||
final digest = tx2.getTransactionSegwitDigit(
|
||||
txInIndex: e.key,
|
||||
script: key.getPublic().toP2pkhAddress().toScriptPubKey(),
|
||||
amount: BigInt.from(utxo.value),
|
||||
);
|
||||
return TxWitnessInput(stack: [key.signInput(digest), key.getPublic().toHex()]);
|
||||
}).toList())
|
||||
.toHex();
|
||||
tx.outputAddresses = resp.outputId;
|
||||
|
||||
return tx
|
||||
..addListener((transaction) async {
|
||||
final addresses = <String>{};
|
||||
transaction.inputAddresses?.forEach((id) async {
|
||||
final utxo = mwebUtxosBox.get(id);
|
||||
// await mwebUtxosBox.delete(id);// gets deleted in checkMwebUtxosSpent
|
||||
if (utxo == null) return;
|
||||
final addressRecord = walletAddresses.allAddresses
|
||||
.firstWhere((addressRecord) => addressRecord.address == utxo.address);
|
||||
if (!addresses.contains(utxo.address)) {
|
||||
addresses.add(utxo.address);
|
||||
}
|
||||
addressRecord.balance -= utxo.value.toInt();
|
||||
});
|
||||
transaction.inputAddresses?.addAll(addresses);
|
||||
|
||||
transactionHistory.addOne(transaction);
|
||||
await updateUnspent();
|
||||
await updateBalance();
|
||||
});
|
||||
} catch (e, s) {
|
||||
print(e);
|
||||
print(s);
|
||||
if (e.toString().contains("commit failed")) {
|
||||
throw Exception("Transaction commit failed (no peers responded), please try again.");
|
||||
}
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> save() async {
|
||||
await super.save();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
_utxoStream?.cancel();
|
||||
_feeRatesTimer?.cancel();
|
||||
_syncTimer?.cancel();
|
||||
_processingTimer?.cancel();
|
||||
await stopSync();
|
||||
await super.close();
|
||||
}
|
||||
|
||||
Future<void> setMwebEnabled(bool enabled) async {
|
||||
if (mwebEnabled == enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
mwebEnabled = enabled;
|
||||
(walletAddresses as LitecoinWalletAddresses).mwebEnabled = enabled;
|
||||
await stopSync();
|
||||
await startSync();
|
||||
}
|
||||
|
||||
Future<RpcClient> getStub() async {
|
||||
_stub = await CwMweb.stub();
|
||||
return _stub;
|
||||
}
|
||||
|
||||
Future<StatusResponse> getStatusRequest() async {
|
||||
final resp = await CwMweb.status(StatusRequest());
|
||||
return resp;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> signMessage(String message, {String? address = null}) async {
|
||||
final index = address != null
|
||||
|
|
|
@ -1,8 +1,15 @@
|
|||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||
import 'package:blockchain_utils/blockchain_utils.dart';
|
||||
import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
||||
import 'package:cw_bitcoin/electrum_wallet.dart';
|
||||
import 'package:cw_bitcoin/utils.dart';
|
||||
import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_mweb/cw_mweb.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part 'litecoin_wallet_addresses.g.dart';
|
||||
|
@ -15,15 +22,145 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
|
|||
required super.mainHd,
|
||||
required super.sideHd,
|
||||
required super.network,
|
||||
required this.mwebHd,
|
||||
required this.mwebEnabled,
|
||||
super.initialAddresses,
|
||||
super.initialMwebAddresses,
|
||||
super.initialRegularAddressIndex,
|
||||
super.initialChangeAddressIndex,
|
||||
}) : super(walletInfo);
|
||||
}) : super(walletInfo) {
|
||||
for (int i = 0; i < mwebAddresses.length; i++) {
|
||||
mwebAddrs.add(mwebAddresses[i].address);
|
||||
}
|
||||
print("initialized with ${mwebAddrs.length} mweb addresses");
|
||||
}
|
||||
|
||||
final Bip32Slip10Secp256k1 mwebHd;
|
||||
bool mwebEnabled;
|
||||
int mwebTopUpIndex = 1000;
|
||||
List<String> mwebAddrs = [];
|
||||
|
||||
List<int> get scanSecret => mwebHd.childKey(Bip32KeyIndex(0x80000000)).privateKey.privKey.raw;
|
||||
List<int> get spendPubkey =>
|
||||
mwebHd.childKey(Bip32KeyIndex(0x80000001)).publicKey.pubKey.compressed;
|
||||
|
||||
@override
|
||||
String getAddress(
|
||||
{required int index,
|
||||
required Bip32Slip10Secp256k1 hd,
|
||||
BitcoinAddressType? addressType}) =>
|
||||
generateP2WPKHAddress(hd: hd, index: index, network: network);
|
||||
Future<void> init() async {
|
||||
await initMwebAddresses();
|
||||
await super.init();
|
||||
}
|
||||
|
||||
@computed
|
||||
@override
|
||||
List<BitcoinAddressRecord> get allAddresses {
|
||||
return List.from(super.allAddresses)..addAll(mwebAddresses);
|
||||
}
|
||||
|
||||
Future<void> ensureMwebAddressUpToIndexExists(int index) async {
|
||||
Uint8List scan = Uint8List.fromList(scanSecret);
|
||||
Uint8List spend = Uint8List.fromList(spendPubkey);
|
||||
int count = 0;
|
||||
while (mwebAddrs.length <= (index + 1)) {
|
||||
final address = await CwMweb.address(scan, spend, mwebAddrs.length);
|
||||
mwebAddrs.add(address!);
|
||||
count++;
|
||||
// sleep for a bit to avoid making the main thread unresponsive:
|
||||
if (count > 50) {
|
||||
count = 0;
|
||||
await Future.delayed(Duration(milliseconds: 100));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> initMwebAddresses() async {
|
||||
if (mwebAddrs.length < 1000) {
|
||||
print("Generating MWEB addresses...");
|
||||
await ensureMwebAddressUpToIndexExists(1020);
|
||||
print("done generating MWEB addresses");
|
||||
List<BitcoinAddressRecord> addressRecords = mwebAddrs
|
||||
.asMap()
|
||||
.entries
|
||||
.map((e) => BitcoinAddressRecord(
|
||||
e.value,
|
||||
index: e.key,
|
||||
type: SegwitAddresType.mweb,
|
||||
network: network,
|
||||
))
|
||||
.toList();
|
||||
addMwebAddresses(addressRecords);
|
||||
print("added ${addressRecords.length} mweb addresses");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
String getAddress({
|
||||
required int index,
|
||||
required Bip32Slip10Secp256k1 hd,
|
||||
BitcoinAddressType? addressType,
|
||||
}) {
|
||||
if (addressType == SegwitAddresType.mweb) {
|
||||
return hd == sideHd ? mwebAddrs[0] : mwebAddrs[index + 1];
|
||||
}
|
||||
return generateP2WPKHAddress(hd: hd, index: index, network: network);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> getAddressAsync({
|
||||
required int index,
|
||||
required Bip32Slip10Secp256k1 hd,
|
||||
BitcoinAddressType? addressType,
|
||||
}) async {
|
||||
if (addressType == SegwitAddresType.mweb) {
|
||||
await ensureMwebAddressUpToIndexExists(index);
|
||||
}
|
||||
return getAddress(index: index, hd: hd, addressType: addressType);
|
||||
}
|
||||
|
||||
@action
|
||||
@override
|
||||
Future<String> getChangeAddress({List<BitcoinOutput>? outputs, UtxoDetails? utxoDetails}) async {
|
||||
// use regular change address on peg in, otherwise use mweb for change address:
|
||||
|
||||
if (!mwebEnabled) {
|
||||
return super.getChangeAddress();
|
||||
}
|
||||
|
||||
if (outputs != null && utxoDetails != null) {
|
||||
// check if this is a PEGIN:
|
||||
bool outputsToMweb = false;
|
||||
bool comesFromMweb = false;
|
||||
|
||||
for (var i = 0; i < outputs.length; i++) {
|
||||
// TODO: probably not the best way to tell if this is an mweb address
|
||||
// (but it doesn't contain the "mweb" text at this stage)
|
||||
if (outputs[i].address.toAddress(network).length > 110) {
|
||||
outputsToMweb = true;
|
||||
}
|
||||
}
|
||||
// TODO: this doesn't respect coin control because it doesn't know which available inputs are selected
|
||||
utxoDetails.availableInputs.forEach((element) {
|
||||
if (element.address.contains("mweb")) {
|
||||
comesFromMweb = true;
|
||||
}
|
||||
});
|
||||
|
||||
bool isPegIn = !comesFromMweb && outputsToMweb;
|
||||
if (isPegIn && mwebEnabled) {
|
||||
return super.getChangeAddress();
|
||||
}
|
||||
|
||||
// use regular change address if it's not an mweb tx:
|
||||
if (!comesFromMweb && !outputsToMweb) {
|
||||
return super.getChangeAddress();
|
||||
}
|
||||
}
|
||||
|
||||
if (mwebEnabled) {
|
||||
await ensureMwebAddressUpToIndexExists(1);
|
||||
return mwebAddrs[0];
|
||||
}
|
||||
|
||||
return super.getChangeAddress();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import 'dart:io';
|
||||
import 'package:cw_bitcoin/bitcoin_mnemonics_bip39.dart';
|
||||
import 'package:cw_bitcoin/mnemonic_is_incorrect_exception.dart';
|
||||
import 'package:cw_core/encryption_file_utils.dart';
|
||||
import 'package:cw_core/unspent_coins_info.dart';
|
||||
import 'package:hive/hive.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_bitcoin/litecoin_wallet.dart';
|
||||
import 'package:cw_core/wallet_service.dart';
|
||||
|
@ -14,16 +14,19 @@ import 'package:cw_core/wallet_info.dart';
|
|||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:bip39/bip39.dart' as bip39;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
|
||||
class LitecoinWalletService extends WalletService<
|
||||
BitcoinNewWalletCredentials,
|
||||
BitcoinRestoreWalletFromSeedCredentials,
|
||||
BitcoinRestoreWalletFromWIFCredentials,
|
||||
BitcoinNewWalletCredentials> {
|
||||
LitecoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource, this.isDirect);
|
||||
LitecoinWalletService(
|
||||
this.walletInfoSource, this.unspentCoinsInfoSource, this.alwaysScan, this.isDirect);
|
||||
|
||||
final Box<WalletInfo> walletInfoSource;
|
||||
final Box<UnspentCoinsInfo> unspentCoinsInfoSource;
|
||||
final bool alwaysScan;
|
||||
final bool isDirect;
|
||||
|
||||
@override
|
||||
|
@ -64,6 +67,7 @@ class LitecoinWalletService extends WalletService<
|
|||
|
||||
@override
|
||||
Future<LitecoinWallet> openWallet(String name, String password) async {
|
||||
|
||||
final walletInfo = walletInfoSource.values
|
||||
.firstWhereOrNull((info) => info.id == WalletBase.idFor(name, getType()))!;
|
||||
|
||||
|
@ -73,6 +77,7 @@ class LitecoinWalletService extends WalletService<
|
|||
name: name,
|
||||
walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||
alwaysScan: alwaysScan,
|
||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||
);
|
||||
await wallet.init();
|
||||
|
@ -85,6 +90,7 @@ class LitecoinWalletService extends WalletService<
|
|||
name: name,
|
||||
walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||
alwaysScan: alwaysScan,
|
||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||
);
|
||||
await wallet.init();
|
||||
|
@ -98,6 +104,23 @@ class LitecoinWalletService extends WalletService<
|
|||
final walletInfo = walletInfoSource.values
|
||||
.firstWhereOrNull((info) => info.id == WalletBase.idFor(wallet, getType()))!;
|
||||
await walletInfoSource.delete(walletInfo.key);
|
||||
|
||||
// if there are no more litecoin wallets left, cleanup the neutrino db and other files created by mwebd:
|
||||
if (walletInfoSource.values.where((info) => info.type == WalletType.litecoin).isEmpty) {
|
||||
final appDirPath = (await getApplicationSupportDirectory()).path;
|
||||
File neturinoDb = File('$appDirPath/neutrino.db');
|
||||
File blockHeaders = File('$appDirPath/block_headers.bin');
|
||||
File regFilterHeaders = File('$appDirPath/reg_filter_headers.bin');
|
||||
if (neturinoDb.existsSync()) {
|
||||
neturinoDb.deleteSync();
|
||||
}
|
||||
if (blockHeaders.existsSync()) {
|
||||
blockHeaders.deleteSync();
|
||||
}
|
||||
if (regFilterHeaders.existsSync()) {
|
||||
regFilterHeaders.deleteSync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -109,6 +132,7 @@ class LitecoinWalletService extends WalletService<
|
|||
name: currentName,
|
||||
walletInfo: currentWalletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||
alwaysScan: alwaysScan,
|
||||
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
|
||||
);
|
||||
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
import 'package:grpc/grpc.dart';
|
||||
import 'package:cw_bitcoin/exceptions.dart';
|
||||
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||
import 'package:blockchain_utils/blockchain_utils.dart';
|
||||
import 'package:cw_core/pending_transaction.dart';
|
||||
import 'package:cw_bitcoin/electrum.dart';
|
||||
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/wallet_type.dart';
|
||||
import 'package:cw_mweb/cw_mweb.dart';
|
||||
import 'package:cw_mweb/mwebd.pb.dart';
|
||||
|
||||
class PendingBitcoinTransaction with PendingTransaction {
|
||||
PendingBitcoinTransaction(
|
||||
|
@ -19,6 +23,7 @@ class PendingBitcoinTransaction with PendingTransaction {
|
|||
required this.hasChange,
|
||||
this.isSendAll = false,
|
||||
this.hasTaprootInputs = false,
|
||||
this.isMweb = false,
|
||||
}) : _listeners = <void Function(ElectrumTransactionInfo transaction)>[];
|
||||
|
||||
final WalletType type;
|
||||
|
@ -28,15 +33,19 @@ class PendingBitcoinTransaction with PendingTransaction {
|
|||
final int fee;
|
||||
final String feeRate;
|
||||
final BasedUtxoNetwork? network;
|
||||
final bool hasChange;
|
||||
final bool isSendAll;
|
||||
final bool hasChange;
|
||||
final bool hasTaprootInputs;
|
||||
bool isMweb;
|
||||
String? idOverride;
|
||||
String? hexOverride;
|
||||
List<String>? outputAddresses;
|
||||
|
||||
@override
|
||||
String get id => _tx.txId();
|
||||
String get id => idOverride ?? _tx.txId();
|
||||
|
||||
@override
|
||||
String get hex => _tx.serialize();
|
||||
String get hex => hexOverride ?? _tx.serialize();
|
||||
|
||||
@override
|
||||
String get amountFormatted => bitcoinAmountToString(amount: amount);
|
||||
|
@ -62,8 +71,7 @@ class PendingBitcoinTransaction with PendingTransaction {
|
|||
|
||||
final List<void Function(ElectrumTransactionInfo transaction)> _listeners;
|
||||
|
||||
@override
|
||||
Future<void> commit() async {
|
||||
Future<void> _commit() async {
|
||||
int? callId;
|
||||
|
||||
final result = await electrumClient.broadcastTransaction(
|
||||
|
@ -100,6 +108,25 @@ class PendingBitcoinTransaction with PendingTransaction {
|
|||
|
||||
throw BitcoinTransactionCommitFailed();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _ltcCommit() async {
|
||||
try {
|
||||
final stub = await CwMweb.stub();
|
||||
final resp = await stub.broadcast(BroadcastRequest(rawTx: BytesUtils.fromHexString(hex)));
|
||||
idOverride = resp.txid;
|
||||
} on GrpcError catch (e) {
|
||||
throw BitcoinTransactionCommitFailed(errorMessage: e.message);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> commit() async {
|
||||
if (isMweb) {
|
||||
await _ltcCommit();
|
||||
} else {
|
||||
await _commit();
|
||||
}
|
||||
|
||||
_listeners.forEach((listener) => listener(transactionInfo()));
|
||||
}
|
||||
|
@ -116,5 +143,7 @@ class PendingBitcoinTransaction with PendingTransaction {
|
|||
isPending: true,
|
||||
isReplaced: false,
|
||||
confirmations: 0,
|
||||
inputAddresses: _tx.inputs.map((input) => input.txId).toList(),
|
||||
outputAddresses: outputAddresses,
|
||||
fee: fee);
|
||||
}
|
||||
|
|
|
@ -17,6 +17,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.7.0"
|
||||
archive:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: archive
|
||||
sha256: "22600aa1e926be775fa5fe7e6894e7fb3df9efda8891c73f70fb3262399a432d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.4.10"
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -260,6 +268,13 @@ packages:
|
|||
relative: true
|
||||
source: path
|
||||
version: "0.0.1"
|
||||
cw_mweb:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "../cw_mweb"
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.0.1"
|
||||
dart_style:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -379,6 +394,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
googleapis_auth:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: googleapis_auth
|
||||
sha256: af7c3a3edf9d0de2e1e0a77e994fae0a581c525fa7012af4fa0d4a52ed9484da
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
graphs:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -387,6 +410,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.1"
|
||||
grpc:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: grpc
|
||||
sha256: e93ee3bce45c134bf44e9728119102358c7cd69de7832d9a874e2e74eb8cab40
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.4"
|
||||
hex:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
@ -34,11 +34,16 @@ dependencies:
|
|||
ledger_bitcoin:
|
||||
git:
|
||||
url: https://github.com/cake-tech/ledger-bitcoin
|
||||
cw_mweb:
|
||||
path: ../cw_mweb
|
||||
grpc: ^3.2.4
|
||||
sp_scanner:
|
||||
git:
|
||||
url: https://github.com/cake-tech/sp_scanner
|
||||
ref: sp_v4.0.0
|
||||
|
||||
bech32:
|
||||
git:
|
||||
url: https://github.com/cake-tech/bech32.git
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
@ -54,10 +59,13 @@ dependency_overrides:
|
|||
url: https://github.com/cake-tech/ledger-flutter.git
|
||||
ref: cake-v3
|
||||
watcher: ^1.1.0
|
||||
protobuf: ^3.1.0
|
||||
bitcoin_base:
|
||||
git:
|
||||
url: https://github.com/cake-tech/bitcoin_base
|
||||
ref: cake-update-v7
|
||||
ref: cake-update-v8
|
||||
pointycastle: 3.7.4
|
||||
ffi: 2.1.0
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
# following page: https://dart.dev/tools/pub/pubspec
|
||||
|
|
|
@ -42,7 +42,7 @@ dependency_overrides:
|
|||
bitcoin_base:
|
||||
git:
|
||||
url: https://github.com/cake-tech/bitcoin_base
|
||||
ref: cake-update-v7
|
||||
ref: cake-update-v8
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
# following page: https://dart.dev/tools/pub/pubspec
|
||||
|
|
|
@ -1,13 +1,17 @@
|
|||
abstract class Balance {
|
||||
const Balance(this.available, this.additional);
|
||||
const Balance(this.available, this.additional, {this.secondAvailable, this.secondAdditional});
|
||||
|
||||
final int available;
|
||||
|
||||
final int additional;
|
||||
|
||||
final int? secondAvailable;
|
||||
final int? secondAdditional;
|
||||
|
||||
String get formattedAvailableBalance;
|
||||
|
||||
String get formattedAdditionalBalance;
|
||||
|
||||
String get formattedUnAvailableBalance => '';
|
||||
String get formattedSecondAvailableBalance => '';
|
||||
String get formattedSecondAdditionalBalance => '';
|
||||
String get formattedFullAvailableBalance => formattedAvailableBalance;
|
||||
}
|
||||
|
|
|
@ -310,6 +310,11 @@ DateTime getDateByBitcoinHeight(int height) {
|
|||
return estimatedDate;
|
||||
}
|
||||
|
||||
int getLtcHeightByDate({required DateTime date}) {
|
||||
// TODO: use the proxy layer to get the height with a binary search of blocked header heights
|
||||
return 0;
|
||||
}
|
||||
|
||||
// TODO: enhance all of this global const lists
|
||||
const wowDates = {
|
||||
"2023-12": 583048,
|
||||
|
|
|
@ -18,3 +18,4 @@ const SPL_TOKEN_TYPE_ID = 16;
|
|||
const DERIVATION_INFO_TYPE_ID = 17;
|
||||
const TRON_TOKEN_TYPE_ID = 18;
|
||||
const HARDWARE_WALLET_TYPE_TYPE_ID = 19;
|
||||
const MWEB_UTXO_TYPE_ID = 20;
|
33
cw_core/lib/mweb_utxo.dart
Normal file
33
cw_core/lib/mweb_utxo.dart
Normal file
|
@ -0,0 +1,33 @@
|
|||
import 'package:cw_core/hive_type_ids.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
|
||||
part 'mweb_utxo.g.dart';
|
||||
|
||||
@HiveType(typeId: MWEB_UTXO_TYPE_ID)
|
||||
class MwebUtxo extends HiveObject {
|
||||
MwebUtxo({
|
||||
required this.height,
|
||||
required this.value,
|
||||
required this.address,
|
||||
required this.outputId,
|
||||
required this.blockTime,
|
||||
});
|
||||
|
||||
static const typeId = MWEB_UTXO_TYPE_ID;
|
||||
static const boxName = 'MwebUtxo';
|
||||
|
||||
@HiveField(0)
|
||||
int height;
|
||||
|
||||
@HiveField(1)
|
||||
int value;
|
||||
|
||||
@HiveField(2)
|
||||
String address;
|
||||
|
||||
@HiveField(3)
|
||||
String outputId;
|
||||
|
||||
@HiveField(4)
|
||||
int blockTime;
|
||||
}
|
|
@ -67,6 +67,8 @@ abstract class WalletBase<BalanceType extends Balance, HistoryType extends Trans
|
|||
|
||||
Future<void> startSync();
|
||||
|
||||
Future<void> stopSync() async {}
|
||||
|
||||
Future<PendingTransaction> createTransaction(Object credentials);
|
||||
|
||||
int calculateEstimatedFee(TransactionPriority priority, int? amount);
|
||||
|
|
|
@ -35,7 +35,6 @@ android {
|
|||
}
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path "CMakeLists.txt"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
30
cw_mweb/.gitignore
vendored
Normal file
30
cw_mweb/.gitignore
vendored
Normal file
|
@ -0,0 +1,30 @@
|
|||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
migrate_working_dir/
|
||||
|
||||
# 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
|
||||
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
|
||||
/pubspec.lock
|
||||
**/doc/api/
|
||||
.dart_tool/
|
||||
.packages
|
||||
build/
|
36
cw_mweb/.metadata
Normal file
36
cw_mweb/.metadata
Normal file
|
@ -0,0 +1,36 @@
|
|||
# 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.
|
||||
|
||||
version:
|
||||
revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
|
||||
channel: stable
|
||||
|
||||
project_type: plugin
|
||||
|
||||
# Tracks metadata for the flutter migrate command
|
||||
migration:
|
||||
platforms:
|
||||
- platform: root
|
||||
create_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
|
||||
base_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
|
||||
- platform: android
|
||||
create_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
|
||||
base_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
|
||||
- platform: ios
|
||||
create_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
|
||||
base_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
|
||||
- platform: macos
|
||||
create_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
|
||||
base_revision: f468f3366c26a5092eb964a230ce7892fda8f2f8
|
||||
|
||||
# User provided section
|
||||
|
||||
# List of Local paths (relative to this file) that should be
|
||||
# ignored by the migrate tool.
|
||||
#
|
||||
# Files that are not part of the templates will be ignored by default.
|
||||
unmanaged_files:
|
||||
- 'lib/main.dart'
|
||||
- 'ios/Runner.xcodeproj/project.pbxproj'
|
3
cw_mweb/CHANGELOG.md
Normal file
3
cw_mweb/CHANGELOG.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
## 0.0.1
|
||||
|
||||
* TODO: Describe initial release.
|
1
cw_mweb/LICENSE
Normal file
1
cw_mweb/LICENSE
Normal file
|
@ -0,0 +1 @@
|
|||
TODO: Add your license here.
|
15
cw_mweb/README.md
Normal file
15
cw_mweb/README.md
Normal file
|
@ -0,0 +1,15 @@
|
|||
# cw_mweb
|
||||
|
||||
A new Flutter plugin project.
|
||||
|
||||
## Getting Started
|
||||
|
||||
This project is a starting point for a Flutter
|
||||
[plug-in package](https://flutter.dev/developing-packages/),
|
||||
a specialized package that includes platform-specific implementation code for
|
||||
Android and/or iOS.
|
||||
|
||||
For help getting started with Flutter development, view the
|
||||
[online documentation](https://flutter.dev/docs), which offers tutorials,
|
||||
samples, guidance on mobile development, and a full API reference.
|
||||
|
4
cw_mweb/analysis_options.yaml
Normal file
4
cw_mweb/analysis_options.yaml
Normal file
|
@ -0,0 +1,4 @@
|
|||
include: package:flutter_lints/flutter.yaml
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
10
cw_mweb/android/.gitignore
vendored
Normal file
10
cw_mweb/android/.gitignore
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/workspace.xml
|
||||
/.idea/libraries
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
/libs
|
||||
.cxx
|
76
cw_mweb/android/build.gradle
Normal file
76
cw_mweb/android/build.gradle
Normal file
|
@ -0,0 +1,76 @@
|
|||
group 'com.cakewallet.mweb'
|
||||
version '1.0-SNAPSHOT'
|
||||
|
||||
buildscript {
|
||||
ext.kotlin_version = '1.7.10'
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.3.0'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.allprojects {
|
||||
repositories {
|
||||
flatDir {
|
||||
dirs project(':cw_mweb').file('libs')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
android {
|
||||
compileSdkVersion 31
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = '1.8'
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main.java.srcDirs += 'src/main/kotlin'
|
||||
test.java.srcDirs += 'src/test/kotlin'
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testImplementation 'org.jetbrains.kotlin:kotlin-test'
|
||||
testImplementation 'org.mockito:mockito-core:5.0.0'
|
||||
}
|
||||
|
||||
testOptions {
|
||||
unitTests.all {
|
||||
useJUnitPlatform()
|
||||
|
||||
testLogging {
|
||||
events "passed", "skipped", "failed", "standardOut", "standardError"
|
||||
outputs.upToDateWhen {false}
|
||||
showStandardStreams = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation (name: 'mwebd', ext: 'aar')
|
||||
}
|
1
cw_mweb/android/settings.gradle
Normal file
1
cw_mweb/android/settings.gradle
Normal file
|
@ -0,0 +1 @@
|
|||
rootProject.name = 'cw_mweb'
|
3
cw_mweb/android/src/main/AndroidManifest.xml
Normal file
3
cw_mweb/android/src/main/AndroidManifest.xml
Normal file
|
@ -0,0 +1,3 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.cakewallet.mweb">
|
||||
</manifest>
|
|
@ -0,0 +1,58 @@
|
|||
package com.cakewallet.mweb
|
||||
|
||||
import androidx.annotation.NonNull
|
||||
|
||||
import io.flutter.embedding.engine.plugins.FlutterPlugin
|
||||
import io.flutter.plugin.common.MethodCall
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
|
||||
import io.flutter.plugin.common.MethodChannel.Result
|
||||
|
||||
import mwebd.Mwebd
|
||||
import mwebd.Server
|
||||
|
||||
/** CwMwebPlugin */
|
||||
class CwMwebPlugin: FlutterPlugin, MethodCallHandler {
|
||||
/// The MethodChannel that will the communication between Flutter and native Android
|
||||
///
|
||||
/// This local reference serves to register the plugin with the Flutter Engine and unregister it
|
||||
/// when the Flutter Engine is detached from the Activity
|
||||
private lateinit var channel : MethodChannel
|
||||
private var server: Server? = null
|
||||
private var port: Long? = null
|
||||
|
||||
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
|
||||
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "cw_mweb")
|
||||
channel.setMethodCallHandler(this)
|
||||
}
|
||||
|
||||
override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
|
||||
if (call.method == "start") {
|
||||
server?.stop()
|
||||
val dataDir = call.argument("dataDir") ?: ""
|
||||
server = server ?: Mwebd.newServer("", dataDir, "")
|
||||
port = server?.start(0)
|
||||
result.success(port)
|
||||
} else if (call.method == "stop") {
|
||||
server?.stop()
|
||||
server = null
|
||||
port = null
|
||||
result.success(null)
|
||||
} else if (call.method == "address") {
|
||||
val scanSecret: ByteArray = call.argument<ByteArray>("scanSecret") ?: ByteArray(0)
|
||||
val spendPub: ByteArray = call.argument<ByteArray>("spendPub") ?: ByteArray(0)
|
||||
val index: Int = call.argument<Int>("index") ?: 0
|
||||
val res = Mwebd.address(scanSecret, spendPub, index)
|
||||
result.success(res)
|
||||
} else {
|
||||
result.notImplemented()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
|
||||
channel.setMethodCallHandler(null)
|
||||
server?.stop()
|
||||
server = null
|
||||
port = null
|
||||
}
|
||||
}
|
38
cw_mweb/ios/.gitignore
vendored
Normal file
38
cw_mweb/ios/.gitignore
vendored
Normal file
|
@ -0,0 +1,38 @@
|
|||
.idea/
|
||||
.vagrant/
|
||||
.sconsign.dblite
|
||||
.svn/
|
||||
|
||||
.DS_Store
|
||||
*.swp
|
||||
profile
|
||||
|
||||
DerivedData/
|
||||
build/
|
||||
GeneratedPluginRegistrant.h
|
||||
GeneratedPluginRegistrant.m
|
||||
|
||||
.generated/
|
||||
|
||||
*.pbxuser
|
||||
*.mode1v3
|
||||
*.mode2v3
|
||||
*.perspectivev3
|
||||
|
||||
!default.pbxuser
|
||||
!default.mode1v3
|
||||
!default.mode2v3
|
||||
!default.perspectivev3
|
||||
|
||||
xcuserdata
|
||||
|
||||
*.moved-aside
|
||||
|
||||
*.pyc
|
||||
*sync/
|
||||
Icon?
|
||||
.tags*
|
||||
|
||||
/Flutter/Generated.xcconfig
|
||||
/Flutter/ephemeral/
|
||||
/Flutter/flutter_export_environment.sh
|
0
cw_mweb/ios/Assets/.gitkeep
Normal file
0
cw_mweb/ios/Assets/.gitkeep
Normal file
86
cw_mweb/ios/Classes/CwMwebPlugin.swift
Normal file
86
cw_mweb/ios/Classes/CwMwebPlugin.swift
Normal file
|
@ -0,0 +1,86 @@
|
|||
import Flutter
|
||||
import UIKit
|
||||
import Mwebd
|
||||
|
||||
public class CwMwebPlugin: NSObject, FlutterPlugin {
|
||||
public static func register(with registrar: FlutterPluginRegistrar) {
|
||||
let channel = FlutterMethodChannel(name: "cw_mweb", binaryMessenger: registrar.messenger())
|
||||
let instance = CwMwebPlugin()
|
||||
registrar.addMethodCallDelegate(instance, channel: channel)
|
||||
}
|
||||
|
||||
private static var server: MwebdServer?
|
||||
private static var port: Int = 0
|
||||
private static var dataDir: String?
|
||||
|
||||
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
|
||||
switch call.method {
|
||||
case "getPlatformVersion":
|
||||
result("iOS " + UIDevice.current.systemVersion)
|
||||
break
|
||||
case "start":
|
||||
stopServer()
|
||||
let args = call.arguments as? [String: String]
|
||||
let dataDir = args?["dataDir"]
|
||||
CwMwebPlugin.dataDir = dataDir
|
||||
startServer(result: result)
|
||||
break
|
||||
case "stop":
|
||||
stopServer()
|
||||
result(nil)
|
||||
break
|
||||
case "address":
|
||||
let args = call.arguments as! [String: Any]
|
||||
let scanSecret = args["scanSecret"] as! FlutterStandardTypedData
|
||||
let spendPub = args["spendPub"] as! FlutterStandardTypedData
|
||||
let index = args["index"] as! Int32
|
||||
|
||||
let scanSecretData = scanSecret.data
|
||||
let spendPubData = spendPub.data
|
||||
result(MwebdAddress(scanSecretData, spendPubData, index))
|
||||
break
|
||||
default:
|
||||
result(FlutterMethodNotImplemented)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
private func startServer(result: @escaping FlutterResult) {
|
||||
if CwMwebPlugin.server == nil {
|
||||
var error: NSError?
|
||||
CwMwebPlugin.server = MwebdNewServer("", CwMwebPlugin.dataDir, "", &error)
|
||||
|
||||
if let server = CwMwebPlugin.server {
|
||||
do {
|
||||
print("Starting server...")
|
||||
try server.start(0, ret0_: &CwMwebPlugin.port)
|
||||
print("Server started successfully on port: \(CwMwebPlugin.port)")
|
||||
result(CwMwebPlugin.port)
|
||||
} catch let startError as NSError {
|
||||
print("Server Start Error: \(startError.localizedDescription)")
|
||||
result(FlutterError(code: "Server Start Error", message: startError.localizedDescription, details: nil))
|
||||
}
|
||||
} else if let error = error {
|
||||
print("Server Creation Error: \(error.localizedDescription)")
|
||||
result(FlutterError(code: "Server Creation Error", message: error.localizedDescription, details: nil))
|
||||
} else {
|
||||
print("Unknown Error: Failed to create server")
|
||||
result(FlutterError(code: "Unknown Error", message: "Failed to create server", details: nil))
|
||||
}
|
||||
} else {
|
||||
print("Server already running on port: \(CwMwebPlugin.port)")
|
||||
result(CwMwebPlugin.port)
|
||||
}
|
||||
}
|
||||
|
||||
private func stopServer() {
|
||||
print("Stopping server")
|
||||
CwMwebPlugin.server?.stop()
|
||||
CwMwebPlugin.server = nil
|
||||
CwMwebPlugin.port = 0
|
||||
}
|
||||
|
||||
deinit {
|
||||
stopServer()
|
||||
}
|
||||
}
|
26
cw_mweb/ios/cw_mweb.podspec
Normal file
26
cw_mweb/ios/cw_mweb.podspec
Normal file
|
@ -0,0 +1,26 @@
|
|||
#
|
||||
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
|
||||
# Run `pod lib lint cw_mweb.podspec` to validate before publishing.
|
||||
#
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'cw_mweb'
|
||||
s.version = '0.0.1'
|
||||
s.summary = 'A new Flutter plugin project.'
|
||||
s.description = <<-DESC
|
||||
A new Flutter plugin project.
|
||||
DESC
|
||||
s.homepage = 'http://example.com'
|
||||
s.license = { :file => '../LICENSE' }
|
||||
s.author = { 'Your Company' => 'email@example.com' }
|
||||
s.source = { :path => '.' }
|
||||
s.source_files = 'Classes/**/*'
|
||||
s.dependency 'Flutter'
|
||||
s.platform = :ios, '11.0'
|
||||
|
||||
# Flutter.framework does not contain a i386 slice.
|
||||
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' }
|
||||
s.swift_version = '5.0'
|
||||
s.ios.vendored_frameworks = 'Mwebd.xcframework'
|
||||
s.preserve_paths = 'Mwebd.xcframework/**/*'
|
||||
|
||||
end
|
133
cw_mweb/lib/cw_mweb.dart
Normal file
133
cw_mweb/lib/cw_mweb.dart
Normal file
|
@ -0,0 +1,133 @@
|
|||
import 'dart:typed_data';
|
||||
|
||||
import 'package:grpc/grpc.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'cw_mweb_platform_interface.dart';
|
||||
import 'mwebd.pbgrpc.dart';
|
||||
|
||||
class CwMweb {
|
||||
static RpcClient? _rpcClient;
|
||||
static ClientChannel? _clientChannel;
|
||||
static int? _port;
|
||||
static const TIMEOUT_DURATION = Duration(seconds: 5);
|
||||
|
||||
static Future<void> _initializeClient() async {
|
||||
await stop();
|
||||
// wait a few seconds to make sure the server is stopped
|
||||
await Future.delayed(const Duration(seconds: 5));
|
||||
|
||||
final appDir = await getApplicationSupportDirectory();
|
||||
_port = await CwMwebPlatform.instance.start(appDir.path);
|
||||
if (_port == null || _port == 0) {
|
||||
throw Exception("Failed to start server");
|
||||
}
|
||||
print("Attempting to connect to server on port: $_port");
|
||||
|
||||
// wait for the server to finish starting up before we try to connect to it:
|
||||
await Future.delayed(const Duration(seconds: 5));
|
||||
|
||||
_clientChannel = ClientChannel('127.0.0.1', port: _port!, channelShutdownHandler: () {
|
||||
print("Channel is shutting down!");
|
||||
},
|
||||
options: const ChannelOptions(
|
||||
credentials: ChannelCredentials.insecure(),
|
||||
keepAlive: ClientKeepAliveOptions(permitWithoutCalls: true),
|
||||
));
|
||||
_rpcClient = RpcClient(_clientChannel!);
|
||||
}
|
||||
|
||||
static Future<RpcClient> stub({int maxRetries = 3}) async {
|
||||
for (int i = 0; i < maxRetries; i++) {
|
||||
try {
|
||||
if (_rpcClient == null) {
|
||||
await _initializeClient();
|
||||
}
|
||||
final status = await _rpcClient!
|
||||
.status(StatusRequest(), options: CallOptions(timeout: TIMEOUT_DURATION));
|
||||
if (status.blockTime == 0) {
|
||||
throw Exception("blockTime shouldn't be 0! (this connection is likely broken)");
|
||||
}
|
||||
return _rpcClient!;
|
||||
} catch (e) {
|
||||
print("Attempt $i failed: $e");
|
||||
_rpcClient = null;
|
||||
}
|
||||
}
|
||||
throw Exception("Failed to connect after $maxRetries attempts");
|
||||
}
|
||||
|
||||
static Future<void> stop() async {
|
||||
try {
|
||||
await CwMwebPlatform.instance.stop();
|
||||
await cleanup();
|
||||
} catch (e) {
|
||||
print("Error stopping server: $e");
|
||||
}
|
||||
}
|
||||
|
||||
static Future<String?> address(Uint8List scanSecret, Uint8List spendPub, int index) async {
|
||||
try {
|
||||
return CwMwebPlatform.instance.address(scanSecret, spendPub, index);
|
||||
} catch (e) {
|
||||
print("Error getting address: $e");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static Future<void> cleanup() async {
|
||||
await _clientChannel?.terminate();
|
||||
_rpcClient = null;
|
||||
_clientChannel = null;
|
||||
_port = null;
|
||||
}
|
||||
|
||||
// wrappers that handle the connection issues:
|
||||
static Future<SpentResponse> spent(SpentRequest request) async {
|
||||
try {
|
||||
if (_rpcClient == null) {
|
||||
await _initializeClient();
|
||||
}
|
||||
return await _rpcClient!.spent(request, options: CallOptions(timeout: TIMEOUT_DURATION));
|
||||
} catch (e) {
|
||||
print("Error getting spent: $e");
|
||||
return SpentResponse();
|
||||
}
|
||||
}
|
||||
|
||||
static Future<StatusResponse> status(StatusRequest request) async {
|
||||
try {
|
||||
if (_rpcClient == null) {
|
||||
await _initializeClient();
|
||||
}
|
||||
return await _rpcClient!.status(request, options: CallOptions(timeout: TIMEOUT_DURATION));
|
||||
} catch (e) {
|
||||
print("Error getting status: $e");
|
||||
return StatusResponse();
|
||||
}
|
||||
}
|
||||
|
||||
static Future<CreateResponse> create(CreateRequest request) async {
|
||||
try {
|
||||
if (_rpcClient == null) {
|
||||
await _initializeClient();
|
||||
}
|
||||
return await _rpcClient!.create(request, options: CallOptions(timeout: TIMEOUT_DURATION));
|
||||
} catch (e) {
|
||||
print("Error getting create: $e");
|
||||
return CreateResponse();
|
||||
}
|
||||
}
|
||||
|
||||
static Future<ResponseStream<Utxo>?> utxos(UtxosRequest request) async {
|
||||
try {
|
||||
if (_rpcClient == null) {
|
||||
await _initializeClient();
|
||||
}
|
||||
// this is a stream, so we should have an effectively infinite timeout:
|
||||
return _rpcClient!.utxos(request, options: CallOptions(timeout: const Duration(days: 1000 * 365)));
|
||||
} catch (e) {
|
||||
print("Error getting utxos: $e");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
32
cw_mweb/lib/cw_mweb_method_channel.dart
Normal file
32
cw_mweb/lib/cw_mweb_method_channel.dart
Normal file
|
@ -0,0 +1,32 @@
|
|||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'cw_mweb_platform_interface.dart';
|
||||
|
||||
/// An implementation of [CwMwebPlatform] that uses method channels.
|
||||
class MethodChannelCwMweb extends CwMwebPlatform {
|
||||
/// The method channel used to interact with the native platform.
|
||||
@visibleForTesting
|
||||
final methodChannel = const MethodChannel('cw_mweb');
|
||||
|
||||
@override
|
||||
Future<int?> start(String dataDir) async {
|
||||
final result = await methodChannel.invokeMethod<int>('start', {'dataDir': dataDir});
|
||||
return result;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> stop() async {
|
||||
await methodChannel.invokeMethod<void>('stop');
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String?> address(Uint8List scanSecret, Uint8List spendPub, int index) async {
|
||||
final result = await methodChannel.invokeMethod<String>('address', {
|
||||
'scanSecret': scanSecret,
|
||||
'spendPub': spendPub,
|
||||
'index': index,
|
||||
});
|
||||
return result;
|
||||
}
|
||||
}
|
39
cw_mweb/lib/cw_mweb_platform_interface.dart
Normal file
39
cw_mweb/lib/cw_mweb_platform_interface.dart
Normal file
|
@ -0,0 +1,39 @@
|
|||
import 'dart:typed_data';
|
||||
|
||||
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
|
||||
|
||||
import 'cw_mweb_method_channel.dart';
|
||||
|
||||
abstract class CwMwebPlatform extends PlatformInterface {
|
||||
/// Constructs a CwMwebPlatform.
|
||||
CwMwebPlatform() : super(token: _token);
|
||||
|
||||
static final Object _token = Object();
|
||||
|
||||
static CwMwebPlatform _instance = MethodChannelCwMweb();
|
||||
|
||||
/// The default instance of [CwMwebPlatform] to use.
|
||||
///
|
||||
/// Defaults to [MethodChannelCwMweb].
|
||||
static CwMwebPlatform get instance => _instance;
|
||||
|
||||
/// Platform-specific implementations should set this with their own
|
||||
/// platform-specific class that extends [CwMwebPlatform] when
|
||||
/// they register themselves.
|
||||
static set instance(CwMwebPlatform instance) {
|
||||
PlatformInterface.verifyToken(instance, _token);
|
||||
_instance = instance;
|
||||
}
|
||||
|
||||
Future<int?> start(String dataDir) {
|
||||
throw UnimplementedError('start() has not been implemented.');
|
||||
}
|
||||
|
||||
Future<void> stop() {
|
||||
throw UnimplementedError('stop() has not been implemented.');
|
||||
}
|
||||
|
||||
Future<String?> address(Uint8List scanSecret, Uint8List spendPub, int index) {
|
||||
throw UnimplementedError('address(int) has not been implemented.');
|
||||
}
|
||||
}
|
801
cw_mweb/lib/mwebd.pb.dart
Normal file
801
cw_mweb/lib/mwebd.pb.dart
Normal file
|
@ -0,0 +1,801 @@
|
|||
//
|
||||
// Generated code. Do not modify.
|
||||
// source: mwebd.proto
|
||||
//
|
||||
// @dart = 2.12
|
||||
|
||||
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
|
||||
// ignore_for_file: constant_identifier_names, library_prefixes
|
||||
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
|
||||
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
||||
|
||||
import 'dart:core' as $core;
|
||||
|
||||
import 'package:fixnum/fixnum.dart' as $fixnum;
|
||||
import 'package:protobuf/protobuf.dart' as $pb;
|
||||
|
||||
class StatusRequest extends $pb.GeneratedMessage {
|
||||
factory StatusRequest() => create();
|
||||
StatusRequest._() : super();
|
||||
factory StatusRequest.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory StatusRequest.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'StatusRequest', createEmptyInstance: create)
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
StatusRequest clone() => StatusRequest()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
StatusRequest copyWith(void Function(StatusRequest) updates) => super.copyWith((message) => updates(message as StatusRequest)) as StatusRequest;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static StatusRequest create() => StatusRequest._();
|
||||
StatusRequest createEmptyInstance() => create();
|
||||
static $pb.PbList<StatusRequest> createRepeated() => $pb.PbList<StatusRequest>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static StatusRequest getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<StatusRequest>(create);
|
||||
static StatusRequest? _defaultInstance;
|
||||
}
|
||||
|
||||
class StatusResponse extends $pb.GeneratedMessage {
|
||||
factory StatusResponse({
|
||||
$core.int? blockHeaderHeight,
|
||||
$core.int? mwebHeaderHeight,
|
||||
$core.int? mwebUtxosHeight,
|
||||
$core.int? blockTime,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (blockHeaderHeight != null) {
|
||||
$result.blockHeaderHeight = blockHeaderHeight;
|
||||
}
|
||||
if (mwebHeaderHeight != null) {
|
||||
$result.mwebHeaderHeight = mwebHeaderHeight;
|
||||
}
|
||||
if (mwebUtxosHeight != null) {
|
||||
$result.mwebUtxosHeight = mwebUtxosHeight;
|
||||
}
|
||||
if (blockTime != null) {
|
||||
$result.blockTime = blockTime;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
StatusResponse._() : super();
|
||||
factory StatusResponse.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory StatusResponse.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'StatusResponse', createEmptyInstance: create)
|
||||
..a<$core.int>(1, _omitFieldNames ? '' : 'blockHeaderHeight', $pb.PbFieldType.O3)
|
||||
..a<$core.int>(2, _omitFieldNames ? '' : 'mwebHeaderHeight', $pb.PbFieldType.O3)
|
||||
..a<$core.int>(3, _omitFieldNames ? '' : 'mwebUtxosHeight', $pb.PbFieldType.O3)
|
||||
..a<$core.int>(4, _omitFieldNames ? '' : 'blockTime', $pb.PbFieldType.OU3)
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
StatusResponse clone() => StatusResponse()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
StatusResponse copyWith(void Function(StatusResponse) updates) => super.copyWith((message) => updates(message as StatusResponse)) as StatusResponse;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static StatusResponse create() => StatusResponse._();
|
||||
StatusResponse createEmptyInstance() => create();
|
||||
static $pb.PbList<StatusResponse> createRepeated() => $pb.PbList<StatusResponse>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static StatusResponse getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<StatusResponse>(create);
|
||||
static StatusResponse? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.int get blockHeaderHeight => $_getIZ(0);
|
||||
@$pb.TagNumber(1)
|
||||
set blockHeaderHeight($core.int v) { $_setSignedInt32(0, v); }
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasBlockHeaderHeight() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearBlockHeaderHeight() => clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.int get mwebHeaderHeight => $_getIZ(1);
|
||||
@$pb.TagNumber(2)
|
||||
set mwebHeaderHeight($core.int v) { $_setSignedInt32(1, v); }
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasMwebHeaderHeight() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearMwebHeaderHeight() => clearField(2);
|
||||
|
||||
@$pb.TagNumber(3)
|
||||
$core.int get mwebUtxosHeight => $_getIZ(2);
|
||||
@$pb.TagNumber(3)
|
||||
set mwebUtxosHeight($core.int v) { $_setSignedInt32(2, v); }
|
||||
@$pb.TagNumber(3)
|
||||
$core.bool hasMwebUtxosHeight() => $_has(2);
|
||||
@$pb.TagNumber(3)
|
||||
void clearMwebUtxosHeight() => clearField(3);
|
||||
|
||||
@$pb.TagNumber(4)
|
||||
$core.int get blockTime => $_getIZ(3);
|
||||
@$pb.TagNumber(4)
|
||||
set blockTime($core.int v) { $_setUnsignedInt32(3, v); }
|
||||
@$pb.TagNumber(4)
|
||||
$core.bool hasBlockTime() => $_has(3);
|
||||
@$pb.TagNumber(4)
|
||||
void clearBlockTime() => clearField(4);
|
||||
}
|
||||
|
||||
class UtxosRequest extends $pb.GeneratedMessage {
|
||||
factory UtxosRequest({
|
||||
$core.int? fromHeight,
|
||||
$core.List<$core.int>? scanSecret,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (fromHeight != null) {
|
||||
$result.fromHeight = fromHeight;
|
||||
}
|
||||
if (scanSecret != null) {
|
||||
$result.scanSecret = scanSecret;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
UtxosRequest._() : super();
|
||||
factory UtxosRequest.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory UtxosRequest.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'UtxosRequest', createEmptyInstance: create)
|
||||
..a<$core.int>(1, _omitFieldNames ? '' : 'fromHeight', $pb.PbFieldType.O3)
|
||||
..a<$core.List<$core.int>>(2, _omitFieldNames ? '' : 'scanSecret', $pb.PbFieldType.OY)
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
UtxosRequest clone() => UtxosRequest()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
UtxosRequest copyWith(void Function(UtxosRequest) updates) => super.copyWith((message) => updates(message as UtxosRequest)) as UtxosRequest;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static UtxosRequest create() => UtxosRequest._();
|
||||
UtxosRequest createEmptyInstance() => create();
|
||||
static $pb.PbList<UtxosRequest> createRepeated() => $pb.PbList<UtxosRequest>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static UtxosRequest getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<UtxosRequest>(create);
|
||||
static UtxosRequest? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.int get fromHeight => $_getIZ(0);
|
||||
@$pb.TagNumber(1)
|
||||
set fromHeight($core.int v) { $_setSignedInt32(0, v); }
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasFromHeight() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearFromHeight() => clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.List<$core.int> get scanSecret => $_getN(1);
|
||||
@$pb.TagNumber(2)
|
||||
set scanSecret($core.List<$core.int> v) { $_setBytes(1, v); }
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasScanSecret() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearScanSecret() => clearField(2);
|
||||
}
|
||||
|
||||
class Utxo extends $pb.GeneratedMessage {
|
||||
factory Utxo({
|
||||
$core.int? height,
|
||||
$fixnum.Int64? value,
|
||||
$core.String? address,
|
||||
$core.String? outputId,
|
||||
$core.int? blockTime,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (height != null) {
|
||||
$result.height = height;
|
||||
}
|
||||
if (value != null) {
|
||||
$result.value = value;
|
||||
}
|
||||
if (address != null) {
|
||||
$result.address = address;
|
||||
}
|
||||
if (outputId != null) {
|
||||
$result.outputId = outputId;
|
||||
}
|
||||
if (blockTime != null) {
|
||||
$result.blockTime = blockTime;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
Utxo._() : super();
|
||||
factory Utxo.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory Utxo.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'Utxo', createEmptyInstance: create)
|
||||
..a<$core.int>(1, _omitFieldNames ? '' : 'height', $pb.PbFieldType.O3)
|
||||
..a<$fixnum.Int64>(2, _omitFieldNames ? '' : 'value', $pb.PbFieldType.OU6, defaultOrMaker: $fixnum.Int64.ZERO)
|
||||
..aOS(3, _omitFieldNames ? '' : 'address')
|
||||
..aOS(4, _omitFieldNames ? '' : 'outputId')
|
||||
..a<$core.int>(5, _omitFieldNames ? '' : 'blockTime', $pb.PbFieldType.OU3)
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
Utxo clone() => Utxo()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
Utxo copyWith(void Function(Utxo) updates) => super.copyWith((message) => updates(message as Utxo)) as Utxo;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static Utxo create() => Utxo._();
|
||||
Utxo createEmptyInstance() => create();
|
||||
static $pb.PbList<Utxo> createRepeated() => $pb.PbList<Utxo>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static Utxo getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<Utxo>(create);
|
||||
static Utxo? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.int get height => $_getIZ(0);
|
||||
@$pb.TagNumber(1)
|
||||
set height($core.int v) { $_setSignedInt32(0, v); }
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasHeight() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearHeight() => clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$fixnum.Int64 get value => $_getI64(1);
|
||||
@$pb.TagNumber(2)
|
||||
set value($fixnum.Int64 v) { $_setInt64(1, v); }
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasValue() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearValue() => clearField(2);
|
||||
|
||||
@$pb.TagNumber(3)
|
||||
$core.String get address => $_getSZ(2);
|
||||
@$pb.TagNumber(3)
|
||||
set address($core.String v) { $_setString(2, v); }
|
||||
@$pb.TagNumber(3)
|
||||
$core.bool hasAddress() => $_has(2);
|
||||
@$pb.TagNumber(3)
|
||||
void clearAddress() => clearField(3);
|
||||
|
||||
@$pb.TagNumber(4)
|
||||
$core.String get outputId => $_getSZ(3);
|
||||
@$pb.TagNumber(4)
|
||||
set outputId($core.String v) { $_setString(3, v); }
|
||||
@$pb.TagNumber(4)
|
||||
$core.bool hasOutputId() => $_has(3);
|
||||
@$pb.TagNumber(4)
|
||||
void clearOutputId() => clearField(4);
|
||||
|
||||
@$pb.TagNumber(5)
|
||||
$core.int get blockTime => $_getIZ(4);
|
||||
@$pb.TagNumber(5)
|
||||
set blockTime($core.int v) { $_setUnsignedInt32(4, v); }
|
||||
@$pb.TagNumber(5)
|
||||
$core.bool hasBlockTime() => $_has(4);
|
||||
@$pb.TagNumber(5)
|
||||
void clearBlockTime() => clearField(5);
|
||||
}
|
||||
|
||||
class AddressRequest extends $pb.GeneratedMessage {
|
||||
factory AddressRequest({
|
||||
$core.int? fromIndex,
|
||||
$core.int? toIndex,
|
||||
$core.List<$core.int>? scanSecret,
|
||||
$core.List<$core.int>? spendPubkey,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (fromIndex != null) {
|
||||
$result.fromIndex = fromIndex;
|
||||
}
|
||||
if (toIndex != null) {
|
||||
$result.toIndex = toIndex;
|
||||
}
|
||||
if (scanSecret != null) {
|
||||
$result.scanSecret = scanSecret;
|
||||
}
|
||||
if (spendPubkey != null) {
|
||||
$result.spendPubkey = spendPubkey;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
AddressRequest._() : super();
|
||||
factory AddressRequest.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory AddressRequest.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'AddressRequest', createEmptyInstance: create)
|
||||
..a<$core.int>(1, _omitFieldNames ? '' : 'fromIndex', $pb.PbFieldType.OU3)
|
||||
..a<$core.int>(2, _omitFieldNames ? '' : 'toIndex', $pb.PbFieldType.OU3)
|
||||
..a<$core.List<$core.int>>(3, _omitFieldNames ? '' : 'scanSecret', $pb.PbFieldType.OY)
|
||||
..a<$core.List<$core.int>>(4, _omitFieldNames ? '' : 'spendPubkey', $pb.PbFieldType.OY)
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
AddressRequest clone() => AddressRequest()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
AddressRequest copyWith(void Function(AddressRequest) updates) => super.copyWith((message) => updates(message as AddressRequest)) as AddressRequest;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static AddressRequest create() => AddressRequest._();
|
||||
AddressRequest createEmptyInstance() => create();
|
||||
static $pb.PbList<AddressRequest> createRepeated() => $pb.PbList<AddressRequest>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static AddressRequest getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<AddressRequest>(create);
|
||||
static AddressRequest? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.int get fromIndex => $_getIZ(0);
|
||||
@$pb.TagNumber(1)
|
||||
set fromIndex($core.int v) { $_setUnsignedInt32(0, v); }
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasFromIndex() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearFromIndex() => clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.int get toIndex => $_getIZ(1);
|
||||
@$pb.TagNumber(2)
|
||||
set toIndex($core.int v) { $_setUnsignedInt32(1, v); }
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasToIndex() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearToIndex() => clearField(2);
|
||||
|
||||
@$pb.TagNumber(3)
|
||||
$core.List<$core.int> get scanSecret => $_getN(2);
|
||||
@$pb.TagNumber(3)
|
||||
set scanSecret($core.List<$core.int> v) { $_setBytes(2, v); }
|
||||
@$pb.TagNumber(3)
|
||||
$core.bool hasScanSecret() => $_has(2);
|
||||
@$pb.TagNumber(3)
|
||||
void clearScanSecret() => clearField(3);
|
||||
|
||||
@$pb.TagNumber(4)
|
||||
$core.List<$core.int> get spendPubkey => $_getN(3);
|
||||
@$pb.TagNumber(4)
|
||||
set spendPubkey($core.List<$core.int> v) { $_setBytes(3, v); }
|
||||
@$pb.TagNumber(4)
|
||||
$core.bool hasSpendPubkey() => $_has(3);
|
||||
@$pb.TagNumber(4)
|
||||
void clearSpendPubkey() => clearField(4);
|
||||
}
|
||||
|
||||
class AddressResponse extends $pb.GeneratedMessage {
|
||||
factory AddressResponse({
|
||||
$core.Iterable<$core.String>? address,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (address != null) {
|
||||
$result.address.addAll(address);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
AddressResponse._() : super();
|
||||
factory AddressResponse.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory AddressResponse.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'AddressResponse', createEmptyInstance: create)
|
||||
..pPS(1, _omitFieldNames ? '' : 'address')
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
AddressResponse clone() => AddressResponse()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
AddressResponse copyWith(void Function(AddressResponse) updates) => super.copyWith((message) => updates(message as AddressResponse)) as AddressResponse;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static AddressResponse create() => AddressResponse._();
|
||||
AddressResponse createEmptyInstance() => create();
|
||||
static $pb.PbList<AddressResponse> createRepeated() => $pb.PbList<AddressResponse>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static AddressResponse getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<AddressResponse>(create);
|
||||
static AddressResponse? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.List<$core.String> get address => $_getList(0);
|
||||
}
|
||||
|
||||
class SpentRequest extends $pb.GeneratedMessage {
|
||||
factory SpentRequest({
|
||||
$core.Iterable<$core.String>? outputId,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (outputId != null) {
|
||||
$result.outputId.addAll(outputId);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
SpentRequest._() : super();
|
||||
factory SpentRequest.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory SpentRequest.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'SpentRequest', createEmptyInstance: create)
|
||||
..pPS(1, _omitFieldNames ? '' : 'outputId')
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
SpentRequest clone() => SpentRequest()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
SpentRequest copyWith(void Function(SpentRequest) updates) => super.copyWith((message) => updates(message as SpentRequest)) as SpentRequest;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static SpentRequest create() => SpentRequest._();
|
||||
SpentRequest createEmptyInstance() => create();
|
||||
static $pb.PbList<SpentRequest> createRepeated() => $pb.PbList<SpentRequest>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static SpentRequest getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<SpentRequest>(create);
|
||||
static SpentRequest? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.List<$core.String> get outputId => $_getList(0);
|
||||
}
|
||||
|
||||
class SpentResponse extends $pb.GeneratedMessage {
|
||||
factory SpentResponse({
|
||||
$core.Iterable<$core.String>? outputId,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (outputId != null) {
|
||||
$result.outputId.addAll(outputId);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
SpentResponse._() : super();
|
||||
factory SpentResponse.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory SpentResponse.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'SpentResponse', createEmptyInstance: create)
|
||||
..pPS(1, _omitFieldNames ? '' : 'outputId')
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
SpentResponse clone() => SpentResponse()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
SpentResponse copyWith(void Function(SpentResponse) updates) => super.copyWith((message) => updates(message as SpentResponse)) as SpentResponse;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static SpentResponse create() => SpentResponse._();
|
||||
SpentResponse createEmptyInstance() => create();
|
||||
static $pb.PbList<SpentResponse> createRepeated() => $pb.PbList<SpentResponse>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static SpentResponse getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<SpentResponse>(create);
|
||||
static SpentResponse? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.List<$core.String> get outputId => $_getList(0);
|
||||
}
|
||||
|
||||
class CreateRequest extends $pb.GeneratedMessage {
|
||||
factory CreateRequest({
|
||||
$core.List<$core.int>? rawTx,
|
||||
$core.List<$core.int>? scanSecret,
|
||||
$core.List<$core.int>? spendSecret,
|
||||
$fixnum.Int64? feeRatePerKb,
|
||||
$core.bool? dryRun,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (rawTx != null) {
|
||||
$result.rawTx = rawTx;
|
||||
}
|
||||
if (scanSecret != null) {
|
||||
$result.scanSecret = scanSecret;
|
||||
}
|
||||
if (spendSecret != null) {
|
||||
$result.spendSecret = spendSecret;
|
||||
}
|
||||
if (feeRatePerKb != null) {
|
||||
$result.feeRatePerKb = feeRatePerKb;
|
||||
}
|
||||
if (dryRun != null) {
|
||||
$result.dryRun = dryRun;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
CreateRequest._() : super();
|
||||
factory CreateRequest.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory CreateRequest.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'CreateRequest', createEmptyInstance: create)
|
||||
..a<$core.List<$core.int>>(1, _omitFieldNames ? '' : 'rawTx', $pb.PbFieldType.OY)
|
||||
..a<$core.List<$core.int>>(2, _omitFieldNames ? '' : 'scanSecret', $pb.PbFieldType.OY)
|
||||
..a<$core.List<$core.int>>(3, _omitFieldNames ? '' : 'spendSecret', $pb.PbFieldType.OY)
|
||||
..a<$fixnum.Int64>(4, _omitFieldNames ? '' : 'feeRatePerKb', $pb.PbFieldType.OU6, defaultOrMaker: $fixnum.Int64.ZERO)
|
||||
..aOB(5, _omitFieldNames ? '' : 'dryRun')
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
CreateRequest clone() => CreateRequest()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
CreateRequest copyWith(void Function(CreateRequest) updates) => super.copyWith((message) => updates(message as CreateRequest)) as CreateRequest;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static CreateRequest create() => CreateRequest._();
|
||||
CreateRequest createEmptyInstance() => create();
|
||||
static $pb.PbList<CreateRequest> createRepeated() => $pb.PbList<CreateRequest>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static CreateRequest getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<CreateRequest>(create);
|
||||
static CreateRequest? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.List<$core.int> get rawTx => $_getN(0);
|
||||
@$pb.TagNumber(1)
|
||||
set rawTx($core.List<$core.int> v) { $_setBytes(0, v); }
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasRawTx() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearRawTx() => clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.List<$core.int> get scanSecret => $_getN(1);
|
||||
@$pb.TagNumber(2)
|
||||
set scanSecret($core.List<$core.int> v) { $_setBytes(1, v); }
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasScanSecret() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearScanSecret() => clearField(2);
|
||||
|
||||
@$pb.TagNumber(3)
|
||||
$core.List<$core.int> get spendSecret => $_getN(2);
|
||||
@$pb.TagNumber(3)
|
||||
set spendSecret($core.List<$core.int> v) { $_setBytes(2, v); }
|
||||
@$pb.TagNumber(3)
|
||||
$core.bool hasSpendSecret() => $_has(2);
|
||||
@$pb.TagNumber(3)
|
||||
void clearSpendSecret() => clearField(3);
|
||||
|
||||
@$pb.TagNumber(4)
|
||||
$fixnum.Int64 get feeRatePerKb => $_getI64(3);
|
||||
@$pb.TagNumber(4)
|
||||
set feeRatePerKb($fixnum.Int64 v) { $_setInt64(3, v); }
|
||||
@$pb.TagNumber(4)
|
||||
$core.bool hasFeeRatePerKb() => $_has(3);
|
||||
@$pb.TagNumber(4)
|
||||
void clearFeeRatePerKb() => clearField(4);
|
||||
|
||||
@$pb.TagNumber(5)
|
||||
$core.bool get dryRun => $_getBF(4);
|
||||
@$pb.TagNumber(5)
|
||||
set dryRun($core.bool v) { $_setBool(4, v); }
|
||||
@$pb.TagNumber(5)
|
||||
$core.bool hasDryRun() => $_has(4);
|
||||
@$pb.TagNumber(5)
|
||||
void clearDryRun() => clearField(5);
|
||||
}
|
||||
|
||||
class CreateResponse extends $pb.GeneratedMessage {
|
||||
factory CreateResponse({
|
||||
$core.List<$core.int>? rawTx,
|
||||
$core.Iterable<$core.String>? outputId,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (rawTx != null) {
|
||||
$result.rawTx = rawTx;
|
||||
}
|
||||
if (outputId != null) {
|
||||
$result.outputId.addAll(outputId);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
CreateResponse._() : super();
|
||||
factory CreateResponse.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory CreateResponse.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'CreateResponse', createEmptyInstance: create)
|
||||
..a<$core.List<$core.int>>(1, _omitFieldNames ? '' : 'rawTx', $pb.PbFieldType.OY)
|
||||
..pPS(2, _omitFieldNames ? '' : 'outputId')
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
CreateResponse clone() => CreateResponse()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
CreateResponse copyWith(void Function(CreateResponse) updates) => super.copyWith((message) => updates(message as CreateResponse)) as CreateResponse;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static CreateResponse create() => CreateResponse._();
|
||||
CreateResponse createEmptyInstance() => create();
|
||||
static $pb.PbList<CreateResponse> createRepeated() => $pb.PbList<CreateResponse>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static CreateResponse getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<CreateResponse>(create);
|
||||
static CreateResponse? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.List<$core.int> get rawTx => $_getN(0);
|
||||
@$pb.TagNumber(1)
|
||||
set rawTx($core.List<$core.int> v) { $_setBytes(0, v); }
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasRawTx() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearRawTx() => clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.List<$core.String> get outputId => $_getList(1);
|
||||
}
|
||||
|
||||
class BroadcastRequest extends $pb.GeneratedMessage {
|
||||
factory BroadcastRequest({
|
||||
$core.List<$core.int>? rawTx,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (rawTx != null) {
|
||||
$result.rawTx = rawTx;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
BroadcastRequest._() : super();
|
||||
factory BroadcastRequest.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory BroadcastRequest.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'BroadcastRequest', createEmptyInstance: create)
|
||||
..a<$core.List<$core.int>>(1, _omitFieldNames ? '' : 'rawTx', $pb.PbFieldType.OY)
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
BroadcastRequest clone() => BroadcastRequest()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
BroadcastRequest copyWith(void Function(BroadcastRequest) updates) => super.copyWith((message) => updates(message as BroadcastRequest)) as BroadcastRequest;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static BroadcastRequest create() => BroadcastRequest._();
|
||||
BroadcastRequest createEmptyInstance() => create();
|
||||
static $pb.PbList<BroadcastRequest> createRepeated() => $pb.PbList<BroadcastRequest>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static BroadcastRequest getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<BroadcastRequest>(create);
|
||||
static BroadcastRequest? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.List<$core.int> get rawTx => $_getN(0);
|
||||
@$pb.TagNumber(1)
|
||||
set rawTx($core.List<$core.int> v) { $_setBytes(0, v); }
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasRawTx() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearRawTx() => clearField(1);
|
||||
}
|
||||
|
||||
class BroadcastResponse extends $pb.GeneratedMessage {
|
||||
factory BroadcastResponse({
|
||||
$core.String? txid,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (txid != null) {
|
||||
$result.txid = txid;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
BroadcastResponse._() : super();
|
||||
factory BroadcastResponse.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
|
||||
factory BroadcastResponse.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromJson(i, r);
|
||||
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(_omitMessageNames ? '' : 'BroadcastResponse', createEmptyInstance: create)
|
||||
..aOS(1, _omitFieldNames ? '' : 'txid')
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
BroadcastResponse clone() => BroadcastResponse()..mergeFromMessage(this);
|
||||
@$core.Deprecated(
|
||||
'Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
BroadcastResponse copyWith(void Function(BroadcastResponse) updates) => super.copyWith((message) => updates(message as BroadcastResponse)) as BroadcastResponse;
|
||||
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static BroadcastResponse create() => BroadcastResponse._();
|
||||
BroadcastResponse createEmptyInstance() => create();
|
||||
static $pb.PbList<BroadcastResponse> createRepeated() => $pb.PbList<BroadcastResponse>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static BroadcastResponse getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<BroadcastResponse>(create);
|
||||
static BroadcastResponse? _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.String get txid => $_getSZ(0);
|
||||
@$pb.TagNumber(1)
|
||||
set txid($core.String v) { $_setString(0, v); }
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasTxid() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearTxid() => clearField(1);
|
||||
}
|
||||
|
||||
|
||||
const _omitFieldNames = $core.bool.fromEnvironment('protobuf.omit_field_names');
|
||||
const _omitMessageNames = $core.bool.fromEnvironment('protobuf.omit_message_names');
|
159
cw_mweb/lib/mwebd.pbgrpc.dart
Normal file
159
cw_mweb/lib/mwebd.pbgrpc.dart
Normal file
|
@ -0,0 +1,159 @@
|
|||
//
|
||||
// Generated code. Do not modify.
|
||||
// source: mwebd.proto
|
||||
//
|
||||
// @dart = 2.12
|
||||
|
||||
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
|
||||
// ignore_for_file: constant_identifier_names, library_prefixes
|
||||
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
|
||||
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
|
||||
|
||||
import 'dart:async' as $async;
|
||||
import 'dart:core' as $core;
|
||||
|
||||
import 'package:grpc/service_api.dart' as $grpc;
|
||||
import 'package:protobuf/protobuf.dart' as $pb;
|
||||
|
||||
import 'mwebd.pb.dart' as $0;
|
||||
|
||||
export 'mwebd.pb.dart';
|
||||
|
||||
@$pb.GrpcServiceName('Rpc')
|
||||
class RpcClient extends $grpc.Client {
|
||||
static final _$status = $grpc.ClientMethod<$0.StatusRequest, $0.StatusResponse>(
|
||||
'/Rpc/Status',
|
||||
($0.StatusRequest value) => value.writeToBuffer(),
|
||||
($core.List<$core.int> value) => $0.StatusResponse.fromBuffer(value));
|
||||
static final _$utxos = $grpc.ClientMethod<$0.UtxosRequest, $0.Utxo>(
|
||||
'/Rpc/Utxos',
|
||||
($0.UtxosRequest value) => value.writeToBuffer(),
|
||||
($core.List<$core.int> value) => $0.Utxo.fromBuffer(value));
|
||||
static final _$addresses = $grpc.ClientMethod<$0.AddressRequest, $0.AddressResponse>(
|
||||
'/Rpc/Addresses',
|
||||
($0.AddressRequest value) => value.writeToBuffer(),
|
||||
($core.List<$core.int> value) => $0.AddressResponse.fromBuffer(value));
|
||||
static final _$spent = $grpc.ClientMethod<$0.SpentRequest, $0.SpentResponse>(
|
||||
'/Rpc/Spent',
|
||||
($0.SpentRequest value) => value.writeToBuffer(),
|
||||
($core.List<$core.int> value) => $0.SpentResponse.fromBuffer(value));
|
||||
static final _$create = $grpc.ClientMethod<$0.CreateRequest, $0.CreateResponse>(
|
||||
'/Rpc/Create',
|
||||
($0.CreateRequest value) => value.writeToBuffer(),
|
||||
($core.List<$core.int> value) => $0.CreateResponse.fromBuffer(value));
|
||||
static final _$broadcast = $grpc.ClientMethod<$0.BroadcastRequest, $0.BroadcastResponse>(
|
||||
'/Rpc/Broadcast',
|
||||
($0.BroadcastRequest value) => value.writeToBuffer(),
|
||||
($core.List<$core.int> value) => $0.BroadcastResponse.fromBuffer(value));
|
||||
|
||||
RpcClient($grpc.ClientChannel channel,
|
||||
{$grpc.CallOptions? options,
|
||||
$core.Iterable<$grpc.ClientInterceptor>? interceptors})
|
||||
: super(channel, options: options,
|
||||
interceptors: interceptors);
|
||||
|
||||
$grpc.ResponseFuture<$0.StatusResponse> status($0.StatusRequest request, {$grpc.CallOptions? options}) {
|
||||
return $createUnaryCall(_$status, request, options: options);
|
||||
}
|
||||
|
||||
$grpc.ResponseStream<$0.Utxo> utxos($0.UtxosRequest request, {$grpc.CallOptions? options}) {
|
||||
return $createStreamingCall(_$utxos, $async.Stream.fromIterable([request]), options: options);
|
||||
}
|
||||
|
||||
$grpc.ResponseFuture<$0.AddressResponse> addresses($0.AddressRequest request, {$grpc.CallOptions? options}) {
|
||||
return $createUnaryCall(_$addresses, request, options: options);
|
||||
}
|
||||
|
||||
$grpc.ResponseFuture<$0.SpentResponse> spent($0.SpentRequest request, {$grpc.CallOptions? options}) {
|
||||
return $createUnaryCall(_$spent, request, options: options);
|
||||
}
|
||||
|
||||
$grpc.ResponseFuture<$0.CreateResponse> create($0.CreateRequest request, {$grpc.CallOptions? options}) {
|
||||
return $createUnaryCall(_$create, request, options: options);
|
||||
}
|
||||
|
||||
$grpc.ResponseFuture<$0.BroadcastResponse> broadcast($0.BroadcastRequest request, {$grpc.CallOptions? options}) {
|
||||
return $createUnaryCall(_$broadcast, request, options: options);
|
||||
}
|
||||
}
|
||||
|
||||
@$pb.GrpcServiceName('Rpc')
|
||||
abstract class RpcServiceBase extends $grpc.Service {
|
||||
$core.String get $name => 'Rpc';
|
||||
|
||||
RpcServiceBase() {
|
||||
$addMethod($grpc.ServiceMethod<$0.StatusRequest, $0.StatusResponse>(
|
||||
'Status',
|
||||
status_Pre,
|
||||
false,
|
||||
false,
|
||||
($core.List<$core.int> value) => $0.StatusRequest.fromBuffer(value),
|
||||
($0.StatusResponse value) => value.writeToBuffer()));
|
||||
$addMethod($grpc.ServiceMethod<$0.UtxosRequest, $0.Utxo>(
|
||||
'Utxos',
|
||||
utxos_Pre,
|
||||
false,
|
||||
true,
|
||||
($core.List<$core.int> value) => $0.UtxosRequest.fromBuffer(value),
|
||||
($0.Utxo value) => value.writeToBuffer()));
|
||||
$addMethod($grpc.ServiceMethod<$0.AddressRequest, $0.AddressResponse>(
|
||||
'Addresses',
|
||||
addresses_Pre,
|
||||
false,
|
||||
false,
|
||||
($core.List<$core.int> value) => $0.AddressRequest.fromBuffer(value),
|
||||
($0.AddressResponse value) => value.writeToBuffer()));
|
||||
$addMethod($grpc.ServiceMethod<$0.SpentRequest, $0.SpentResponse>(
|
||||
'Spent',
|
||||
spent_Pre,
|
||||
false,
|
||||
false,
|
||||
($core.List<$core.int> value) => $0.SpentRequest.fromBuffer(value),
|
||||
($0.SpentResponse value) => value.writeToBuffer()));
|
||||
$addMethod($grpc.ServiceMethod<$0.CreateRequest, $0.CreateResponse>(
|
||||
'Create',
|
||||
create_Pre,
|
||||
false,
|
||||
false,
|
||||
($core.List<$core.int> value) => $0.CreateRequest.fromBuffer(value),
|
||||
($0.CreateResponse value) => value.writeToBuffer()));
|
||||
$addMethod($grpc.ServiceMethod<$0.BroadcastRequest, $0.BroadcastResponse>(
|
||||
'Broadcast',
|
||||
broadcast_Pre,
|
||||
false,
|
||||
false,
|
||||
($core.List<$core.int> value) => $0.BroadcastRequest.fromBuffer(value),
|
||||
($0.BroadcastResponse value) => value.writeToBuffer()));
|
||||
}
|
||||
|
||||
$async.Future<$0.StatusResponse> status_Pre($grpc.ServiceCall call, $async.Future<$0.StatusRequest> request) async {
|
||||
return status(call, await request);
|
||||
}
|
||||
|
||||
$async.Stream<$0.Utxo> utxos_Pre($grpc.ServiceCall call, $async.Future<$0.UtxosRequest> request) async* {
|
||||
yield* utxos(call, await request);
|
||||
}
|
||||
|
||||
$async.Future<$0.AddressResponse> addresses_Pre($grpc.ServiceCall call, $async.Future<$0.AddressRequest> request) async {
|
||||
return addresses(call, await request);
|
||||
}
|
||||
|
||||
$async.Future<$0.SpentResponse> spent_Pre($grpc.ServiceCall call, $async.Future<$0.SpentRequest> request) async {
|
||||
return spent(call, await request);
|
||||
}
|
||||
|
||||
$async.Future<$0.CreateResponse> create_Pre($grpc.ServiceCall call, $async.Future<$0.CreateRequest> request) async {
|
||||
return create(call, await request);
|
||||
}
|
||||
|
||||
$async.Future<$0.BroadcastResponse> broadcast_Pre($grpc.ServiceCall call, $async.Future<$0.BroadcastRequest> request) async {
|
||||
return broadcast(call, await request);
|
||||
}
|
||||
|
||||
$async.Future<$0.StatusResponse> status($grpc.ServiceCall call, $0.StatusRequest request);
|
||||
$async.Stream<$0.Utxo> utxos($grpc.ServiceCall call, $0.UtxosRequest request);
|
||||
$async.Future<$0.AddressResponse> addresses($grpc.ServiceCall call, $0.AddressRequest request);
|
||||
$async.Future<$0.SpentResponse> spent($grpc.ServiceCall call, $0.SpentRequest request);
|
||||
$async.Future<$0.CreateResponse> create($grpc.ServiceCall call, $0.CreateRequest request);
|
||||
$async.Future<$0.BroadcastResponse> broadcast($grpc.ServiceCall call, $0.BroadcastRequest request);
|
||||
}
|
19
cw_mweb/macos/Classes/CwMwebPlugin.swift
Normal file
19
cw_mweb/macos/Classes/CwMwebPlugin.swift
Normal file
|
@ -0,0 +1,19 @@
|
|||
import Cocoa
|
||||
import FlutterMacOS
|
||||
|
||||
public class CwMwebPlugin: NSObject, FlutterPlugin {
|
||||
public static func register(with registrar: FlutterPluginRegistrar) {
|
||||
let channel = FlutterMethodChannel(name: "cw_mweb", binaryMessenger: registrar.messenger)
|
||||
let instance = CwMwebPlugin()
|
||||
registrar.addMethodCallDelegate(instance, channel: channel)
|
||||
}
|
||||
|
||||
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
|
||||
switch call.method {
|
||||
case "getPlatformVersion":
|
||||
result("macOS " + ProcessInfo.processInfo.operatingSystemVersionString)
|
||||
default:
|
||||
result(FlutterMethodNotImplemented)
|
||||
}
|
||||
}
|
||||
}
|
23
cw_mweb/macos/cw_mweb.podspec
Normal file
23
cw_mweb/macos/cw_mweb.podspec
Normal file
|
@ -0,0 +1,23 @@
|
|||
#
|
||||
# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html.
|
||||
# Run `pod lib lint cw_mweb.podspec` to validate before publishing.
|
||||
#
|
||||
Pod::Spec.new do |s|
|
||||
s.name = 'cw_mweb'
|
||||
s.version = '0.0.1'
|
||||
s.summary = 'A new Flutter plugin project.'
|
||||
s.description = <<-DESC
|
||||
A new Flutter plugin project.
|
||||
DESC
|
||||
s.homepage = 'http://example.com'
|
||||
s.license = { :file => '../LICENSE' }
|
||||
s.author = { 'Your Company' => 'email@example.com' }
|
||||
|
||||
s.source = { :path => '.' }
|
||||
s.source_files = 'Classes/**/*'
|
||||
s.dependency 'FlutterMacOS'
|
||||
|
||||
s.platform = :osx, '10.11'
|
||||
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' }
|
||||
s.swift_version = '5.0'
|
||||
end
|
76
cw_mweb/pubspec.yaml
Normal file
76
cw_mweb/pubspec.yaml
Normal file
|
@ -0,0 +1,76 @@
|
|||
name: cw_mweb
|
||||
description: A new Flutter plugin project.
|
||||
version: 0.0.1
|
||||
homepage:
|
||||
|
||||
environment:
|
||||
sdk: '>=3.0.6 <4.0.0'
|
||||
flutter: ">=3.3.0"
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
grpc: ^3.2.4
|
||||
path_provider: ^2.1.2
|
||||
plugin_platform_interface: ^2.0.2
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
flutter_lints: ^2.0.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 packages.
|
||||
flutter:
|
||||
# This section identifies this Flutter project as a plugin project.
|
||||
# The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.)
|
||||
# which should be registered in the plugin registry. This is required for
|
||||
# using method channels.
|
||||
# The Android 'package' specifies package in which the registered class is.
|
||||
# This is required for using method channels on Android.
|
||||
# The 'ffiPlugin' specifies that native code should be built and bundled.
|
||||
# This is required for using `dart:ffi`.
|
||||
# All these are used by the tooling to maintain consistency when
|
||||
# adding or updating assets for this project.
|
||||
plugin:
|
||||
platforms:
|
||||
android:
|
||||
package: com.cakewallet.mweb
|
||||
pluginClass: CwMwebPlugin
|
||||
ios:
|
||||
pluginClass: CwMwebPlugin
|
||||
macos:
|
||||
pluginClass: CwMwebPlugin
|
||||
|
||||
# To add assets to your plugin 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 plugin 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
|
2
ios/.gitignore
vendored
2
ios/.gitignore
vendored
|
@ -30,3 +30,5 @@ Runner/GeneratedPluginRegistrant.*
|
|||
!default.mode2v3
|
||||
!default.pbxuser
|
||||
!default.perspectivev3
|
||||
|
||||
Mwebd.xcframework
|
|
@ -7,6 +7,7 @@ PODS:
|
|||
- Flutter
|
||||
- ReachabilitySwift
|
||||
- CryptoSwift (1.8.2)
|
||||
- cw_mweb (0.0.1):
|
||||
- device_display_brightness (0.0.1):
|
||||
- Flutter
|
||||
- device_info_plus (0.0.1):
|
||||
|
@ -111,6 +112,7 @@ DEPENDENCIES:
|
|||
- barcode_scan2 (from `.symlinks/plugins/barcode_scan2/ios`)
|
||||
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
|
||||
- CryptoSwift
|
||||
- cw_mweb (from `.symlinks/plugins/cw_mweb/ios`)
|
||||
- device_display_brightness (from `.symlinks/plugins/device_display_brightness/ios`)
|
||||
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
|
||||
- devicelocale (from `.symlinks/plugins/devicelocale/ios`)
|
||||
|
@ -156,6 +158,8 @@ EXTERNAL SOURCES:
|
|||
:path: ".symlinks/plugins/barcode_scan2/ios"
|
||||
connectivity_plus:
|
||||
:path: ".symlinks/plugins/connectivity_plus/ios"
|
||||
cw_mweb:
|
||||
:path: ".symlinks/plugins/cw_mweb/ios"
|
||||
device_display_brightness:
|
||||
:path: ".symlinks/plugins/device_display_brightness/ios"
|
||||
device_info_plus:
|
||||
|
@ -211,6 +215,7 @@ SPEC CHECKSUMS:
|
|||
barcode_scan2: 0af2bb63c81b4565aab6cd78278e4c0fa136dbb0
|
||||
connectivity_plus: bf0076dd84a130856aa636df1c71ccaff908fa1d
|
||||
CryptoSwift: c63a805d8bb5e5538e88af4e44bb537776af11ea
|
||||
cw_mweb: 87af74f9659fed0c1a2cbfb44413f1070e79e3ae
|
||||
device_display_brightness: 1510e72c567a1f6ce6ffe393dcd9afd1426034f7
|
||||
device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6
|
||||
devicelocale: b22617f40038496deffba44747101255cee005b0
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
A3D5E17CC53DF13FA740DEFA /* RedeemSwap.swift in Resources */ = {isa = PBXBuildFile; fileRef = 9D2F2C9F2555316C95EE7EA3 /* RedeemSwap.swift */; settings = {ASSET_TAGS = (BreezSDK, ); }; };
|
||||
B6C6E59403ACDE44724C12F4 /* ServiceConfig.swift in Resources */ = {isa = PBXBuildFile; fileRef = B3D5E78267F5F18D882FDC3B /* ServiceConfig.swift */; settings = {ASSET_TAGS = (BreezSDK, ); }; };
|
||||
CE291CFE2C15DB9A00B9F709 /* WowneroWallet.framework in CopyFiles */ = {isa = PBXBuildFile; fileRef = CE291CFD2C15DB9A00B9F709 /* WowneroWallet.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
CEAFE4A02C53926F009FF3AD /* libresolv.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = C58D93382C00FAC6004BCF69 /* libresolv.tbd */; };
|
||||
CFEFC24F82F78FE747DF1D22 /* LnurlPayInfo.swift in Resources */ = {isa = PBXBuildFile; fileRef = 58C22CBD8C22B9D6023D59F8 /* LnurlPayInfo.swift */; settings = {ASSET_TAGS = (BreezSDK, ); }; };
|
||||
D0D7A0D4E13F31C4E02E235B /* ReceivePayment.swift in Resources */ = {isa = PBXBuildFile; fileRef = 91C524F800843E0A3F17E004 /* ReceivePayment.swift */; settings = {ASSET_TAGS = (BreezSDK, ); }; };
|
||||
D3AD73A327249AFE8F016A51 /* BreezSDK.swift in Resources */ = {isa = PBXBuildFile; fileRef = ABD6FCBB0F4244B090459128 /* BreezSDK.swift */; settings = {ASSET_TAGS = (BreezSDK, ); }; };
|
||||
|
@ -84,7 +85,9 @@
|
|||
ABD6FCBB0F4244B090459128 /* BreezSDK.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = BreezSDK.swift; path = "../.symlinks/plugins/breez_sdk/ios/bindings-swift/Sources/BreezSDK/BreezSDK.swift"; sourceTree = "<group>"; };
|
||||
AD0937B0140D5A4C24E73BEA /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
||||
B3D5E78267F5F18D882FDC3B /* ServiceConfig.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ServiceConfig.swift; path = "../.symlinks/plugins/breez_sdk/ios/bindings-swift/Sources/BreezSDK/ServiceConfig.swift"; sourceTree = "<group>"; };
|
||||
C58D93382C00FAC6004BCF69 /* libresolv.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libresolv.tbd; path = usr/lib/libresolv.tbd; sourceTree = SDKROOT; };
|
||||
CE291CFD2C15DB9A00B9F709 /* WowneroWallet.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = WowneroWallet.framework; sourceTree = "<group>"; };
|
||||
CEAFE49D2C539250009FF3AD /* Mwebd.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = Mwebd.xcframework; sourceTree = "<group>"; };
|
||||
DCEA540E3586164FB47AD13E /* LnurlPayInvoice.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = LnurlPayInvoice.swift; path = "../.symlinks/plugins/breez_sdk/ios/bindings-swift/Sources/BreezSDK/Task/LnurlPayInvoice.swift"; sourceTree = "<group>"; };
|
||||
F42258C3697CFE3C8C8D1933 /* ServiceLogger.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ServiceLogger.swift; path = "../.symlinks/plugins/breez_sdk/ios/bindings-swift/Sources/BreezSDK/ServiceLogger.swift"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
@ -95,6 +98,7 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
4DFD1BB54A3A50573E19A583 /* Pods_Runner.framework in Frameworks */,
|
||||
CEAFE4A02C53926F009FF3AD /* libresolv.tbd in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
@ -104,6 +108,8 @@
|
|||
06957875428D0F5AAE053765 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CEAFE49D2C539250009FF3AD /* Mwebd.xcframework */,
|
||||
C58D93382C00FAC6004BCF69 /* libresolv.tbd */,
|
||||
0C9986A3251A932F00D566FD /* CryptoSwift.framework */,
|
||||
3C663361C56EBB242598F609 /* Pods_Runner.framework */,
|
||||
);
|
||||
|
|
|
@ -220,9 +220,9 @@ class CWBitcoin extends Bitcoin {
|
|||
return BitcoinWalletService(walletInfoSource, unspentCoinSource, alwaysScan, isDirect);
|
||||
}
|
||||
|
||||
WalletService createLitecoinWalletService(
|
||||
Box<WalletInfo> walletInfoSource, Box<UnspentCoinsInfo> unspentCoinSource, bool isDirect) {
|
||||
return LitecoinWalletService(walletInfoSource, unspentCoinSource, isDirect);
|
||||
WalletService createLitecoinWalletService(Box<WalletInfo> walletInfoSource,
|
||||
Box<UnspentCoinsInfo> unspentCoinSource, bool alwaysScan, bool isDirect) {
|
||||
return LitecoinWalletService(walletInfoSource, unspentCoinSource, alwaysScan, isDirect);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -261,6 +261,9 @@ class CWBitcoin extends Bitcoin {
|
|||
@override
|
||||
List<ReceivePageOption> getBitcoinReceivePageOptions() => BitcoinReceivePageOption.all;
|
||||
|
||||
@override
|
||||
List<ReceivePageOption> getLitecoinReceivePageOptions() => BitcoinReceivePageOption.allLitecoin;
|
||||
|
||||
@override
|
||||
BitcoinAddressType getBitcoinAddressType(ReceivePageOption option) {
|
||||
switch (option) {
|
||||
|
@ -272,6 +275,8 @@ class CWBitcoin extends Bitcoin {
|
|||
return SegwitAddresType.p2tr;
|
||||
case BitcoinReceivePageOption.p2wsh:
|
||||
return SegwitAddresType.p2wsh;
|
||||
case BitcoinReceivePageOption.mweb:
|
||||
return SegwitAddresType.mweb;
|
||||
case BitcoinReceivePageOption.p2wpkh:
|
||||
default:
|
||||
return SegwitAddresType.p2wpkh;
|
||||
|
@ -555,6 +560,9 @@ class CWBitcoin extends Bitcoin {
|
|||
return await getBitcoinHeightByDate(date: date);
|
||||
}
|
||||
|
||||
@override
|
||||
int getLitecoinHeightByDate({required DateTime date}) => getLtcHeightByDate(date: date);
|
||||
|
||||
@override
|
||||
Future<void> rescan(Object wallet, {required int height, bool? doSingleScan}) async {
|
||||
final bitcoinWallet = wallet as ElectrumWallet;
|
||||
|
@ -580,6 +588,17 @@ class CWBitcoin extends Bitcoin {
|
|||
}
|
||||
|
||||
@override
|
||||
Future<void> setMwebEnabled(Object wallet, bool enabled) async {
|
||||
final litecoinWallet = wallet as LitecoinWallet;
|
||||
litecoinWallet.setMwebEnabled(enabled);
|
||||
}
|
||||
|
||||
@override
|
||||
bool getMwebEnabled(Object wallet) {
|
||||
final litecoinWallet = wallet as LitecoinWallet;
|
||||
return litecoinWallet.mwebEnabled;
|
||||
}
|
||||
|
||||
List<Output> updateOutputs(PendingTransaction pendingTransaction, List<Output> outputs) {
|
||||
final pendingTx = pendingTransaction as PendingBitcoinTransaction;
|
||||
|
||||
|
@ -588,7 +607,6 @@ class CWBitcoin extends Bitcoin {
|
|||
}
|
||||
|
||||
final updatedOutputs = outputs.map((output) {
|
||||
|
||||
try {
|
||||
final pendingOut = pendingTx!.outputs[outputs.indexOf(output)];
|
||||
final updatedOutput = output;
|
||||
|
@ -609,4 +627,31 @@ class CWBitcoin extends Bitcoin {
|
|||
final tx = txInfo as ElectrumTransactionInfo;
|
||||
return tx.isReceivedSilentPayment;
|
||||
}
|
||||
|
||||
@override
|
||||
bool txIsMweb(TransactionInfo txInfo) {
|
||||
final tx = txInfo as ElectrumTransactionInfo;
|
||||
|
||||
List<String> inputAddresses = tx.inputAddresses ?? [];
|
||||
List<String> outputAddresses = tx.outputAddresses ?? [];
|
||||
bool inputAddressesContainMweb = false;
|
||||
bool outputAddressesContainMweb = false;
|
||||
|
||||
for (var address in inputAddresses) {
|
||||
if (address.toLowerCase().contains('mweb')) {
|
||||
inputAddressesContainMweb = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (var address in outputAddresses) {
|
||||
if (address.toLowerCase().contains('mweb')) {
|
||||
outputAddressesContainMweb = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: this could be improved:
|
||||
return inputAddressesContainMweb || outputAddressesContainMweb;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,10 +12,12 @@ class AddressValidator extends TextValidator {
|
|||
AddressValidator({required CryptoCurrency type})
|
||||
: super(
|
||||
errorMessage: S.current.error_text_address,
|
||||
useAdditionalValidation: type == CryptoCurrency.btc
|
||||
useAdditionalValidation: type == CryptoCurrency.btc || type == CryptoCurrency.ltc
|
||||
? (String txt) => BitcoinAddressUtils.validateAddress(
|
||||
address: txt,
|
||||
network: BitcoinNetwork.mainnet,
|
||||
network: type == CryptoCurrency.btc
|
||||
? BitcoinNetwork.mainnet
|
||||
: LitecoinNetwork.mainnet,
|
||||
)
|
||||
: null,
|
||||
pattern: getPattern(type),
|
||||
|
@ -35,6 +37,8 @@ class AddressValidator extends TextValidator {
|
|||
case CryptoCurrency.btc:
|
||||
pattern =
|
||||
'${P2pkhAddress.regex.pattern}|${P2shAddress.regex.pattern}|${P2wpkhAddress.regex.pattern}|${P2trAddress.regex.pattern}|${P2wshAddress.regex.pattern}|${SilentPaymentAddress.regex.pattern}';
|
||||
case CryptoCurrency.ltc:
|
||||
pattern = '^${P2wpkhAddress.regex.pattern}\$|^${MwebAddress.regex.pattern}\$';
|
||||
case CryptoCurrency.nano:
|
||||
pattern = '[0-9a-zA-Z_]+';
|
||||
case CryptoCurrency.banano:
|
||||
|
@ -106,8 +110,6 @@ class AddressValidator extends TextValidator {
|
|||
'(?!bitcoincash:)[0-9a-zA-Z]*|(?!bitcoincash:)q|p[0-9a-zA-Z]{41}|(?!bitcoincash:)q|p[0-9a-zA-Z]{42}|bitcoincash:q|p[0-9a-zA-Z]{41}|bitcoincash:q|p[0-9a-zA-Z]{42}';
|
||||
case CryptoCurrency.bnb:
|
||||
pattern = '[0-9a-zA-Z]+';
|
||||
case CryptoCurrency.ltc:
|
||||
pattern = '((?!(ltc|LTC)1)[0-9a-zA-Z]*)|(LTC1[A-Z0-9]*)|(ltc1[a-z0-9]*)';
|
||||
case CryptoCurrency.hbar:
|
||||
pattern = '[0-9a-zA-Z.]+';
|
||||
case CryptoCurrency.zaddr:
|
||||
|
@ -157,6 +159,8 @@ class AddressValidator extends TextValidator {
|
|||
return null;
|
||||
case CryptoCurrency.btc:
|
||||
return null;
|
||||
case CryptoCurrency.ltc:
|
||||
return null;
|
||||
case CryptoCurrency.dash:
|
||||
return [34];
|
||||
case CryptoCurrency.eos:
|
||||
|
@ -203,8 +207,6 @@ class AddressValidator extends TextValidator {
|
|||
return [42, 43, 44, 54, 55];
|
||||
case CryptoCurrency.bnb:
|
||||
return [42];
|
||||
case CryptoCurrency.ltc:
|
||||
return [34, 43, 63];
|
||||
case CryptoCurrency.nano:
|
||||
return [64, 65];
|
||||
case CryptoCurrency.banano:
|
||||
|
@ -289,9 +291,10 @@ class AddressValidator extends TextValidator {
|
|||
pattern =
|
||||
'${P2pkhAddress.regex.pattern}|${P2shAddress.regex.pattern}|${P2wpkhAddress.regex.pattern}|${P2trAddress.regex.pattern}|${P2wshAddress.regex.pattern}|${SilentPaymentAddress.regex.pattern}';
|
||||
case CryptoCurrency.ltc:
|
||||
pattern = '(L[a-zA-Z0-9]{26,33})'
|
||||
'|([LM][a-km-zA-HJ-NP-Z1-9]{26,33})'
|
||||
'|(ltc[a-zA-Z0-9]{26,45})';
|
||||
pattern = '([^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]|\$)'
|
||||
'|([^0-9a-zA-Z]|^)ltc[a-zA-Z0-9]{26,45}([^0-9a-zA-Z]|\$)'
|
||||
'|([^0-9a-zA-Z]|^)((ltc|t)mweb1q[ac-hj-np-z02-9]{90,120})([^0-9a-zA-Z]|\$)';
|
||||
case CryptoCurrency.eth:
|
||||
case CryptoCurrency.maticpoly:
|
||||
pattern = '0x[0-9a-zA-Z]+';
|
||||
|
|
|
@ -85,7 +85,8 @@ class WalletLoadingService {
|
|||
authenticatedErrorStreamController.add(corruptedWalletsSeeds);
|
||||
|
||||
return wallet;
|
||||
} catch (_) {
|
||||
} catch (e) {
|
||||
print(e);
|
||||
// save seeds and show corrupted wallets' seeds to the user
|
||||
try {
|
||||
final seeds = await _getCorruptedWalletSeeds(walletInfo.name, walletInfo.type);
|
||||
|
|
33
lib/di.dart
33
lib/di.dart
|
@ -106,6 +106,7 @@ import 'package:cake_wallet/src/screens/settings/desktop_settings/desktop_settin
|
|||
import 'package:cake_wallet/src/screens/settings/display_settings_page.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/domain_lookups_page.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/manage_nodes_page.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/mweb_settings.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/other_settings_page.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/privacy_page.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/security_backup_page.dart';
|
||||
|
@ -156,6 +157,7 @@ import 'package:cake_wallet/view_model/seed_settings_view_model.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';
|
||||
import 'package:cake_wallet/view_model/settings/mweb_settings_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/settings/other_settings_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/settings/privacy_settings_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/settings/security_settings_view_model.dart';
|
||||
|
@ -671,7 +673,8 @@ Future<void> setup({
|
|||
getIt.registerFactory<Modify2FAPage>(
|
||||
() => Modify2FAPage(setup2FAViewModel: getIt.get<Setup2FAViewModel>()));
|
||||
|
||||
getIt.registerFactory<DesktopSettingsPage>(() => DesktopSettingsPage(getIt.get<DashboardViewModel>()));
|
||||
getIt.registerFactory<DesktopSettingsPage>(
|
||||
() => DesktopSettingsPage(getIt.get<DashboardViewModel>()));
|
||||
|
||||
getIt.registerFactoryParam<ReceiveOptionViewModel, ReceivePageOption?, void>(
|
||||
(pageOption, _) => ReceiveOptionViewModel(getIt.get<AppStore>().wallet!, pageOption));
|
||||
|
@ -803,7 +806,9 @@ Future<void> setup({
|
|||
|
||||
getIt.registerFactory<MoneroAccountListViewModel>(() {
|
||||
final wallet = getIt.get<AppStore>().wallet!;
|
||||
if (wallet.type == WalletType.monero || wallet.type == WalletType.wownero || wallet.type == WalletType.haven) {
|
||||
if (wallet.type == WalletType.monero ||
|
||||
wallet.type == WalletType.wownero ||
|
||||
wallet.type == WalletType.haven) {
|
||||
return MoneroAccountListViewModel(wallet);
|
||||
}
|
||||
throw Exception(
|
||||
|
@ -863,6 +868,9 @@ Future<void> setup({
|
|||
getIt.registerFactory(() =>
|
||||
SilentPaymentsSettingsViewModel(getIt.get<SettingsStore>(), getIt.get<AppStore>().wallet!));
|
||||
|
||||
getIt.registerFactory(
|
||||
() => MwebSettingsViewModel(getIt.get<SettingsStore>(), getIt.get<AppStore>().wallet!));
|
||||
|
||||
getIt.registerFactory(() {
|
||||
return PrivacySettingsViewModel(getIt.get<SettingsStore>(), getIt.get<AppStore>().wallet!);
|
||||
});
|
||||
|
@ -929,6 +937,8 @@ Future<void> setup({
|
|||
getIt.registerFactory(
|
||||
() => SilentPaymentsSettingsPage(getIt.get<SilentPaymentsSettingsViewModel>()));
|
||||
|
||||
getIt.registerFactory(() => MwebSettingsPage(getIt.get<MwebSettingsViewModel>()));
|
||||
|
||||
getIt.registerFactory(() => OtherSettingsPage(getIt.get<OtherSettingsViewModel>()));
|
||||
|
||||
getIt.registerFactory(() => NanoChangeRepPage(
|
||||
|
@ -1023,8 +1033,12 @@ Future<void> setup({
|
|||
SettingsStoreBase.walletPasswordDirectInput,
|
||||
);
|
||||
case WalletType.litecoin:
|
||||
return bitcoin!.createLitecoinWalletService(_walletInfoSource, _unspentCoinsInfoSource,
|
||||
SettingsStoreBase.walletPasswordDirectInput);
|
||||
return bitcoin!.createLitecoinWalletService(
|
||||
_walletInfoSource,
|
||||
_unspentCoinsInfoSource,
|
||||
getIt.get<SettingsStore>().mwebAlwaysScan,
|
||||
SettingsStoreBase.walletPasswordDirectInput,
|
||||
);
|
||||
case WalletType.ethereum:
|
||||
return ethereum!.createEthereumWalletService(
|
||||
_walletInfoSource, SettingsStoreBase.walletPasswordDirectInput);
|
||||
|
@ -1236,7 +1250,8 @@ Future<void> setup({
|
|||
getIt.registerFactory<CakePayService>(
|
||||
() => CakePayService(getIt.get<SecureStorage>(), getIt.get<CakePayApi>()));
|
||||
|
||||
getIt.registerFactory(() => CakePayCardsListViewModel(cakePayService: getIt.get<CakePayService>()));
|
||||
getIt.registerFactory(
|
||||
() => CakePayCardsListViewModel(cakePayService: getIt.get<CakePayService>()));
|
||||
|
||||
getIt.registerFactory(() => CakePayAuthViewModel(cakePayService: getIt.get<CakePayService>()));
|
||||
|
||||
|
@ -1268,12 +1283,12 @@ Future<void> setup({
|
|||
getIt.registerFactoryParam<CakePayBuyCardPage, List<dynamic>, void>((List<dynamic> args, _) {
|
||||
final vendor = args.first as CakePayVendor;
|
||||
|
||||
return CakePayBuyCardPage(getIt.get<CakePayBuyCardViewModel>(param1: vendor),
|
||||
getIt.get<CakePayService>());
|
||||
return CakePayBuyCardPage(
|
||||
getIt.get<CakePayBuyCardViewModel>(param1: vendor), getIt.get<CakePayService>());
|
||||
});
|
||||
|
||||
getIt.registerFactoryParam<CakePayBuyCardDetailPage, List<dynamic>, void>(
|
||||
(List<dynamic> args, _) {
|
||||
getIt
|
||||
.registerFactoryParam<CakePayBuyCardDetailPage, List<dynamic>, void>((List<dynamic> args, _) {
|
||||
final paymentCredential = args.first as PaymentCredential;
|
||||
final card = args[1] as CakePayCard;
|
||||
return CakePayBuyCardDetailPage(
|
||||
|
|
|
@ -48,6 +48,10 @@ class PreferencesKey {
|
|||
static const customBitcoinFeeRate = 'custom_electrum_fee_rate';
|
||||
static const silentPaymentsCardDisplay = 'silentPaymentsCardDisplay';
|
||||
static const silentPaymentsAlwaysScan = 'silentPaymentsAlwaysScan';
|
||||
static const mwebCardDisplay = 'mwebCardDisplay';
|
||||
static const mwebEnabled = 'mwebEnabled';
|
||||
static const hasEnabledMwebBefore = 'hasEnabledMwebBefore';
|
||||
static const mwebAlwaysScan = 'mwebAlwaysScan';
|
||||
static const shouldShowReceiveWarning = 'should_show_receive_warning';
|
||||
static const shouldShowYatPopup = 'should_show_yat_popup';
|
||||
static const shouldShowRepWarning = 'should_show_rep_warning';
|
||||
|
|
|
@ -30,6 +30,7 @@ import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
|||
import 'package:cw_core/address_info.dart';
|
||||
import 'package:cw_core/cake_hive.dart';
|
||||
import 'package:cw_core/hive_type_ids.dart';
|
||||
import 'package:cw_core/mweb_utxo.dart';
|
||||
import 'package:cw_core/node.dart';
|
||||
import 'package:cw_core/unspent_coins_info.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
|
@ -152,6 +153,10 @@ Future<void> initializeAppConfigs() async {
|
|||
CakeHive.registerAdapter(AnonpayInvoiceInfoAdapter());
|
||||
}
|
||||
|
||||
if (!CakeHive.isAdapterRegistered(MwebUtxo.typeId)) {
|
||||
CakeHive.registerAdapter(MwebUtxoAdapter());
|
||||
}
|
||||
|
||||
final secureStorage = secureStorageShared;
|
||||
final transactionDescriptionsBoxKey =
|
||||
await getEncryptionKey(secureStorage: secureStorage, forKey: TransactionDescription.boxKey);
|
||||
|
@ -233,7 +238,6 @@ Future<void> initialSetup(
|
|||
secureStorage: secureStorage,
|
||||
);
|
||||
await bootstrap(navigatorKey);
|
||||
monero?.onStartup();
|
||||
}
|
||||
|
||||
class App extends StatefulWidget {
|
||||
|
|
|
@ -72,6 +72,7 @@ import 'package:cake_wallet/src/screens/settings/desktop_settings/desktop_settin
|
|||
import 'package:cake_wallet/src/screens/settings/display_settings_page.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/domain_lookups_page.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/manage_nodes_page.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/mweb_settings.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/other_settings_page.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/privacy_page.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/security_backup_page.dart';
|
||||
|
@ -450,6 +451,10 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
|||
return CupertinoPageRoute<void>(
|
||||
fullscreenDialog: true, builder: (_) => getIt.get<SilentPaymentsSettingsPage>());
|
||||
|
||||
case Routes.mwebSettings:
|
||||
return CupertinoPageRoute<void>(
|
||||
fullscreenDialog: true, builder: (_) => getIt.get<MwebSettingsPage>());
|
||||
|
||||
case Routes.connectionSync:
|
||||
return CupertinoPageRoute<void>(
|
||||
fullscreenDialog: true, builder: (_) => getIt.get<ConnectionSyncPage>());
|
||||
|
|
|
@ -73,6 +73,7 @@ class Routes {
|
|||
static const cakePayAccountPage = '/cake_pay_account_page';
|
||||
static const webViewPage = '/web_view_page';
|
||||
static const silentPaymentsSettings = '/silent_payments_settings';
|
||||
static const mwebSettings = '/mweb_settings';
|
||||
static const connectionSync = '/connection_sync_page';
|
||||
static const securityBackupPage = '/security_and_backup_page';
|
||||
static const privacyPage = '/privacy_page';
|
||||
|
|
|
@ -225,7 +225,8 @@ class AddressPage extends BasePage {
|
|||
}
|
||||
break;
|
||||
default:
|
||||
if (addressListViewModel.type == WalletType.bitcoin) {
|
||||
if (addressListViewModel.type == WalletType.bitcoin ||
|
||||
addressListViewModel.type == WalletType.litecoin) {
|
||||
addressListViewModel.setAddressType(bitcoin!.getBitcoinAddressType(option));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import 'package:cake_wallet/reactions/wallet_connect.dart';
|
|||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/pages/nft_listing_page.dart';
|
||||
import 'package:cake_wallet/src/screens/dashboard/widgets/home_screen_account_widget.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
|
||||
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
|
||||
import 'package:cake_wallet/src/screens/exchange_trade/information_page.dart';
|
||||
|
@ -25,6 +26,7 @@ import 'package:cw_core/crypto_currency.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class BalancePage extends StatelessWidget {
|
||||
|
@ -149,8 +151,7 @@ class CryptoBalanceWidget extends StatelessWidget {
|
|||
title: "Invalid wownero bindings",
|
||||
subTitle: dashboardViewModel.getWowneroError.toString(),
|
||||
onTap: () {},
|
||||
)
|
||||
);
|
||||
));
|
||||
}
|
||||
return Container();
|
||||
},
|
||||
|
@ -271,6 +272,18 @@ class CryptoBalanceWidget extends StatelessWidget {
|
|||
currency: balance.asset,
|
||||
hasAdditionalBalance:
|
||||
dashboardViewModel.balanceViewModel.hasAdditionalBalance,
|
||||
hasSecondAdditionalBalance:
|
||||
dashboardViewModel.balanceViewModel.hasSecondAdditionalBalance,
|
||||
hasSecondAvailableBalance:
|
||||
dashboardViewModel.balanceViewModel.hasSecondAvailableBalance,
|
||||
secondAdditionalBalance: balance.secondAdditionalBalance,
|
||||
secondAdditionalFiatBalance: balance.fiatSecondAdditionalBalance,
|
||||
secondAvailableBalance: balance.secondAvailableBalance,
|
||||
secondAvailableFiatBalance: balance.fiatSecondAvailableBalance,
|
||||
secondAdditionalBalanceLabel:
|
||||
'${dashboardViewModel.balanceViewModel.secondAdditionalBalanceLabel}',
|
||||
secondAvailableBalanceLabel:
|
||||
'${dashboardViewModel.balanceViewModel.secondAvailableBalanceLabel}',
|
||||
isTestnet: dashboardViewModel.isTestnet,
|
||||
);
|
||||
});
|
||||
|
@ -288,12 +301,11 @@ class CryptoBalanceWidget extends StatelessWidget {
|
|||
child: DashBoardRoundedCardWidget(
|
||||
customBorder: 30,
|
||||
title: "This wallet has encountered an issue",
|
||||
subTitle: "Here are the things that you should note:\n - "
|
||||
+dashboardViewModel.isMoneroWalletBrokenReasons.join("\n - ")
|
||||
+"\n\nPlease restart your wallet and if it doesn't help contact our support.",
|
||||
subTitle: "Here are the things that you should note:\n - " +
|
||||
dashboardViewModel.isMoneroWalletBrokenReasons.join("\n - ") +
|
||||
"\n\nPlease restart your wallet and if it doesn't help contact our support.",
|
||||
onTap: () {},
|
||||
)
|
||||
)
|
||||
))
|
||||
],
|
||||
if (dashboardViewModel.showSilentPaymentsCard) ...[
|
||||
SizedBox(height: 10),
|
||||
|
@ -360,7 +372,73 @@ class CryptoBalanceWidget extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
),
|
||||
]
|
||||
],
|
||||
if (dashboardViewModel.showMwebCard) ...[
|
||||
SizedBox(height: 10),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 0, 16, 8),
|
||||
child: DashBoardRoundedCardWidget(
|
||||
customBorder: 30,
|
||||
title: S.current.litecoin_mweb,
|
||||
subTitle: S.current.litecoin_enable_mweb_sync,
|
||||
hint: Column(
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () => launchUrl(
|
||||
Uri.parse(
|
||||
"https://guides.cakewallet.com/docs/cryptos/litecoin/#mweb"),
|
||||
mode: LaunchMode.externalApplication,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
S.current.litecoin_what_is_mweb,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context)
|
||||
.extension<BalancePageTheme>()!
|
||||
.labelTextColor,
|
||||
height: 1,
|
||||
),
|
||||
softWrap: true,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: Icon(Icons.help_outline,
|
||||
size: 16,
|
||||
color: Theme.of(context)
|
||||
.extension<BalancePageTheme>()!
|
||||
.labelTextColor),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
Observer(
|
||||
builder: (_) => StandardSwitch(
|
||||
value: dashboardViewModel.mwebScanningActive,
|
||||
onTaped: () => _toggleMweb(context),
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
onTap: () => _toggleMweb(context),
|
||||
icon: ImageIcon(
|
||||
AssetImage('assets/images/mweb_logo.png'),
|
||||
color:
|
||||
Theme.of(context).extension<DashboardPageTheme>()!.pageTitleTextColor,
|
||||
size: 50,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
}),
|
||||
|
@ -400,6 +478,22 @@ class CryptoBalanceWidget extends StatelessWidget {
|
|||
|
||||
return dashboardViewModel.setSilentPaymentsScanning(newValue);
|
||||
}
|
||||
|
||||
Future<void> _toggleMweb(BuildContext context) async {
|
||||
if (!dashboardViewModel.hasEnabledMwebBefore) {
|
||||
await showPopUp<void>(
|
||||
context: context,
|
||||
builder: (BuildContext context) => AlertWithOneAction(
|
||||
alertTitle: S.of(context).warning,
|
||||
alertContent: S.current.litecoin_mweb_warning,
|
||||
buttonText: S.of(context).ok,
|
||||
buttonAction: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
));
|
||||
}
|
||||
dashboardViewModel.setMwebScanningActive(!dashboardViewModel.mwebScanningActive);
|
||||
}
|
||||
}
|
||||
|
||||
class BalanceRowWidget extends StatelessWidget {
|
||||
|
@ -410,10 +504,18 @@ class BalanceRowWidget extends StatelessWidget {
|
|||
required this.additionalBalanceLabel,
|
||||
required this.additionalBalance,
|
||||
required this.additionalFiatBalance,
|
||||
required this.secondAvailableBalanceLabel,
|
||||
required this.secondAvailableBalance,
|
||||
required this.secondAvailableFiatBalance,
|
||||
required this.secondAdditionalBalanceLabel,
|
||||
required this.secondAdditionalBalance,
|
||||
required this.secondAdditionalFiatBalance,
|
||||
required this.frozenBalance,
|
||||
required this.frozenFiatBalance,
|
||||
required this.currency,
|
||||
required this.hasAdditionalBalance,
|
||||
required this.hasSecondAvailableBalance,
|
||||
required this.hasSecondAdditionalBalance,
|
||||
required this.isTestnet,
|
||||
super.key,
|
||||
});
|
||||
|
@ -424,10 +526,18 @@ class BalanceRowWidget extends StatelessWidget {
|
|||
final String additionalBalanceLabel;
|
||||
final String additionalBalance;
|
||||
final String additionalFiatBalance;
|
||||
final String secondAvailableBalanceLabel;
|
||||
final String secondAvailableBalance;
|
||||
final String secondAvailableFiatBalance;
|
||||
final String secondAdditionalBalanceLabel;
|
||||
final String secondAdditionalBalance;
|
||||
final String secondAdditionalFiatBalance;
|
||||
final String frozenBalance;
|
||||
final String frozenFiatBalance;
|
||||
final CryptoCurrency currency;
|
||||
final bool hasAdditionalBalance;
|
||||
final bool hasSecondAvailableBalance;
|
||||
final bool hasSecondAdditionalBalance;
|
||||
final bool isTestnet;
|
||||
|
||||
// void _showBalanceDescription(BuildContext context) {
|
||||
|
@ -675,6 +785,94 @@ class BalanceRowWidget extends StatelessWidget {
|
|||
),
|
||||
],
|
||||
),
|
||||
if (hasSecondAvailableBalance)
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(height: 24),
|
||||
Text(
|
||||
'${secondAvailableBalanceLabel}',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context).extension<BalancePageTheme>()!.labelTextColor,
|
||||
height: 1,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
AutoSizeText(
|
||||
secondAvailableBalance,
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context).extension<BalancePageTheme>()!.assetTitleColor,
|
||||
height: 1,
|
||||
),
|
||||
maxLines: 1,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
SizedBox(height: 4),
|
||||
if (!isTestnet)
|
||||
Text(
|
||||
'${secondAvailableFiatBalance}',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context).extension<BalancePageTheme>()!.textColor,
|
||||
height: 1,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (hasSecondAdditionalBalance)
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(height: 24),
|
||||
Text(
|
||||
'${secondAdditionalBalanceLabel}',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context).extension<BalancePageTheme>()!.labelTextColor,
|
||||
height: 1,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
AutoSizeText(
|
||||
secondAdditionalBalance,
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context).extension<BalancePageTheme>()!.assetTitleColor,
|
||||
height: 1,
|
||||
),
|
||||
maxLines: 1,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
SizedBox(height: 4),
|
||||
if (!isTestnet)
|
||||
Text(
|
||||
'${secondAdditionalFiatBalance}',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context).extension<BalancePageTheme>()!.textColor,
|
||||
height: 1,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -86,6 +86,18 @@ class TransactionsPage extends StatelessWidget {
|
|||
final transaction = item.transaction;
|
||||
final transactionType = dashboardViewModel.getTransactionType(transaction);
|
||||
|
||||
List<String> tags = [];
|
||||
if (dashboardViewModel.type == WalletType.bitcoin) {
|
||||
if (bitcoin!.txIsReceivedSilentPayment(transaction)) {
|
||||
tags.add(S.of(context).silent_payment);
|
||||
}
|
||||
}
|
||||
if (dashboardViewModel.type == WalletType.litecoin) {
|
||||
if (bitcoin!.txIsMweb(transaction)) {
|
||||
tags.add("MWEB");
|
||||
}
|
||||
}
|
||||
|
||||
return Observer(
|
||||
builder: (_) => TransactionRow(
|
||||
onTap: () => Navigator.of(context)
|
||||
|
@ -100,9 +112,7 @@ class TransactionsPage extends StatelessWidget {
|
|||
isPending: transaction.isPending,
|
||||
title:
|
||||
item.formattedTitle + item.formattedStatus + transactionType,
|
||||
isReceivedSilentPayment:
|
||||
dashboardViewModel.type == WalletType.bitcoin &&
|
||||
bitcoin!.txIsReceivedSilentPayment(transaction),
|
||||
tags: tags,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -191,6 +191,11 @@ class MenuWidgetState extends State<MenuWidget> {
|
|||
|
||||
final item = items[index];
|
||||
|
||||
if (!widget.dashboardViewModel.hasMweb &&
|
||||
item.name(context) == S.current.litecoin_mweb_settings) {
|
||||
return const SizedBox();
|
||||
}
|
||||
|
||||
final isLastTile = index == itemCount - 1;
|
||||
|
||||
return SettingActionButton(
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cw_core/transaction_direction.dart';
|
||||
|
@ -12,7 +11,7 @@ class TransactionRow extends StatelessWidget {
|
|||
required this.formattedAmount,
|
||||
required this.formattedFiatAmount,
|
||||
required this.isPending,
|
||||
required this.isReceivedSilentPayment,
|
||||
required this.tags,
|
||||
required this.title,
|
||||
required this.onTap,
|
||||
});
|
||||
|
@ -23,8 +22,8 @@ class TransactionRow extends StatelessWidget {
|
|||
final String formattedAmount;
|
||||
final String formattedFiatAmount;
|
||||
final bool isPending;
|
||||
final bool isReceivedSilentPayment;
|
||||
final String title;
|
||||
final List<String> tags;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -61,7 +60,7 @@ class TransactionRow extends StatelessWidget {
|
|||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).extension<DashboardPageTheme>()!.textColor,
|
||||
)),
|
||||
if (isReceivedSilentPayment) TxTag(tag: S.of(context).silent_payment),
|
||||
...tags.map((tag) => Row(children: [SizedBox(width: 8), TxTag(tag: tag)])),
|
||||
],
|
||||
),
|
||||
Text(formattedAmount,
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:cake_wallet/di.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
|
|
|
@ -33,7 +33,9 @@ class RescanPage extends BasePage {
|
|||
key: _blockchainHeightWidgetKey,
|
||||
onHeightOrDateEntered: (value) => _rescanViewModel.isButtonEnabled = value,
|
||||
isSilentPaymentsScan: _rescanViewModel.isSilentPaymentsScan,
|
||||
isMwebScan: _rescanViewModel.isMwebScan,
|
||||
doSingleScan: _rescanViewModel.doSingleScan,
|
||||
hasDatePicker: !_rescanViewModel.isMwebScan,// disable date picker for mweb for now
|
||||
toggleSingleScan: () =>
|
||||
_rescanViewModel.doSingleScan = !_rescanViewModel.doSingleScan,
|
||||
walletType: _rescanViewModel.wallet.type,
|
||||
|
|
|
@ -5,6 +5,7 @@ import 'package:cake_wallet/core/totp_request_details.dart';
|
|||
import 'package:cake_wallet/utils/device_info.dart';
|
||||
import 'package:cake_wallet/view_model/link_view_model.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/auth/auth_page.dart';
|
||||
|
@ -134,6 +135,10 @@ class RootState extends State<Root> with WidgetsBindingObserver {
|
|||
setState(() => _setInactive(true));
|
||||
}
|
||||
|
||||
if (widget.appStore.wallet?.type == WalletType.litecoin) {
|
||||
widget.appStore.wallet?.stopSync();
|
||||
}
|
||||
|
||||
break;
|
||||
case AppLifecycleState.resumed:
|
||||
widget.authService.requireAuth().then((value) {
|
||||
|
@ -143,6 +148,9 @@ class RootState extends State<Root> with WidgetsBindingObserver {
|
|||
});
|
||||
}
|
||||
});
|
||||
if (widget.appStore.wallet?.type == WalletType.litecoin) {
|
||||
widget.appStore.wallet?.startSync();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
|
|
@ -60,6 +60,11 @@ class _DesktopSettingsPageState extends State<DesktopSettingsPage> {
|
|||
return Container();
|
||||
}
|
||||
|
||||
if (!widget.dashboardViewModel.hasMweb &&
|
||||
item.name(context) == S.of(context).litecoin_mweb_settings) {
|
||||
return Container();
|
||||
}
|
||||
|
||||
final isLastTile = index == itemCount - 1;
|
||||
return SettingActionButton(
|
||||
isLastTile: isLastTile,
|
||||
|
|
51
lib/src/screens/settings/mweb_settings.dart
Normal file
51
lib/src/screens/settings/mweb_settings.dart
Normal file
|
@ -0,0 +1,51 @@
|
|||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/widgets/settings_cell_with_arrow.dart';
|
||||
import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart';
|
||||
import 'package:cake_wallet/view_model/settings/mweb_settings_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/settings/silent_payments_settings_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
|
||||
class MwebSettingsPage extends BasePage {
|
||||
MwebSettingsPage(this._mwebSettingsViewModel);
|
||||
|
||||
@override
|
||||
String get title => S.current.litecoin_mweb_settings;
|
||||
|
||||
final MwebSettingsViewModel _mwebSettingsViewModel;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
return SingleChildScrollView(
|
||||
child: Observer(builder: (_) {
|
||||
return Container(
|
||||
padding: EdgeInsets.only(top: 10),
|
||||
child: Column(
|
||||
children: [
|
||||
SettingsSwitcherCell(
|
||||
title: S.current.litecoin_mweb_display_card,
|
||||
value: _mwebSettingsViewModel.mwebCardDisplay,
|
||||
onValueChange: (_, bool value) {
|
||||
_mwebSettingsViewModel.setMwebCardDisplay(value);
|
||||
},
|
||||
),
|
||||
SettingsSwitcherCell(
|
||||
title: S.current.litecoin_mweb_always_scan,
|
||||
value: _mwebSettingsViewModel.mwebAlwaysScan,
|
||||
onValueChange: (_, bool value) {
|
||||
_mwebSettingsViewModel.setMwebAlwaysScan(value);
|
||||
},
|
||||
),
|
||||
SettingsCellWithArrow(
|
||||
title: S.current.litecoin_mweb_scanning,
|
||||
handler: (BuildContext context) => Navigator.of(context).pushNamed(Routes.rescan),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -103,6 +103,9 @@ class UnspentCoinsListItem extends StatelessWidget {
|
|||
),
|
||||
maxLines: 1,
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
if (isChange)
|
||||
Container(
|
||||
height: 17,
|
||||
|
@ -120,6 +123,24 @@ class UnspentCoinsListItem extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
),
|
||||
if (address.toLowerCase().contains("mweb"))
|
||||
Container(
|
||||
height: 17,
|
||||
padding: EdgeInsets.only(left: 6, right: 6),
|
||||
margin: EdgeInsets.only(left: 6),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.all(Radius.circular(8.5)),
|
||||
color: Colors.white),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
"MWEB",
|
||||
style: TextStyle(
|
||||
color: itemColor,
|
||||
fontSize: 7,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (isSilentPayment)
|
||||
Container(
|
||||
height: 17,
|
||||
|
@ -139,6 +160,8 @@ class UnspentCoinsListItem extends StatelessWidget {
|
|||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
])),
|
||||
],
|
||||
|
|
|
@ -18,6 +18,7 @@ class BlockchainHeightWidget extends StatefulWidget {
|
|||
this.onHeightOrDateEntered,
|
||||
this.hasDatePicker = true,
|
||||
this.isSilentPaymentsScan = false,
|
||||
this.isMwebScan = false,
|
||||
this.toggleSingleScan,
|
||||
this.doSingleScan = false,
|
||||
this.bitcoinMempoolAPIEnabled,
|
||||
|
@ -29,6 +30,7 @@ class BlockchainHeightWidget extends StatefulWidget {
|
|||
final FocusNode? focusNode;
|
||||
final bool hasDatePicker;
|
||||
final bool isSilentPaymentsScan;
|
||||
final bool isMwebScan;
|
||||
final bool doSingleScan;
|
||||
final Future<bool>? bitcoinMempoolAPIEnabled;
|
||||
final Function()? toggleSingleScan;
|
||||
|
@ -170,7 +172,9 @@ class BlockchainHeightState extends State<BlockchainHeightWidget> {
|
|||
|
||||
if (date != null) {
|
||||
int height;
|
||||
if (widget.isSilentPaymentsScan) {
|
||||
if (widget.isMwebScan) {
|
||||
height = bitcoin!.getLitecoinHeightByDate(date: date);
|
||||
} else if (widget.isSilentPaymentsScan) {
|
||||
height = await bitcoin!.getHeightByDate(
|
||||
date: date,
|
||||
bitcoinMempoolAPIEnabled: await widget.bitcoinMempoolAPIEnabled,
|
||||
|
|
|
@ -23,8 +23,8 @@ class DashBoardRoundedCardWidget extends StatelessWidget {
|
|||
final String subTitle;
|
||||
final Widget? hint;
|
||||
final SvgPicture? svgPicture;
|
||||
final Widget? icon;
|
||||
final Image? image;
|
||||
final Icon? icon;
|
||||
final double? customBorder;
|
||||
|
||||
@override
|
||||
|
|
|
@ -18,6 +18,7 @@ class SettingActions {
|
|||
walletSettingAction,
|
||||
addressBookSettingAction,
|
||||
silentPaymentsSettingAction,
|
||||
litecoinMwebSettingAction,
|
||||
securityBackupSettingAction,
|
||||
privacySettingAction,
|
||||
displaySettingAction,
|
||||
|
@ -30,6 +31,7 @@ class SettingActions {
|
|||
walletSettingAction,
|
||||
addressBookSettingAction,
|
||||
silentPaymentsSettingAction,
|
||||
litecoinMwebSettingAction,
|
||||
securityBackupSettingAction,
|
||||
privacySettingAction,
|
||||
displaySettingAction,
|
||||
|
@ -46,6 +48,15 @@ class SettingActions {
|
|||
},
|
||||
);
|
||||
|
||||
static SettingActions litecoinMwebSettingAction = SettingActions._(
|
||||
name: (context) => S.current.litecoin_mweb_settings,
|
||||
image: 'assets/images/bitcoin_menu.png',
|
||||
onTap: (BuildContext context) {
|
||||
Navigator.pop(context);
|
||||
Navigator.of(context).pushNamed(Routes.mwebSettings);
|
||||
},
|
||||
);
|
||||
|
||||
static SettingActions connectionSettingAction = SettingActions._(
|
||||
name: (context) => S.of(context).connection_sync,
|
||||
image: 'assets/images/nodes_menu.png',
|
||||
|
|
|
@ -114,6 +114,10 @@ abstract class SettingsStoreBase with Store {
|
|||
required this.customBitcoinFeeRate,
|
||||
required this.silentPaymentsCardDisplay,
|
||||
required this.silentPaymentsAlwaysScan,
|
||||
required this.mwebAlwaysScan,
|
||||
required this.mwebCardDisplay,
|
||||
required this.mwebEnabled,
|
||||
required this.hasEnabledMwebBefore,
|
||||
TransactionPriority? initialBitcoinTransactionPriority,
|
||||
TransactionPriority? initialMoneroTransactionPriority,
|
||||
TransactionPriority? initialWowneroTransactionPriority,
|
||||
|
@ -555,6 +559,24 @@ abstract class SettingsStoreBase with Store {
|
|||
(bool silentPaymentsAlwaysScan) => _sharedPreferences.setBool(
|
||||
PreferencesKey.silentPaymentsAlwaysScan, silentPaymentsAlwaysScan));
|
||||
|
||||
reaction(
|
||||
(_) => mwebAlwaysScan,
|
||||
(bool mwebAlwaysScan) =>
|
||||
_sharedPreferences.setBool(PreferencesKey.mwebAlwaysScan, mwebAlwaysScan));
|
||||
|
||||
reaction(
|
||||
(_) => mwebCardDisplay,
|
||||
(bool mwebCardDisplay) =>
|
||||
_sharedPreferences.setBool(PreferencesKey.mwebCardDisplay, mwebCardDisplay));
|
||||
|
||||
reaction((_) => mwebEnabled,
|
||||
(bool mwebEnabled) => _sharedPreferences.setBool(PreferencesKey.mwebEnabled, mwebEnabled));
|
||||
|
||||
reaction(
|
||||
(_) => hasEnabledMwebBefore,
|
||||
(bool hasEnabledMwebBefore) =>
|
||||
_sharedPreferences.setBool(PreferencesKey.hasEnabledMwebBefore, hasEnabledMwebBefore));
|
||||
|
||||
this.nodes.observe((change) {
|
||||
if (change.newValue != null && change.key != null) {
|
||||
_saveCurrentNode(change.newValue!, change.key!);
|
||||
|
@ -768,6 +790,18 @@ abstract class SettingsStoreBase with Store {
|
|||
@observable
|
||||
bool silentPaymentsAlwaysScan;
|
||||
|
||||
@observable
|
||||
bool mwebAlwaysScan;
|
||||
|
||||
@observable
|
||||
bool mwebCardDisplay;
|
||||
|
||||
@observable
|
||||
bool mwebEnabled;
|
||||
|
||||
@observable
|
||||
bool hasEnabledMwebBefore;
|
||||
|
||||
final SecureStorage _secureStorage;
|
||||
final SharedPreferences _sharedPreferences;
|
||||
final BackgroundTasks _backgroundTasks;
|
||||
|
@ -925,6 +959,11 @@ abstract class SettingsStoreBase with Store {
|
|||
sharedPreferences.getBool(PreferencesKey.silentPaymentsCardDisplay) ?? true;
|
||||
final silentPaymentsAlwaysScan =
|
||||
sharedPreferences.getBool(PreferencesKey.silentPaymentsAlwaysScan) ?? false;
|
||||
final mwebAlwaysScan = sharedPreferences.getBool(PreferencesKey.mwebAlwaysScan) ?? false;
|
||||
final mwebCardDisplay = sharedPreferences.getBool(PreferencesKey.mwebCardDisplay) ?? true;
|
||||
final mwebEnabled = sharedPreferences.getBool(PreferencesKey.mwebEnabled) ?? false;
|
||||
final hasEnabledMwebBefore =
|
||||
sharedPreferences.getBool(PreferencesKey.hasEnabledMwebBefore) ?? false;
|
||||
|
||||
// If no value
|
||||
if (pinLength == null || pinLength == 0) {
|
||||
|
@ -1191,6 +1230,10 @@ abstract class SettingsStoreBase with Store {
|
|||
customBitcoinFeeRate: customBitcoinFeeRate,
|
||||
silentPaymentsCardDisplay: silentPaymentsCardDisplay,
|
||||
silentPaymentsAlwaysScan: silentPaymentsAlwaysScan,
|
||||
mwebAlwaysScan: mwebAlwaysScan,
|
||||
mwebCardDisplay: mwebCardDisplay,
|
||||
mwebEnabled: mwebEnabled,
|
||||
hasEnabledMwebBefore: hasEnabledMwebBefore,
|
||||
initialMoneroTransactionPriority: moneroTransactionPriority,
|
||||
initialWowneroTransactionPriority: wowneroTransactionPriority,
|
||||
initialBitcoinTransactionPriority: bitcoinTransactionPriority,
|
||||
|
@ -1353,6 +1396,10 @@ abstract class SettingsStoreBase with Store {
|
|||
sharedPreferences.getBool(PreferencesKey.silentPaymentsCardDisplay) ?? true;
|
||||
silentPaymentsAlwaysScan =
|
||||
sharedPreferences.getBool(PreferencesKey.silentPaymentsAlwaysScan) ?? false;
|
||||
mwebAlwaysScan = sharedPreferences.getBool(PreferencesKey.mwebAlwaysScan) ?? false;
|
||||
mwebCardDisplay = sharedPreferences.getBool(PreferencesKey.mwebCardDisplay) ?? true;
|
||||
mwebEnabled = sharedPreferences.getBool(PreferencesKey.mwebEnabled) ?? false;
|
||||
hasEnabledMwebBefore = sharedPreferences.getBool(PreferencesKey.hasEnabledMwebBefore) ?? false;
|
||||
final nodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
|
||||
final bitcoinElectrumServerId =
|
||||
sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
|
||||
|
|
|
@ -83,7 +83,7 @@ class ExceptionHandler {
|
|||
}
|
||||
|
||||
static void onError(FlutterErrorDetails errorDetails) async {
|
||||
if (kDebugMode) {
|
||||
if (kDebugMode || kProfileMode) {
|
||||
FlutterError.presentError(errorDetails);
|
||||
debugPrint(errorDetails.toString());
|
||||
return;
|
||||
|
|
|
@ -21,10 +21,14 @@ class BalanceRecord {
|
|||
const BalanceRecord(
|
||||
{required this.availableBalance,
|
||||
required this.additionalBalance,
|
||||
required this.secondAvailableBalance,
|
||||
required this.secondAdditionalBalance,
|
||||
required this.frozenBalance,
|
||||
required this.fiatAvailableBalance,
|
||||
required this.fiatAdditionalBalance,
|
||||
required this.fiatFrozenBalance,
|
||||
required this.fiatSecondAvailableBalance,
|
||||
required this.fiatSecondAdditionalBalance,
|
||||
required this.asset,
|
||||
required this.formattedAssetTitle});
|
||||
final String fiatAdditionalBalance;
|
||||
|
@ -33,6 +37,10 @@ class BalanceRecord {
|
|||
final String additionalBalance;
|
||||
final String availableBalance;
|
||||
final String frozenBalance;
|
||||
final String secondAvailableBalance;
|
||||
final String secondAdditionalBalance;
|
||||
final String fiatSecondAdditionalBalance;
|
||||
final String fiatSecondAvailableBalance;
|
||||
final CryptoCurrency asset;
|
||||
final String formattedAssetTitle;
|
||||
}
|
||||
|
@ -158,6 +166,26 @@ abstract class BalanceViewModelBase with Store {
|
|||
}
|
||||
}
|
||||
|
||||
@computed
|
||||
String get secondAvailableBalanceLabel {
|
||||
switch (wallet.type) {
|
||||
case WalletType.litecoin:
|
||||
return S.current.mweb_confirmed;
|
||||
default:
|
||||
return S.current.confirmed;
|
||||
}
|
||||
}
|
||||
|
||||
@computed
|
||||
String get secondAdditionalBalanceLabel {
|
||||
switch (wallet.type) {
|
||||
case WalletType.litecoin:
|
||||
return S.current.mweb_unconfirmed;
|
||||
default:
|
||||
return S.current.unconfirmed;
|
||||
}
|
||||
}
|
||||
|
||||
@computed
|
||||
bool get hasMultiBalance => appStore.wallet!.type == WalletType.haven;
|
||||
|
||||
|
@ -243,9 +271,13 @@ abstract class BalanceViewModelBase with Store {
|
|||
availableBalance: '---',
|
||||
additionalBalance: '---',
|
||||
frozenBalance: '---',
|
||||
secondAvailableBalance: '---',
|
||||
secondAdditionalBalance: '---',
|
||||
fiatAdditionalBalance: isFiatDisabled ? '' : '---',
|
||||
fiatAvailableBalance: isFiatDisabled ? '' : '---',
|
||||
fiatFrozenBalance: isFiatDisabled ? '' : '---',
|
||||
fiatSecondAvailableBalance: isFiatDisabled ? '' : '---',
|
||||
fiatSecondAdditionalBalance: isFiatDisabled ? '' : '---',
|
||||
asset: key,
|
||||
formattedAssetTitle: _formatterAsset(key)));
|
||||
}
|
||||
|
@ -274,24 +306,46 @@ abstract class BalanceViewModelBase with Store {
|
|||
' ' +
|
||||
_getFiatBalance(price: price, cryptoAmount: getFormattedFrozenBalance(value)));
|
||||
|
||||
final secondAdditionalFiatBalance = isFiatDisabled
|
||||
? ''
|
||||
: (fiatCurrency.toString() +
|
||||
' ' +
|
||||
_getFiatBalance(price: price, cryptoAmount: value.formattedSecondAdditionalBalance));
|
||||
|
||||
final secondAvailableFiatBalance = isFiatDisabled
|
||||
? ''
|
||||
: (fiatCurrency.toString() +
|
||||
' ' +
|
||||
_getFiatBalance(price: price, cryptoAmount: value.formattedSecondAvailableBalance));
|
||||
|
||||
return MapEntry(
|
||||
key,
|
||||
BalanceRecord(
|
||||
availableBalance: value.formattedAvailableBalance,
|
||||
additionalBalance: value.formattedAdditionalBalance,
|
||||
frozenBalance: getFormattedFrozenBalance(value),
|
||||
secondAvailableBalance: value.formattedSecondAvailableBalance,
|
||||
secondAdditionalBalance: value.formattedSecondAdditionalBalance,
|
||||
fiatAdditionalBalance: additionalFiatBalance,
|
||||
fiatAvailableBalance: availableFiatBalance,
|
||||
fiatFrozenBalance: frozenFiatBalance,
|
||||
fiatSecondAvailableBalance: secondAvailableFiatBalance,
|
||||
fiatSecondAdditionalBalance: secondAdditionalFiatBalance,
|
||||
asset: key,
|
||||
formattedAssetTitle: _formatterAsset(key)));
|
||||
});
|
||||
}
|
||||
|
||||
@computed
|
||||
bool get hasAdditionalBalance => _hasAdditionBalanceForWalletType(wallet.type);
|
||||
bool get hasAdditionalBalance => _hasAdditionalBalanceForWalletType(wallet.type);
|
||||
|
||||
bool _hasAdditionBalanceForWalletType(WalletType type) {
|
||||
@computed
|
||||
bool get hasSecondAdditionalBalance => _hasSecondAdditionalBalanceForWalletType(wallet.type);
|
||||
|
||||
@computed
|
||||
bool get hasSecondAvailableBalance => _hasSecondAvailableBalanceForWalletType(wallet.type);
|
||||
|
||||
bool _hasAdditionalBalanceForWalletType(WalletType type) {
|
||||
switch (type) {
|
||||
case WalletType.ethereum:
|
||||
case WalletType.polygon:
|
||||
|
@ -303,6 +357,20 @@ abstract class BalanceViewModelBase with Store {
|
|||
}
|
||||
}
|
||||
|
||||
bool _hasSecondAdditionalBalanceForWalletType(WalletType type) {
|
||||
if (wallet.type == WalletType.litecoin && settingsStore.mwebEnabled) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool _hasSecondAvailableBalanceForWalletType(WalletType type) {
|
||||
if (wallet.type == WalletType.litecoin && settingsStore.mwebEnabled) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@computed
|
||||
List<BalanceRecord> get formattedBalances {
|
||||
final balance = balances.values.toList();
|
||||
|
|
|
@ -137,8 +137,8 @@ abstract class DashboardViewModelBase with Store {
|
|||
FilterItem(
|
||||
value: () => tradeFilterStore.displayLetsExchange,
|
||||
caption: ExchangeProviderDescription.letsExchange.title,
|
||||
onChanged: () =>
|
||||
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.letsExchange)),
|
||||
onChanged: () => tradeFilterStore
|
||||
.toggleDisplayExchange(ExchangeProviderDescription.letsExchange)),
|
||||
FilterItem(
|
||||
value: () => tradeFilterStore.displayStealthEx,
|
||||
caption: ExchangeProviderDescription.stealthEx.title,
|
||||
|
@ -255,6 +255,16 @@ abstract class DashboardViewModelBase with Store {
|
|||
silentPaymentsScanningActive = bitcoin!.getScanningActive(wallet);
|
||||
});
|
||||
}
|
||||
|
||||
if (hasMweb) {
|
||||
mwebScanningActive = bitcoin!.getMwebEnabled(wallet);
|
||||
settingsStore.mwebEnabled = mwebScanningActive;
|
||||
reaction((_) => settingsStore.mwebAlwaysScan, (bool alwaysScan) {
|
||||
if (alwaysScan) {
|
||||
mwebScanningActive = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@observable
|
||||
|
@ -348,6 +358,7 @@ abstract class DashboardViewModelBase with Store {
|
|||
bool get hasRescan =>
|
||||
wallet.type == WalletType.bitcoin ||
|
||||
wallet.type == WalletType.monero ||
|
||||
wallet.type == WalletType.litecoin ||
|
||||
wallet.type == WalletType.wownero ||
|
||||
wallet.type == WalletType.haven;
|
||||
|
||||
|
@ -416,6 +427,33 @@ abstract class DashboardViewModelBase with Store {
|
|||
}
|
||||
}
|
||||
|
||||
@computed
|
||||
bool get hasMweb => wallet.type == WalletType.litecoin;
|
||||
|
||||
@computed
|
||||
bool get showMwebCard => hasMweb && settingsStore.mwebCardDisplay;
|
||||
|
||||
@observable
|
||||
bool mwebScanningActive = false;
|
||||
|
||||
@computed
|
||||
bool get hasEnabledMwebBefore => settingsStore.hasEnabledMwebBefore;
|
||||
|
||||
@action
|
||||
void setMwebScanningActive(bool active) {
|
||||
if (!hasMweb) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (active) {
|
||||
settingsStore.hasEnabledMwebBefore = true;
|
||||
}
|
||||
|
||||
settingsStore.mwebEnabled = active;
|
||||
mwebScanningActive = active;
|
||||
bitcoin!.setMwebEnabled(wallet, active);
|
||||
}
|
||||
|
||||
BalanceViewModel balanceViewModel;
|
||||
|
||||
AppStore appStore;
|
||||
|
|
|
@ -11,19 +11,31 @@ class ReceiveOptionViewModel = ReceiveOptionViewModelBase with _$ReceiveOptionVi
|
|||
abstract class ReceiveOptionViewModelBase with Store {
|
||||
ReceiveOptionViewModelBase(this._wallet, this.initialPageOption)
|
||||
: selectedReceiveOption = initialPageOption ??
|
||||
(_wallet.type == WalletType.bitcoin
|
||||
(_wallet.type == WalletType.bitcoin ||
|
||||
_wallet.type == WalletType.litecoin
|
||||
? bitcoin!.getSelectedAddressType(_wallet)
|
||||
: ReceivePageOption.mainnet),
|
||||
_options = [] {
|
||||
final walletType = _wallet.type;
|
||||
_options = walletType == WalletType.haven
|
||||
? [ReceivePageOption.mainnet]
|
||||
: walletType == WalletType.bitcoin
|
||||
? [
|
||||
switch (walletType) {
|
||||
case WalletType.bitcoin:
|
||||
_options = [
|
||||
...bitcoin!.getBitcoinReceivePageOptions(),
|
||||
...ReceivePageOptions.where((element) => element != ReceivePageOption.mainnet)
|
||||
]
|
||||
: ReceivePageOptions;
|
||||
];
|
||||
break;
|
||||
case WalletType.litecoin:
|
||||
_options = [
|
||||
...bitcoin!.getLitecoinReceivePageOptions(),
|
||||
...ReceivePageOptions.where((element) => element != ReceivePageOption.mainnet)
|
||||
];
|
||||
break;
|
||||
case WalletType.haven:
|
||||
_options = [ReceivePageOption.mainnet];
|
||||
break;
|
||||
default:
|
||||
_options = ReceivePageOptions;
|
||||
}
|
||||
}
|
||||
|
||||
final WalletBase _wallet;
|
||||
|
|
|
@ -56,7 +56,8 @@ class TransactionListItem extends ActionListItem with Keyable {
|
|||
}
|
||||
|
||||
String get formattedPendingStatus {
|
||||
if (balanceViewModel.wallet.type == WalletType.monero || balanceViewModel.wallet.type == WalletType.haven) {
|
||||
if (balanceViewModel.wallet.type == WalletType.monero ||
|
||||
balanceViewModel.wallet.type == WalletType.haven) {
|
||||
if (transaction.confirmations >= 0 && transaction.confirmations < 10) {
|
||||
return ' (${transaction.confirmations}/10)';
|
||||
}
|
||||
|
@ -77,6 +78,13 @@ class TransactionListItem extends ActionListItem with Keyable {
|
|||
return transaction.isPending ? S.current.pending : '';
|
||||
}
|
||||
|
||||
String get formattedType {
|
||||
if (transaction.evmSignatureName == 'approval') {
|
||||
return ' (${transaction.evmSignatureName})';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
CryptoCurrency? get assetOfTransaction {
|
||||
try {
|
||||
if (balanceViewModel.wallet.type == WalletType.ethereum) {
|
||||
|
|
|
@ -30,6 +30,8 @@ abstract class RescanViewModelBase with Store {
|
|||
bool get isSilentPaymentsScan => wallet.type == WalletType.bitcoin;
|
||||
|
||||
@computed
|
||||
bool get isMwebScan => wallet.type == WalletType.litecoin;
|
||||
|
||||
Future<bool> get isBitcoinMempoolAPIEnabled async =>
|
||||
wallet.type == WalletType.bitcoin && await bitcoin!.checkIfMempoolAPIIsEnabled(wallet);
|
||||
|
||||
|
|
|
@ -217,7 +217,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
|||
PendingTransaction? pendingTransaction;
|
||||
|
||||
@computed
|
||||
String get balance => wallet.balance[selectedCryptoCurrency]!.formattedAvailableBalance;
|
||||
String get balance => wallet.balance[selectedCryptoCurrency]!.formattedFullAvailableBalance;
|
||||
|
||||
@computed
|
||||
bool get isFiatDisabled => balanceViewModel.isFiatDisabled;
|
||||
|
@ -675,6 +675,9 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
|||
return S.current.tx_no_dust_exception;
|
||||
}
|
||||
if (error is TransactionCommitFailed) {
|
||||
if (error.errorMessage != null && error.errorMessage!.contains("no peers replied")) {
|
||||
return S.current.tx_commit_failed_no_peers;
|
||||
}
|
||||
return "${S.current.tx_commit_failed}${error.errorMessage != null ? "\n\n${error.errorMessage}" : ""}";
|
||||
}
|
||||
if (error is TransactionCommitFailedDustChange) {
|
||||
|
|
32
lib/view_model/settings/mweb_settings_view_model.dart
Normal file
32
lib/view_model/settings/mweb_settings_view_model.dart
Normal file
|
@ -0,0 +1,32 @@
|
|||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/store/settings_store.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part 'mweb_settings_view_model.g.dart';
|
||||
|
||||
class MwebSettingsViewModel = MwebSettingsViewModelBase with _$MwebSettingsViewModel;
|
||||
|
||||
abstract class MwebSettingsViewModelBase with Store {
|
||||
MwebSettingsViewModelBase(this._settingsStore, this._wallet);
|
||||
|
||||
final SettingsStore _settingsStore;
|
||||
final WalletBase _wallet;
|
||||
|
||||
@computed
|
||||
bool get mwebCardDisplay => _settingsStore.mwebCardDisplay;
|
||||
|
||||
@computed
|
||||
bool get mwebAlwaysScan => _settingsStore.mwebAlwaysScan;
|
||||
|
||||
@action
|
||||
void setMwebCardDisplay(bool value) {
|
||||
_settingsStore.mwebCardDisplay = value;
|
||||
}
|
||||
|
||||
@action
|
||||
void setMwebAlwaysScan(bool value) {
|
||||
_settingsStore.mwebAlwaysScan = value;
|
||||
bitcoin!.setMwebEnabled(_wallet, value);
|
||||
}
|
||||
}
|
|
@ -38,6 +38,10 @@ abstract class UnspentCoinsListViewModelBase with Store {
|
|||
final info =
|
||||
getUnspentCoinInfo(item.hash, item.address, item.amountRaw, item.vout, item.keyImage);
|
||||
|
||||
if (info == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
info.isFrozen = item.isFrozen;
|
||||
info.isSending = item.isSending;
|
||||
info.note = item.note;
|
||||
|
@ -50,15 +54,21 @@ abstract class UnspentCoinsListViewModelBase with Store {
|
|||
}
|
||||
}
|
||||
|
||||
UnspentCoinsInfo getUnspentCoinInfo(
|
||||
String hash, String address, int value, int vout, String? keyImage) =>
|
||||
_unspentCoinsInfo.values.firstWhere((element) =>
|
||||
UnspentCoinsInfo? getUnspentCoinInfo(
|
||||
String hash, String address, int value, int vout, String? keyImage) {
|
||||
try {
|
||||
return _unspentCoinsInfo.values.firstWhere((element) =>
|
||||
element.walletId == wallet.id &&
|
||||
element.hash == hash &&
|
||||
element.address == address &&
|
||||
element.value == value &&
|
||||
element.vout == vout &&
|
||||
element.keyImage == keyImage);
|
||||
} catch (e) {
|
||||
print("UnspentCoinsInfo not found for coin: $e");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
String formatAmountToString(int fullBalance) {
|
||||
if (wallet.type == WalletType.monero)
|
||||
|
@ -85,22 +95,32 @@ abstract class UnspentCoinsListViewModelBase with Store {
|
|||
}
|
||||
|
||||
List<Unspent> _getUnspents() {
|
||||
if (wallet.type == WalletType.monero) return monero!.getUnspents(wallet);
|
||||
if (wallet.type == WalletType.wownero) return wownero!.getUnspents(wallet);
|
||||
if ([WalletType.bitcoin, WalletType.litecoin, WalletType.bitcoinCash].contains(wallet.type))
|
||||
switch (wallet.type) {
|
||||
case WalletType.monero:
|
||||
return monero!.getUnspents(wallet);
|
||||
case WalletType.wownero:
|
||||
return wownero!.getUnspents(wallet);
|
||||
case WalletType.bitcoin:
|
||||
case WalletType.litecoin:
|
||||
case WalletType.bitcoinCash:
|
||||
return bitcoin!.getUnspents(wallet);
|
||||
default:
|
||||
return List.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
void _updateUnspentCoinsInfo() {
|
||||
_items.clear();
|
||||
|
||||
List<UnspentCoinsItem> unspents = [];
|
||||
_getUnspents().forEach((elem) {
|
||||
_getUnspents().forEach((Unspent elem) {
|
||||
try {
|
||||
final info =
|
||||
getUnspentCoinInfo(elem.hash, elem.address, elem.value, elem.vout, elem.keyImage);
|
||||
if (info == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
unspents.add(UnspentCoinsItem(
|
||||
address: elem.address,
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/core/wallet_change_listener_view_model.dart';
|
||||
import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
|
||||
|
@ -217,8 +219,9 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
|||
}) : _baseItems = <ListItem>[],
|
||||
selectedCurrency = walletTypeToCryptoCurrency(appStore.wallet!.type),
|
||||
_cryptoNumberFormat = NumberFormat(_cryptoNumberPattern),
|
||||
hasAccounts =
|
||||
appStore.wallet!.type == WalletType.monero || appStore.wallet!.type == WalletType.wownero || appStore.wallet!.type == WalletType.haven,
|
||||
hasAccounts = appStore.wallet!.type == WalletType.monero ||
|
||||
appStore.wallet!.type == WalletType.wownero ||
|
||||
appStore.wallet!.type == WalletType.haven,
|
||||
amount = '',
|
||||
_settingsStore = appStore.settingsStore,
|
||||
super(appStore: appStore) {
|
||||
|
@ -230,7 +233,9 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
|||
_init();
|
||||
|
||||
selectedCurrency = walletTypeToCryptoCurrency(wallet.type);
|
||||
hasAccounts = wallet.type == WalletType.monero || wallet.type == WalletType.wownero || wallet.type == WalletType.haven;
|
||||
hasAccounts = wallet.type == WalletType.monero ||
|
||||
wallet.type == WalletType.wownero ||
|
||||
wallet.type == WalletType.haven;
|
||||
}
|
||||
|
||||
static const String _cryptoNumberPattern = '0.00000000';
|
||||
|
@ -404,7 +409,7 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
|||
});
|
||||
addressList.addAll(receivedAddressItems);
|
||||
} else {
|
||||
final addressItems = bitcoin!.getSubAddresses(wallet).map((subaddress) {
|
||||
var addressItems = bitcoin!.getSubAddresses(wallet).map((subaddress) {
|
||||
final isPrimary = subaddress.id == 0;
|
||||
|
||||
return WalletAddressListItem(
|
||||
|
@ -417,6 +422,16 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
|||
walletTypeToCryptoCurrency(type), subaddress.balance),
|
||||
isChange: subaddress.isChange);
|
||||
});
|
||||
|
||||
// don't show all 1000+ mweb addresses:
|
||||
if (wallet.type == WalletType.litecoin && addressItems.length >= 1000) {
|
||||
// find the index of the last item with a txCount > 0
|
||||
final addressItemsList = addressItems.toList();
|
||||
final lastItemWithTxCount = addressItemsList.lastWhere((item) => (item.txCount ?? 0) > 0);
|
||||
final index = addressItemsList.indexOf(lastItemWithTxCount);
|
||||
// show only up to that index + 20:
|
||||
addressItems = addressItemsList.sublist(0, index + 20);
|
||||
}
|
||||
addressList.addAll(addressItems);
|
||||
}
|
||||
}
|
||||
|
@ -519,7 +534,7 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
|||
|
||||
@action
|
||||
Future<void> setAddressType(dynamic option) async {
|
||||
if (wallet.type == WalletType.bitcoin) {
|
||||
if (wallet.type == WalletType.bitcoin || wallet.type == WalletType.litecoin) {
|
||||
await bitcoin!.setAddressType(wallet, option);
|
||||
}
|
||||
}
|
||||
|
@ -527,7 +542,9 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
|||
void _init() {
|
||||
_baseItems = [];
|
||||
|
||||
if (wallet.type == WalletType.monero || wallet.type == WalletType.wownero || wallet.type == WalletType.haven) {
|
||||
if (wallet.type == WalletType.monero ||
|
||||
wallet.type == WalletType.wownero ||
|
||||
wallet.type == WalletType.haven) {
|
||||
_baseItems.add(WalletAccountListHeader());
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import FlutterMacOS
|
|||
import Foundation
|
||||
|
||||
import connectivity_plus
|
||||
import cw_mweb
|
||||
import device_info_plus
|
||||
import devicelocale
|
||||
import flutter_inappwebview_macos
|
||||
|
@ -21,6 +22,7 @@ import wakelock_plus
|
|||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
ConnectivityPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlugin"))
|
||||
CwMwebPlugin.register(with: registry.registrar(forPlugin: "CwMwebPlugin"))
|
||||
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
|
||||
DevicelocalePlugin.register(with: registry.registrar(forPlugin: "DevicelocalePlugin"))
|
||||
InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin"))
|
||||
|
|
|
@ -11,4 +11,5 @@ cd cw_tron; flutter pub get; flutter packages pub run build_runner build --delet
|
|||
cd cw_wownero; flutter pub get; flutter packages pub run build_runner build --delete-conflicting-outputs; cd ..
|
||||
cd cw_polygon; flutter pub get; cd ..
|
||||
cd cw_ethereum; flutter pub get; cd ..
|
||||
cd cw_mweb && flutter pub get && cd ..
|
||||
flutter packages pub run build_runner build --delete-conflicting-outputs
|
||||
|
|
|
@ -97,10 +97,6 @@ dependencies:
|
|||
polyseed: ^0.0.6
|
||||
nostr_tools: ^1.0.9
|
||||
solana: ^0.30.1
|
||||
bitcoin_base:
|
||||
git:
|
||||
url: https://github.com/cake-tech/bitcoin_base
|
||||
ref: cake-update-v7
|
||||
ledger_flutter: ^1.0.1
|
||||
hashlib: ^1.19.2
|
||||
|
||||
|
@ -138,10 +134,12 @@ dependency_overrides:
|
|||
url: https://github.com/cake-tech/web3dart.git
|
||||
ref: cake
|
||||
flutter_secure_storage_platform_interface: 1.0.2
|
||||
protobuf: ^3.1.0
|
||||
bitcoin_base:
|
||||
git:
|
||||
url: https://github.com/cake-tech/bitcoin_base
|
||||
ref: cake-update-v7
|
||||
ref: cake-update-v8
|
||||
ffi: 2.1.0
|
||||
|
||||
flutter_icons:
|
||||
image_path: "assets/images/app_logo.png"
|
||||
|
|
|
@ -363,6 +363,14 @@
|
|||
"ledger_error_wrong_app": "يرجى التأكد",
|
||||
"ledger_please_enable_bluetooth": "يرجى تمكين البلوتوث للكشف عن دفتر الأستاذ الخاص بك",
|
||||
"light_theme": "فاتح",
|
||||
"litecoin_enable_mweb_sync": "تمكين MWEB المسح الضوئي",
|
||||
"litecoin_mweb": "mweb",
|
||||
"litecoin_mweb_always_scan": "اضبط MWEB دائمًا على المسح الضوئي",
|
||||
"litecoin_mweb_display_card": "عرض بطاقة mweb",
|
||||
"litecoin_mweb_scanning": "MWEB المسح الضوئي",
|
||||
"litecoin_mweb_settings": "إعدادات MWEB",
|
||||
"litecoin_mweb_warning": "سيقوم استخدام MWEB في البداية بتنزيل ~ 600 ميجابايت من البيانات ، وقد يستغرق ما يصل إلى 30 دقيقة حسب سرعة الشبكة. سيتم تنزيل هذه البيانات الأولية مرة واحدة فقط وستكون متاحة لجميع محافظ Litecoin",
|
||||
"litecoin_what_is_mweb": "ما هو MWEB؟",
|
||||
"live_fee_rates": "أسعار الرسوم المباشرة عبر API",
|
||||
"load_more": "تحميل المزيد",
|
||||
"loading_your_wallet": "يتم تحميل محفظتك",
|
||||
|
@ -393,6 +401,8 @@
|
|||
"monero_light_theme": " ضوء مونيرو",
|
||||
"moonpay_alert_text": "يجب أن تكون قيمة المبلغ أكبر من أو تساوي ${minAmount} ${fiatCurrency}",
|
||||
"more_options": "المزيد من الخيارات",
|
||||
"mweb_confirmed": "أكد MWEB",
|
||||
"mweb_unconfirmed": "غير مؤكد MWEB",
|
||||
"name": "ﻢﺳﺍ",
|
||||
"nano_current_rep": "الممثل الحالي",
|
||||
"nano_gpt_thanks_message": "شكرا لاستخدام nanogpt! تذكر أن تعود إلى المتصفح بعد اكتمال معاملتك!",
|
||||
|
@ -814,6 +824,7 @@
|
|||
"trusted": "موثوق به",
|
||||
"tx_commit_exception_no_dust_on_change": "يتم رفض المعاملة مع هذا المبلغ. باستخدام هذه العملات المعدنية ، يمكنك إرسال ${min} دون تغيير أو ${max} الذي يعيد التغيير.",
|
||||
"tx_commit_failed": "فشل ارتكاب المعاملة. يرجى الاتصال بالدعم.",
|
||||
"tx_commit_failed_no_peers": "فشل المعاملة في البث ، يرجى المحاولة مرة أخرى في ثانية أو نحو ذلك",
|
||||
"tx_invalid_input": "أنت تستخدم نوع الإدخال الخاطئ لهذا النوع من الدفع",
|
||||
"tx_no_dust_exception": "يتم رفض المعاملة عن طريق إرسال مبلغ صغير جدًا. يرجى محاولة زيادة المبلغ.",
|
||||
"tx_not_enough_inputs_exception": "لا يكفي المدخلات المتاحة. الرجاء تحديد المزيد تحت التحكم في العملة",
|
||||
|
|
|
@ -363,6 +363,14 @@
|
|||
"ledger_error_wrong_app": "Моля, уверете се, че сте отворили правилното приложение на вашата книга",
|
||||
"ledger_please_enable_bluetooth": "Моля, активирайте Bluetooth да открие вашата книга",
|
||||
"light_theme": "Светло",
|
||||
"litecoin_enable_mweb_sync": "Активирайте сканирането на MWeb",
|
||||
"litecoin_mweb": "Mweb",
|
||||
"litecoin_mweb_always_scan": "Задайте MWeb винаги сканиране",
|
||||
"litecoin_mweb_display_card": "Показване на MWEB карта",
|
||||
"litecoin_mweb_scanning": "Сканиране на MWEB",
|
||||
"litecoin_mweb_settings": "Настройки на MWEB",
|
||||
"litecoin_mweb_warning": "Използването на MWEB първоначално ще изтегли ~ 600MB данни и може да отнеме до 30 минути в зависимост от скоростта на мрежата. Тези първоначални данни ще изтеглят само веднъж и ще бъдат достъпни за всички портфейли Litecoin",
|
||||
"litecoin_what_is_mweb": "Какво е MWEB?",
|
||||
"live_fee_rates": "Цени на таксите на живо чрез API",
|
||||
"load_more": "Зареди още",
|
||||
"loading_your_wallet": "Зареждане на портфейл",
|
||||
|
@ -393,6 +401,8 @@
|
|||
"monero_light_theme": "Лека тема Monero",
|
||||
"moonpay_alert_text": "Сумата трябва да бъде най-малко ${minAmount} ${fiatCurrency}",
|
||||
"more_options": "Още настройки",
|
||||
"mweb_confirmed": "Потвърден MWeb",
|
||||
"mweb_unconfirmed": "Непотвърден mweb",
|
||||
"name": "Име",
|
||||
"nano_current_rep": "Настоящ представител",
|
||||
"nano_gpt_thanks_message": "Благодаря, че използвахте Nanogpt! Не забравяйте да се върнете обратно към браузъра, след като транзакцията ви приключи!",
|
||||
|
@ -814,6 +824,7 @@
|
|||
"trusted": "Надежден",
|
||||
"tx_commit_exception_no_dust_on_change": "Сделката се отхвърля с тази сума. С тези монети можете да изпратите ${min} без промяна или ${max}, която връща промяна.",
|
||||
"tx_commit_failed": "Компетацията на транзакцията не успя. Моля, свържете се с поддръжката.",
|
||||
"tx_commit_failed_no_peers": "Сделката не успя да излъчи, моля, опитайте отново след секунда или така",
|
||||
"tx_invalid_input": "Използвате грешен тип вход за този тип плащане",
|
||||
"tx_no_dust_exception": "Сделката се отхвърля чрез изпращане на сума твърде малка. Моля, опитайте да увеличите сумата.",
|
||||
"tx_not_enough_inputs_exception": "Няма достатъчно налични входове. Моля, изберете повече под контрол на монети",
|
||||
|
|
|
@ -363,6 +363,14 @@
|
|||
"ledger_error_wrong_app": "Ujistěte se, že se na své knize otevřete správnou aplikaci",
|
||||
"ledger_please_enable_bluetooth": "Umožněte prosím Bluetooth detekovat vaši knihu",
|
||||
"light_theme": "Světlý",
|
||||
"litecoin_enable_mweb_sync": "Povolit skenování MWeb",
|
||||
"litecoin_mweb": "MWeb",
|
||||
"litecoin_mweb_always_scan": "Nastavit MWeb vždy skenování",
|
||||
"litecoin_mweb_display_card": "Zobrazit kartu MWeb",
|
||||
"litecoin_mweb_scanning": "Skenování mWeb",
|
||||
"litecoin_mweb_settings": "Nastavení mWeb",
|
||||
"litecoin_mweb_warning": "Pomocí MWeb zpočátku stahuje ~ 600 MB dat a může trvat až 30 minut v závislosti na rychlosti sítě. Tato počáteční data si stáhnou pouze jednou a budou k dispozici pro všechny litecoinové peněženky",
|
||||
"litecoin_what_is_mweb": "Co je Mweb?",
|
||||
"live_fee_rates": "Živé sazby poplatků prostřednictvím API",
|
||||
"load_more": "Načíst další",
|
||||
"loading_your_wallet": "Načítám peněženku",
|
||||
|
@ -393,6 +401,8 @@
|
|||
"monero_light_theme": "Světlé téma Monero",
|
||||
"moonpay_alert_text": "Částka musí být větší nebo rovna ${minAmount} ${fiatCurrency}",
|
||||
"more_options": "Více možností",
|
||||
"mweb_confirmed": "Potvrzený mweb",
|
||||
"mweb_unconfirmed": "Nepotvrzené mWeb",
|
||||
"name": "název",
|
||||
"nano_current_rep": "Současný zástupce",
|
||||
"nano_gpt_thanks_message": "Děkujeme za používání Nanogpt! Nezapomeňte se po dokončení transakce vydat zpět do prohlížeče!",
|
||||
|
@ -814,6 +824,7 @@
|
|||
"trusted": "Důvěřovat",
|
||||
"tx_commit_exception_no_dust_on_change": "Transakce je zamítnuta s touto částkou. S těmito mincemi můžete odeslat ${min} bez změny nebo ${max}, které se vrátí změna.",
|
||||
"tx_commit_failed": "Transakce COMPORT selhala. Kontaktujte prosím podporu.",
|
||||
"tx_commit_failed_no_peers": "Transakce se nepodařilo vysílat, zkuste to prosím znovu za vteřinu",
|
||||
"tx_invalid_input": "Pro tento typ platby používáte nesprávný typ vstupu",
|
||||
"tx_no_dust_exception": "Transakce je zamítnuta odesláním příliš malé. Zkuste prosím zvýšit částku.",
|
||||
"tx_not_enough_inputs_exception": "Není k dispozici dostatek vstupů. Vyberte prosím více pod kontrolou mincí",
|
||||
|
|
|
@ -363,6 +363,14 @@
|
|||
"ledger_error_wrong_app": "Bitte stellen Sie sicher, dass Sie die richtige App auf Ihrem Ledger geöffnet haben",
|
||||
"ledger_please_enable_bluetooth": "Bitte aktivieren Sie Bluetooth um sich mit Ihren Ledger zu verbinden.",
|
||||
"light_theme": "Hell",
|
||||
"litecoin_enable_mweb_sync": "Aktivieren Sie das MWEB -Scannen",
|
||||
"litecoin_mweb": "MWeb",
|
||||
"litecoin_mweb_always_scan": "Setzen Sie MWeb immer scannen",
|
||||
"litecoin_mweb_display_card": "MWEB -Karte anzeigen",
|
||||
"litecoin_mweb_scanning": "MWEB Scanning",
|
||||
"litecoin_mweb_settings": "MWEB -Einstellungen",
|
||||
"litecoin_mweb_warning": "Durch die Verwendung von MWEB wird zunächst ~ 600 MB Daten heruntergeladen und kann je nach Netzwerkgeschwindigkeit bis zu 30 Minuten dauern. Diese ersten Daten werden nur einmal heruntergeladen und für alle Litecoin -Brieftaschen verfügbar",
|
||||
"litecoin_what_is_mweb": "Was ist MWeb?",
|
||||
"live_fee_rates": "Live -Gebührenpreise über API",
|
||||
"load_more": "Mehr laden",
|
||||
"loading_your_wallet": "Wallet wird geladen",
|
||||
|
@ -393,6 +401,8 @@
|
|||
"monero_light_theme": "Monero Light-Thema",
|
||||
"moonpay_alert_text": "Der Wert des Betrags muss größer oder gleich ${minAmount} ${fiatCurrency} sein",
|
||||
"more_options": "Weitere Optionen",
|
||||
"mweb_confirmed": "Bestätigt MWeb",
|
||||
"mweb_unconfirmed": "Unbestätigter MWeb",
|
||||
"name": "Name",
|
||||
"nano_current_rep": "Aktueller Vertreter",
|
||||
"nano_gpt_thanks_message": "Danke, dass du Nanogpt benutzt hast! Denken Sie daran, nach Abschluss Ihrer Transaktion zurück zum Browser zu gehen!",
|
||||
|
@ -815,6 +825,7 @@
|
|||
"trusted": "Vertrauenswürdige",
|
||||
"tx_commit_exception_no_dust_on_change": "Die Transaktion wird diesen Betrag abgelehnt. Mit diesen Münzen können Sie ${min} ohne Veränderung oder ${max} senden, die Änderungen zurückgeben.",
|
||||
"tx_commit_failed": "Transaktionsausschüsse ist fehlgeschlagen. Bitte wenden Sie sich an Support.",
|
||||
"tx_commit_failed_no_peers": "Transaktion konnte nicht übertragen werden. Bitte versuchen Sie es in einer Sekunde oder so erneut",
|
||||
"tx_invalid_input": "Sie verwenden den falschen Eingangstyp für diese Art von Zahlung",
|
||||
"tx_no_dust_exception": "Die Transaktion wird abgelehnt, indem eine Menge zu klein gesendet wird. Bitte versuchen Sie, die Menge zu erhöhen.",
|
||||
"tx_not_enough_inputs_exception": "Nicht genügend Eingänge verfügbar. Bitte wählen Sie mehr unter Münzkontrolle aus",
|
||||
|
|
|
@ -363,6 +363,14 @@
|
|||
"ledger_error_wrong_app": "Please make sure you opend the right app on your ledger",
|
||||
"ledger_please_enable_bluetooth": "Please enable Bluetooth to detect your Ledger",
|
||||
"light_theme": "Light",
|
||||
"litecoin_enable_mweb_sync": "Enable MWEB scanning",
|
||||
"litecoin_mweb": "MWEB",
|
||||
"litecoin_mweb_always_scan": "Set MWEB always scanning",
|
||||
"litecoin_mweb_display_card": "Show MWEB card",
|
||||
"litecoin_mweb_scanning": "MWEB Scanning",
|
||||
"litecoin_mweb_settings": "MWEB settings",
|
||||
"litecoin_mweb_warning": "Using MWEB will initially download ~600MB of data, and may take up to 30 minutes depending on network speed. This initial data will only download once and be available for all Litecoin wallets",
|
||||
"litecoin_what_is_mweb": "What is MWEB?",
|
||||
"live_fee_rates": "Live fee rates via API",
|
||||
"load_more": "Load more",
|
||||
"loading_your_wallet": "Loading your wallet",
|
||||
|
@ -393,6 +401,8 @@
|
|||
"monero_light_theme": "Monero Light Theme",
|
||||
"moonpay_alert_text": "Value of the amount must be more or equal to ${minAmount} ${fiatCurrency}",
|
||||
"more_options": "More Options",
|
||||
"mweb_confirmed": "Confirmed MWEB",
|
||||
"mweb_unconfirmed": "Unconfirmed MWEB",
|
||||
"name": "Name",
|
||||
"nano_current_rep": "Current Representative",
|
||||
"nano_gpt_thanks_message": "Thanks for using NanoGPT! Remember to head back to the browser after your transaction completes!",
|
||||
|
@ -814,6 +824,7 @@
|
|||
"trusted": "Trusted",
|
||||
"tx_commit_exception_no_dust_on_change": "The transaction is rejected with this amount. With these coins you can send ${min} without change or ${max} that returns change.",
|
||||
"tx_commit_failed": "Transaction commit failed. Please contact support.",
|
||||
"tx_commit_failed_no_peers": "Transaction failed to broadcast, please try again in a second or so",
|
||||
"tx_invalid_input": "You are using the wrong input type for this type of payment",
|
||||
"tx_no_dust_exception": "The transaction is rejected by sending an amount too small. Please try increasing the amount.",
|
||||
"tx_not_enough_inputs_exception": "Not enough inputs available. Please select more under Coin Control",
|
||||
|
|
|
@ -363,6 +363,14 @@
|
|||
"ledger_error_wrong_app": "Por favor, asegúrese de abrir la aplicación correcta en su libro mayor.",
|
||||
"ledger_please_enable_bluetooth": "Habilite Bluetooth para detectar su libro mayor",
|
||||
"light_theme": "Ligera",
|
||||
"litecoin_enable_mweb_sync": "Habilitar el escaneo mweb",
|
||||
"litecoin_mweb": "Mweb",
|
||||
"litecoin_mweb_always_scan": "Establecer mweb siempre escaneo",
|
||||
"litecoin_mweb_display_card": "Mostrar tarjeta MWEB",
|
||||
"litecoin_mweb_scanning": "Escaneo mweb",
|
||||
"litecoin_mweb_settings": "Configuración de MWEB",
|
||||
"litecoin_mweb_warning": "El uso de MWEB inicialmente descargará ~ 600 MB de datos, y puede tomar hasta 30 minutos según la velocidad de la red. Estos datos iniciales solo se descargarán una vez y estarán disponibles para todas las billeteras de Litecoin",
|
||||
"litecoin_what_is_mweb": "¿Qué es mweb?",
|
||||
"live_fee_rates": "Tasas de tarifas en vivo a través de API",
|
||||
"load_more": "Carga más",
|
||||
"loading_your_wallet": "Cargando tu billetera",
|
||||
|
@ -393,6 +401,8 @@
|
|||
"monero_light_theme": "Tema ligero de Monero",
|
||||
"moonpay_alert_text": "El valor de la cantidad debe ser mayor o igual a ${minAmount} ${fiatCurrency}",
|
||||
"more_options": "Más Opciones",
|
||||
"mweb_confirmed": "Confirmado mweb",
|
||||
"mweb_unconfirmed": "Mweb no confirmado",
|
||||
"name": "Nombre",
|
||||
"nano_current_rep": "Representante actual",
|
||||
"nano_gpt_thanks_message": "¡Gracias por usar nanogpt! ¡Recuerde regresar al navegador después de que se complete su transacción!",
|
||||
|
@ -815,6 +825,7 @@
|
|||
"trusted": "de confianza",
|
||||
"tx_commit_exception_no_dust_on_change": "La transacción se rechaza con esta cantidad. Con estas monedas puede enviar ${min} sin cambios o ${max} que devuelve el cambio.",
|
||||
"tx_commit_failed": "La confirmación de transacción falló. Póngase en contacto con el soporte.",
|
||||
"tx_commit_failed_no_peers": "La transacción no se transmitió, intente nuevamente en un segundo más o menos",
|
||||
"tx_invalid_input": "Está utilizando el tipo de entrada incorrecto para este tipo de pago",
|
||||
"tx_no_dust_exception": "La transacción se rechaza enviando una cantidad demasiado pequeña. Intente aumentar la cantidad.",
|
||||
"tx_not_enough_inputs_exception": "No hay suficientes entradas disponibles. Seleccione más bajo control de monedas",
|
||||
|
|
|
@ -363,6 +363,14 @@
|
|||
"ledger_error_wrong_app": "Veuillez vous assurer d'ouvrir la bonne application sur votre grand livre",
|
||||
"ledger_please_enable_bluetooth": "Veuillez activer Bluetooth pour détecter votre grand livre",
|
||||
"light_theme": "Clair",
|
||||
"litecoin_enable_mweb_sync": "Activer la numérisation MWEB",
|
||||
"litecoin_mweb": "Mweb",
|
||||
"litecoin_mweb_always_scan": "Définir MWEB Score Scanning",
|
||||
"litecoin_mweb_display_card": "Afficher la carte MWeb",
|
||||
"litecoin_mweb_scanning": "Scann mweb",
|
||||
"litecoin_mweb_settings": "Paramètres MWEB",
|
||||
"litecoin_mweb_warning": "L'utilisation de MWEB téléchargera initialement ~ 600 Mo de données et peut prendre jusqu'à 30 minutes en fonction de la vitesse du réseau. Ces données initiales ne téléchargeront qu'une seule fois et seront disponibles pour tous les portefeuilles litecoin",
|
||||
"litecoin_what_is_mweb": "Qu'est-ce que MWEB?",
|
||||
"live_fee_rates": "Taux de frais en direct via l'API",
|
||||
"load_more": "Charger plus",
|
||||
"loading_your_wallet": "Chargement de votre portefeuille (wallet)",
|
||||
|
@ -393,6 +401,8 @@
|
|||
"monero_light_theme": "Thème de lumière Monero",
|
||||
"moonpay_alert_text": "Le montant doit être au moins égal à ${minAmount} ${fiatCurrency}",
|
||||
"more_options": "Plus d'options",
|
||||
"mweb_confirmed": "Confirmé MWEB",
|
||||
"mweb_unconfirmed": "Mweb non confirmé",
|
||||
"name": "Nom",
|
||||
"nano_current_rep": "Représentant actuel",
|
||||
"nano_gpt_thanks_message": "Merci d'avoir utilisé Nanogpt! N'oubliez pas de retourner au navigateur une fois votre transaction terminée!",
|
||||
|
@ -814,6 +824,7 @@
|
|||
"trusted": "de confiance",
|
||||
"tx_commit_exception_no_dust_on_change": "La transaction est rejetée avec ce montant. Avec ces pièces, vous pouvez envoyer ${min} sans changement ou ${max} qui renvoie le changement.",
|
||||
"tx_commit_failed": "La validation de la transaction a échoué. Veuillez contacter l'assistance.",
|
||||
"tx_commit_failed_no_peers": "La transaction n'a pas été diffusée, veuillez réessayer dans une seconde environ",
|
||||
"tx_invalid_input": "Vous utilisez le mauvais type d'entrée pour ce type de paiement",
|
||||
"tx_no_dust_exception": "La transaction est rejetée en envoyant un montant trop faible. Veuillez essayer d'augmenter le montant.",
|
||||
"tx_not_enough_inputs_exception": "Pas assez d'entrées disponibles. Veuillez sélectionner plus sous Control Control",
|
||||
|
|
|
@ -363,6 +363,14 @@
|
|||
"ledger_error_wrong_app": "Da fatan za a tabbata kun yi amfani da app ɗin dama akan dillalarku",
|
||||
"ledger_please_enable_bluetooth": "Da fatan za a kunna Bluetooth don gano Ledger ɗinku",
|
||||
"light_theme": "Haske",
|
||||
"litecoin_enable_mweb_sync": "Kunna binciken Mweb",
|
||||
"litecoin_mweb": "Mweb",
|
||||
"litecoin_mweb_always_scan": "Saita Mweb koyaushe",
|
||||
"litecoin_mweb_display_card": "Nuna katin Mweb",
|
||||
"litecoin_mweb_scanning": "Mweb scanning",
|
||||
"litecoin_mweb_settings": "Saitunan Mweb",
|
||||
"litecoin_mweb_warning": "Amfani da Mweb zai fara saukewa ~ 600MB na bayanai, kuma yana iya ɗaukar minti 30 dangane da saurin cibiyar sadarwa. Wannan bayanan farko zai saika saukarwa sau ɗaya kawai kuma a samu don duk wuraren shakatawa",
|
||||
"litecoin_what_is_mweb": "Menene Mweb?",
|
||||
"live_fee_rates": "Kudin Kiɗa ta API",
|
||||
"load_more": "Like more",
|
||||
"loading_your_wallet": "Ana loda walat ɗin ku",
|
||||
|
@ -393,6 +401,8 @@
|
|||
"monero_light_theme": "Jigon Hasken Monero",
|
||||
"moonpay_alert_text": "Darajar adadin dole ne ya zama fiye ko daidai da ${minAmount} ${fiatCurrency}",
|
||||
"more_options": "Ƙarin Zaɓuɓɓuka",
|
||||
"mweb_confirmed": "Tabbatar da Mweb",
|
||||
"mweb_unconfirmed": "Myconfired",
|
||||
"name": "Suna",
|
||||
"nano_current_rep": "Wakilin Yanzu",
|
||||
"nano_gpt_thanks_message": "Na gode da amfani da Nanogpt! Ka tuna da komawa zuwa mai bincike bayan ma'amalar ka ta cika!",
|
||||
|
@ -816,6 +826,7 @@
|
|||
"trusted": "Amintacce",
|
||||
"tx_commit_exception_no_dust_on_change": "An ƙi ma'amala da wannan adadin. Tare da waɗannan tsabar kudi Zaka iya aika ${min}, ba tare da canji ba ko ${max} wanda ya dawo canzawa.",
|
||||
"tx_commit_failed": "Ma'amala ya kasa. Da fatan za a tuntuɓi goyan baya.",
|
||||
"tx_commit_failed_no_peers": "Kasuwanci ya kasa watsa, don Allah sake gwadawa a cikin na biyu ko",
|
||||
"tx_invalid_input": "Kuna amfani da nau'in shigar da ba daidai ba don wannan nau'in biyan kuɗi",
|
||||
"tx_no_dust_exception": "An ƙi ma'amala ta hanyar aika adadin ƙarami. Da fatan za a gwada ƙara adadin.",
|
||||
"tx_not_enough_inputs_exception": "Bai isa ba hanyoyin da ake samu. Da fatan za selectiari a karkashin Kwarewar Coin",
|
||||
|
|
|
@ -363,6 +363,14 @@
|
|||
"ledger_error_wrong_app": "कृपया सुनिश्चित करें कि आप अपने लेजर पर सही ऐप को खोलते हैं",
|
||||
"ledger_please_enable_bluetooth": "कृपया अपने बहीखाने का पता लगाने के लिए ब्लूटूथ को सक्षम करें",
|
||||
"light_theme": "रोशनी",
|
||||
"litecoin_enable_mweb_sync": "MWEB स्कैनिंग सक्षम करें",
|
||||
"litecoin_mweb": "मावली",
|
||||
"litecoin_mweb_always_scan": "MWEB हमेशा स्कैनिंग सेट करें",
|
||||
"litecoin_mweb_display_card": "MWEB कार्ड दिखाएं",
|
||||
"litecoin_mweb_scanning": "MWEB स्कैनिंग",
|
||||
"litecoin_mweb_settings": "MWEB सेटिंग्स",
|
||||
"litecoin_mweb_warning": "MWEB का उपयोग शुरू में ~ 600MB डेटा डाउनलोड करेगा, और नेटवर्क की गति के आधार पर 30 मिनट तक का समय लग सकता है। यह प्रारंभिक डेटा केवल एक बार डाउनलोड करेगा और सभी लिटकोइन वॉलेट के लिए उपलब्ध होगा",
|
||||
"litecoin_what_is_mweb": "MWEB क्या है?",
|
||||
"live_fee_rates": "एपीआई के माध्यम से लाइव शुल्क दरें",
|
||||
"load_more": "और लोड करें",
|
||||
"loading_your_wallet": "अपना बटुआ लोड कर रहा है",
|
||||
|
@ -393,6 +401,8 @@
|
|||
"monero_light_theme": "मोनेरो लाइट थीम",
|
||||
"moonpay_alert_text": "राशि का मूल्य अधिक है या करने के लिए बराबर होना चाहिए ${minAmount} ${fiatCurrency}",
|
||||
"more_options": "और विकल्प",
|
||||
"mweb_confirmed": "MWEB की पुष्टि की",
|
||||
"mweb_unconfirmed": "अपुष्ट MWEB",
|
||||
"name": "नाम",
|
||||
"nano_current_rep": "वर्तमान प्रतिनिधि",
|
||||
"nano_gpt_thanks_message": "Nanogpt का उपयोग करने के लिए धन्यवाद! अपने लेन -देन के पूरा होने के बाद ब्राउज़र पर वापस जाना याद रखें!",
|
||||
|
@ -816,6 +826,7 @@
|
|||
"trusted": "भरोसा",
|
||||
"tx_commit_exception_no_dust_on_change": "लेनदेन को इस राशि से खारिज कर दिया जाता है। इन सिक्कों के साथ आप चेंज या ${min} के बिना ${max} को भेज सकते हैं जो परिवर्तन लौटाता है।",
|
||||
"tx_commit_failed": "लेन -देन प्रतिबद्ध विफल। कृपया संपर्क समर्थन करें।",
|
||||
"tx_commit_failed_no_peers": "लेन -देन प्रसारित करने में विफल रहा, कृपया एक या दो सेकंड में पुनः प्रयास करें",
|
||||
"tx_invalid_input": "आप इस प्रकार के भुगतान के लिए गलत इनपुट प्रकार का उपयोग कर रहे हैं",
|
||||
"tx_no_dust_exception": "लेनदेन को बहुत छोटी राशि भेजकर अस्वीकार कर दिया जाता है। कृपया राशि बढ़ाने का प्रयास करें।",
|
||||
"tx_not_enough_inputs_exception": "पर्याप्त इनपुट उपलब्ध नहीं है। कृपया सिक्का नियंत्रण के तहत अधिक चुनें",
|
||||
|
|
|
@ -363,6 +363,14 @@
|
|||
"ledger_error_wrong_app": "Obavezno obavezno otvorite pravu aplikaciju na knjizi",
|
||||
"ledger_please_enable_bluetooth": "Omogućite Bluetooth da otkrije svoju knjigu",
|
||||
"light_theme": "Svijetla",
|
||||
"litecoin_enable_mweb_sync": "Omogućite MWEB skeniranje",
|
||||
"litecoin_mweb": "MWeb",
|
||||
"litecoin_mweb_always_scan": "Postavite MWeb uvijek skeniranje",
|
||||
"litecoin_mweb_display_card": "Prikaži MWeb karticu",
|
||||
"litecoin_mweb_scanning": "MWEB skeniranje",
|
||||
"litecoin_mweb_settings": "Postavke MWEB -a",
|
||||
"litecoin_mweb_warning": "Korištenje MWEB -a u početku će preuzeti ~ 600MB podataka, a može potrajati do 30 minuta, ovisno o brzini mreže. Ovi početni podaci preuzet će samo jednom i biti dostupni za sve Litecoin novčanike",
|
||||
"litecoin_what_is_mweb": "Što je MWEB?",
|
||||
"live_fee_rates": "Stope naknada uživo putem API -ja",
|
||||
"load_more": "Učitaj više",
|
||||
"loading_your_wallet": "Novčanik se učitava",
|
||||
|
@ -393,6 +401,8 @@
|
|||
"monero_light_theme": "Monero lagana tema",
|
||||
"moonpay_alert_text": "Vrijednost iznosa mora biti veća ili jednaka ${minAmount} ${fiatCurrency}",
|
||||
"more_options": "Više opcija",
|
||||
"mweb_confirmed": "Potvrđen MWeb",
|
||||
"mweb_unconfirmed": "Nepotvrđeni mWeb",
|
||||
"name": "Ime",
|
||||
"nano_current_rep": "Trenutni predstavnik",
|
||||
"nano_gpt_thanks_message": "Hvala što ste koristili nanogpt! Ne zaboravite da se vratite u preglednik nakon što vam se transakcija završi!",
|
||||
|
@ -814,6 +824,7 @@
|
|||
"trusted": "vjerovao",
|
||||
"tx_commit_exception_no_dust_on_change": "Transakcija se odbija s tim iznosom. Pomoću ovih kovanica možete poslati ${min} bez promjene ili ${max} koja vraća promjenu.",
|
||||
"tx_commit_failed": "Obveza transakcije nije uspjela. Molimo kontaktirajte podršku.",
|
||||
"tx_commit_failed_no_peers": "Transakcija nije uspjela emitirati, pokušajte ponovo u sekundi ili tako",
|
||||
"tx_invalid_input": "Koristite pogrešnu vrstu ulaza za ovu vrstu plaćanja",
|
||||
"tx_no_dust_exception": "Transakcija se odbija slanjem iznosa premalo. Pokušajte povećati iznos.",
|
||||
"tx_not_enough_inputs_exception": "Nema dovoljno unosa. Molimo odaberite više pod kontrolom novčića",
|
||||
|
|
|
@ -393,6 +393,8 @@
|
|||
"monero_light_theme": "Monero պայծառ տեսք",
|
||||
"moonpay_alert_text": "Գումարի արժեքը պետք է լինի հավասար կամ ավելի քան ${minAmount} ${fiatCurrency}",
|
||||
"more_options": "Այլ տարբերակներ",
|
||||
"mweb_confirmed": "Հաստատված MWEB",
|
||||
"mweb_unconfirmed": "Չկարգավորված Mweb",
|
||||
"name": "Անուն",
|
||||
"nano_current_rep": "Ընթացիկ ներկայացուցիչ",
|
||||
"nano_gpt_thanks_message": "Շնորհակալություն NanoGPT-ն օգտագործելու համար: Հիշեք վերադառնալ դիտարկիչ ձեր փոխանցումն ավարտելուց հետո",
|
||||
|
@ -814,6 +816,7 @@
|
|||
"trusted": "Վստահելի",
|
||||
"tx_commit_exception_no_dust_on_change": "Փոխանցումը մերժվել է այս գումարով: Այս արժույթներով կարող եք ուղարկել ${min} առանց փոփոխության կամ ${max} որը վերադարձնում է փոփոխությունը",
|
||||
"tx_commit_failed": "Փոխանցումը ձախողվել է: Խնդրում ենք դիմել աջակցությանը",
|
||||
"tx_commit_failed_no_peers": "Գործարքը չի հաջողվել հեռարձակել, խնդրում ենք կրկին փորձել մեկ վայրկյանում",
|
||||
"tx_invalid_input": "Դուք օգտագործում եք սխալ մուտքային տիպ այս տեսակի վճարման համար",
|
||||
"tx_no_dust_exception": "Փոխանցումը մերժվել է շատ փոքր գումարով: Խնդրում ենք փորձել ավելացնել գումարը",
|
||||
"tx_not_enough_inputs_exception": "Չկան բավարար մուտքեր: Խնդրում ենք ընտրել ավելին Coin Control֊ում",
|
||||
|
|
|
@ -363,6 +363,14 @@
|
|||
"ledger_error_wrong_app": "Pastikan Anda membuka aplikasi yang tepat di buku besar Anda",
|
||||
"ledger_please_enable_bluetooth": "Harap aktifkan Bluetooth untuk mendeteksi buku besar Anda",
|
||||
"light_theme": "Terang",
|
||||
"litecoin_enable_mweb_sync": "Aktifkan pemindaian MWEB",
|
||||
"litecoin_mweb": "Mweb",
|
||||
"litecoin_mweb_always_scan": "Atur mWeb selalu memindai",
|
||||
"litecoin_mweb_display_card": "Tunjukkan kartu mWeb",
|
||||
"litecoin_mweb_scanning": "Pemindaian MWEB",
|
||||
"litecoin_mweb_settings": "Pengaturan MWEB",
|
||||
"litecoin_mweb_warning": "Menggunakan MWEB pada awalnya akan mengunduh ~ 600MB data, dan dapat memakan waktu hingga 30 menit tergantung pada kecepatan jaringan. Data awal ini hanya akan mengunduh sekali dan tersedia untuk semua dompet litecoin",
|
||||
"litecoin_what_is_mweb": "Apa itu MWEB?",
|
||||
"live_fee_rates": "Tarif biaya langsung melalui API",
|
||||
"load_more": "Muat lebih banyak",
|
||||
"loading_your_wallet": "Memuat dompet Anda",
|
||||
|
@ -393,6 +401,8 @@
|
|||
"monero_light_theme": "Tema Cahaya Monero",
|
||||
"moonpay_alert_text": "Nilai jumlah harus lebih atau sama dengan ${minAmount} ${fiatCurrency}",
|
||||
"more_options": "Opsi Lainnya",
|
||||
"mweb_confirmed": "Mengkonfirmasi mWeb",
|
||||
"mweb_unconfirmed": "MWEB yang belum dikonfirmasi",
|
||||
"name": "Nama",
|
||||
"nano_current_rep": "Perwakilan saat ini",
|
||||
"nano_gpt_thanks_message": "Terima kasih telah menggunakan Nanogpt! Ingatlah untuk kembali ke browser setelah transaksi Anda selesai!",
|
||||
|
@ -817,6 +827,7 @@
|
|||
"trusted": "Dipercayai",
|
||||
"tx_commit_exception_no_dust_on_change": "Transaksi ditolak dengan jumlah ini. Dengan koin ini Anda dapat mengirim ${min} tanpa perubahan atau ${max} yang mengembalikan perubahan.",
|
||||
"tx_commit_failed": "Transaksi Gagal. Silakan hubungi Dukungan.",
|
||||
"tx_commit_failed_no_peers": "Transaksi gagal untuk disiarkan, silakan coba lagi sebentar lagi",
|
||||
"tx_invalid_input": "Anda menggunakan jenis input yang salah untuk jenis pembayaran ini",
|
||||
"tx_no_dust_exception": "Transaksi ditolak dengan mengirimkan jumlah yang terlalu kecil. Silakan coba tingkatkan jumlahnya.",
|
||||
"tx_not_enough_inputs_exception": "Tidak cukup input yang tersedia. Pilih lebih banyak lagi di bawah Kontrol Koin",
|
||||
|
|
|
@ -364,6 +364,14 @@
|
|||
"ledger_error_wrong_app": "Assicurati di aprire l'app giusta sul libro mastro",
|
||||
"ledger_please_enable_bluetooth": "Si prega di consentire al Bluetooth di rilevare il libro mastro",
|
||||
"light_theme": "Bianco",
|
||||
"litecoin_enable_mweb_sync": "Abilita la scansione MWeb",
|
||||
"litecoin_mweb": "MWeb",
|
||||
"litecoin_mweb_always_scan": "Imposta MWeb per scansionare sempre",
|
||||
"litecoin_mweb_display_card": "Mostra la scheda MWeb",
|
||||
"litecoin_mweb_scanning": "Scansione MWeb",
|
||||
"litecoin_mweb_settings": "Impostazioni MWeb",
|
||||
"litecoin_mweb_warning": "L'uso di MWeb inizialmente scaricherà ~ 600 MB di dati e potrebbe richiedere fino a 30 minuti a seconda della velocità di rete. Questi dati iniziali scaricheranno solo una volta e saranno disponibili per tutti i portafogli Litecoin",
|
||||
"litecoin_what_is_mweb": "Cos'è MWeb?",
|
||||
"live_fee_rates": "Tariffe delle commissioni dal vivo tramite API",
|
||||
"load_more": "Carica di più",
|
||||
"loading_your_wallet": "Caricamento portafoglio",
|
||||
|
@ -394,6 +402,8 @@
|
|||
"monero_light_theme": "Tema leggero Monero",
|
||||
"moonpay_alert_text": "Il valore dell'importo deve essere maggiore o uguale a ${minAmount} ${fiatCurrency}",
|
||||
"more_options": "Altre opzioni",
|
||||
"mweb_confirmed": "MWeb confermato",
|
||||
"mweb_unconfirmed": "MWeb non confermato",
|
||||
"name": "Nome",
|
||||
"nano_current_rep": "Rappresentante attuale",
|
||||
"nano_gpt_thanks_message": "Grazie per aver usato il nanogpt! Ricorda di tornare al browser dopo il completamento della transazione!",
|
||||
|
@ -816,6 +826,7 @@
|
|||
"trusted": "di fiducia",
|
||||
"tx_commit_exception_no_dust_on_change": "La transazione viene respinta con questo importo. Con queste monete è possibile inviare ${min} senza modifiche o ${max} che restituisce il cambiamento.",
|
||||
"tx_commit_failed": "Commit di transazione non riuscita. Si prega di contattare il supporto.",
|
||||
"tx_commit_failed_no_peers": "La transazione non è riuscita a trasmettere, riprovare in un secondo o giù di lì",
|
||||
"tx_invalid_input": "Stai usando il tipo di input sbagliato per questo tipo di pagamento",
|
||||
"tx_no_dust_exception": "La transazione viene respinta inviando un importo troppo piccolo. Per favore, prova ad aumentare l'importo.",
|
||||
"tx_not_enough_inputs_exception": "Input non sufficienti disponibili. Seleziona di più sotto il controllo delle monete",
|
||||
|
|
|
@ -364,6 +364,14 @@
|
|||
"ledger_error_wrong_app": "元帳に適切なアプリを開始するようにしてください",
|
||||
"ledger_please_enable_bluetooth": "Bluetoothが元帳を検出できるようにしてください",
|
||||
"light_theme": "光",
|
||||
"litecoin_enable_mweb_sync": "MWEBスキャンを有効にします",
|
||||
"litecoin_mweb": "mweb",
|
||||
"litecoin_mweb_always_scan": "MWEBを常にスキャンします",
|
||||
"litecoin_mweb_display_card": "MWEBカードを表示します",
|
||||
"litecoin_mweb_scanning": "MWEBスキャン",
|
||||
"litecoin_mweb_settings": "MWEB設定",
|
||||
"litecoin_mweb_warning": "MWEBを使用すると、最初は〜600MBのデータをダウンロードし、ネットワーク速度に応じて最大30分かかる場合があります。この最初のデータは一度だけダウンロードされ、すべてのLitecoinウォレットで利用可能になります",
|
||||
"litecoin_what_is_mweb": "MWEBとは何ですか?",
|
||||
"live_fee_rates": "API経由のライブ料金",
|
||||
"load_more": "もっと読み込む",
|
||||
"loading_your_wallet": "ウォレットをロードしています",
|
||||
|
@ -394,6 +402,8 @@
|
|||
"monero_light_theme": "モネロ ライト テーマ",
|
||||
"moonpay_alert_text": "金額の値は以上でなければなりません ${minAmount} ${fiatCurrency}",
|
||||
"more_options": "その他のオプション",
|
||||
"mweb_confirmed": "確認されたMWEB",
|
||||
"mweb_unconfirmed": "未確認のMWEB",
|
||||
"name": "名前",
|
||||
"nano_current_rep": "現在の代表",
|
||||
"nano_gpt_thanks_message": "NanoGptを使用してくれてありがとう!トランザクションが完了したら、ブラウザに戻ることを忘れないでください!",
|
||||
|
@ -815,6 +825,7 @@
|
|||
"trusted": "信頼できる",
|
||||
"tx_commit_exception_no_dust_on_change": "この金額ではトランザクションは拒否されます。 これらのコインを使用すると、おつりなしの ${min} またはおつりを返す ${max} を送信できます。",
|
||||
"tx_commit_failed": "トランザクションコミットは失敗しました。サポートに連絡してください。",
|
||||
"tx_commit_failed_no_peers": "トランザクションはブロードキャストに失敗しました。一瞬かそこらで再試行してください",
|
||||
"tx_invalid_input": "このタイプの支払いに間違った入力タイプを使用しています",
|
||||
"tx_no_dust_exception": "トランザクションは、小さすぎる金額を送信することにより拒否されます。量を増やしてみてください。",
|
||||
"tx_not_enough_inputs_exception": "利用可能な入力が十分ではありません。コイン制御下でもっと選択してください",
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue