Cw 565 sign messages (#1378)

* version bump to 3.13.9, auth working on mac * bump flutter version in workflow file * workflow fix * test fix * downgrade flutter version * test fix * test fix * update gradle version * start working on ui for message signing * updates * sign working for a few wallet types * updates & verification for electrum currencies * nano support * sign/verify working on eth, bitcoin broken * update translations * Implement Verify Message for Monero * save [skip ci] * pub key extraction working * fixes for electrum signing * verify working for solana! * electrum still not working :( [skip ci] * electrum messages working! * fixes for updated dart version, localization file updates * remove accidental inclusion * missed some unimplemented throws * Update res/values/strings_de.arb Co-authored-by: Konstantin Ullrich <konstantinullrich12@gmail.com> * Apply suggestions from code review Co-authored-by: Konstantin Ullrich <konstantinullrich12@gmail.com> * review suggestions and updates [skip ci] * [skip ci] add polygon * [skip ci] merge mac-auth/update version * fix litecoin * bio auth mac fix * remove comment and change duration from 2 to 0 * cherry pick previous changes * litecoin fixes, sign form fixes, use new walletAddressPicker * support accounts * verify messages working for monero * working sign and verify messages for nano * electrum signing working [skip ci] * additional nano fixes * update translations * attempt to decode signatures with base64 * workaround for secure storage bug on mac * bump version to 3.19.5 (because breez will need this version anyways) * some code cleanup * some changess didn't get saved * just documenting the issue [skip ci] * undo accidental removal + minor code cleanup * merge conflicts * merge fixes [skip ci] * add tron support * [wip] fixing * remove duplicate references to electrum path for maintainability * fixes * minor fix * fixes * undo debug comment * update migration for all electrum based wallets * hotfixes * copy over the rest of the fixes * minor code cleanup [skip ci] * updates * electrum signing workinggit statusgit statusgit statusgit status! * copy same fixes for litecoin * litecoin fixes * add v to litecoin signatures * fix dependencies * fix bitcoin_base version * merge fix * dep override * fix conflicts with main * trial fix for android build * fixes * fix * dep fix, should build * fix signing for bitcoin cash * [skip ci] minor code cleanup * [skip ci] minor code cleanup 2 * forgot wonero, various other fixes * more fixes * fix solana (untested) --------- Co-authored-by: Konstantin Ullrich <konstantinullrich12@gmail.com> Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>

Matthew Fosse committed Aug 17, 2024 at 19:10 UTC 83ef61e92877dfaa7b506df2982d983162c8f0cc
65 files changed +1478 -270
cw_bitcoin/lib/electrum_wallet.dart
+67 -6
@@ -42,6 +42,7 @@ import 'package:flutter/foundation.dart';
42 import 'package:hive/hive.dart';
43 import 'package:mobx/mobx.dart';
44 import 'package:rxdart/subjects.dart';
45 +import 'package:http/http.dart' as http;
46 import 'package:sp_scanner/sp_scanner.dart';
47
48 part 'electrum_wallet.g.dart';
@@ -132,6 +133,7 @@ abstract class ElectrumWalletBase
133 final String? _mnemonic;
134
135 Bip32Slip10Secp256k1 get hd => accountHD.childKey(Bip32KeyIndex(0));
136 + Bip32Slip10Secp256k1 get sideHd => accountHD.childKey(Bip32KeyIndex(1));
137
138 final EncryptionFileUtils encryptionFileUtils;
139 final String? passphrase;
@@ -591,7 +593,7 @@ abstract class ElectrumWalletBase
593 }
594
595 final derivationPath =
594 - "${_hardenedDerivationPath(walletInfo.derivationInfo?.derivationPath ?? "m/0'")}"
596 + "${_hardenedDerivationPath(walletInfo.derivationInfo?.derivationPath ?? electrum_path)}"
597 "/${utx.bitcoinAddressRecord.isHidden ? "1" : "0"}"
598 "/${utx.bitcoinAddressRecord.index}";
599 publicKeys[address.pubKeyHash()] = PublicKeyWithDerivationPath(pubKeyHex, derivationPath);
@@ -1869,11 +1871,70 @@ abstract class ElectrumWalletBase
1871 ? walletAddresses.allAddresses.firstWhere((element) => element.address == address).index
1872 : null;
1873 final HD = index == null ? hd : hd.childKey(Bip32KeyIndex(index));
1872 - final priv = ECPrivate.fromWif(
1873 - WifEncoder.encode(HD.privateKey.raw, netVer: network.wifNetVer),
1874 - netVersion: network.wifNetVer,
1875 - );
1876 - return priv.signMessage(StringUtils.encode(message));
1874 + final priv = ECPrivate.fromHex(HD.privateKey.privKey.toHex());
1875 +
1876 + String messagePrefix = '\x18Bitcoin Signed Message:\n';
1877 + final hexEncoded = priv.signMessage(utf8.encode(message), messagePrefix: messagePrefix);
1878 + final decodedSig = hex.decode(hexEncoded);
1879 + return base64Encode(decodedSig);
1880 + }
1881 +
1882 + @override
1883 + Future<bool> verifyMessage(String message, String signature, {String? address = null}) async {
1884 + if (address == null) {
1885 + return false;
1886 + }
1887 +
1888 + List<int> sigDecodedBytes = [];
1889 +
1890 + if (signature.endsWith('=')) {
1891 + sigDecodedBytes = base64.decode(signature);
1892 + } else {
1893 + sigDecodedBytes = hex.decode(signature);
1894 + }
1895 +
1896 + if (sigDecodedBytes.length != 64 && sigDecodedBytes.length != 65) {
1897 + throw ArgumentException(
1898 + "signature must be 64 bytes without recover-id or 65 bytes with recover-id");
1899 + }
1900 +
1901 + String messagePrefix = '\x18Bitcoin Signed Message:\n';
1902 + final messageHash = QuickCrypto.sha256Hash(
1903 + BitcoinSignerUtils.magicMessage(utf8.encode(message), messagePrefix));
1904 +
1905 + List<int> correctSignature =
1906 + sigDecodedBytes.length == 65 ? sigDecodedBytes.sublist(1) : List.from(sigDecodedBytes);
1907 + List<int> rBytes = correctSignature.sublist(0, 32);
1908 + List<int> sBytes = correctSignature.sublist(32);
1909 + final sig = ECDSASignature(BigintUtils.fromBytes(rBytes), BigintUtils.fromBytes(sBytes));
1910 +
1911 + List<int> possibleRecoverIds = [0, 1];
1912 +
1913 + final baseAddress = addressTypeFromStr(address, network);
1914 +
1915 + for (int recoveryId in possibleRecoverIds) {
1916 + final pubKey = sig.recoverPublicKey(messageHash, Curves.generatorSecp256k1, recoveryId);
1917 +
1918 + final recoveredPub = ECPublic.fromBytes(pubKey!.toBytes());
1919 +
1920 + String? recoveredAddress;
1921 +
1922 + if (baseAddress is P2pkAddress) {
1923 + recoveredAddress = recoveredPub.toP2pkAddress().toAddress(network);
1924 + } else if (baseAddress is P2pkhAddress) {
1925 + recoveredAddress = recoveredPub.toP2pkhAddress().toAddress(network);
1926 + } else if (baseAddress is P2wshAddress) {
1927 + recoveredAddress = recoveredPub.toP2wshAddress().toAddress(network);
1928 + } else if (baseAddress is P2wpkhAddress) {
1929 + recoveredAddress = recoveredPub.toP2wpkhAddress().toAddress(network);
1930 + }
1931 +
1932 + if (recoveredAddress == address) {
1933 + return true;
1934 + }
1935 + }
1936 +
1937 + return false;
1938 }
1939
1940 Future<void> _setInitialHeight() async {
cw_bitcoin/lib/litecoin_wallet.dart
+130 -1
@@ -1,6 +1,9 @@
1 -import 'package:bip39/bip39.dart' as bip39;
1 +import 'dart:convert';
2 +
3 import 'package:bitcoin_base/bitcoin_base.dart';
4 import 'package:blockchain_utils/blockchain_utils.dart';
5 +import 'package:blockchain_utils/signer/ecdsa_signing_key.dart';
6 +import 'package:bip39/bip39.dart' as bip39;
7 import 'package:cw_bitcoin/bitcoin_address_record.dart';
8 import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
9 import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
@@ -17,6 +20,9 @@ import 'package:cw_core/wallet_keys_file.dart';
20 import 'package:flutter/foundation.dart';
21 import 'package:hive/hive.dart';
22 import 'package:mobx/mobx.dart';
23 +import 'package:bitcoin_base/src/crypto/keypair/sign_utils.dart';
24 +import 'package:pointycastle/ecc/api.dart';
25 +import 'package:pointycastle/ecc/curves/secp256k1.dart';
26
27 part 'litecoin_wallet.g.dart';
28
@@ -167,4 +173,127 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
173
174 return 0;
175 }
176 +
177 + @override
178 + Future<String> signMessage(String message, {String? address = null}) async {
179 + final index = address != null
180 + ? walletAddresses.allAddresses.firstWhere((element) => element.address == address).index
181 + : null;
182 + final HD = index == null ? hd : hd.childKey(Bip32KeyIndex(index));
183 + final priv = ECPrivate.fromHex(HD.privateKey.privKey.toHex());
184 +
185 + final privateKey = ECDSAPrivateKey.fromBytes(
186 + priv.toBytes(),
187 + Curves.generatorSecp256k1,
188 + );
189 +
190 + final signature =
191 + signLitecoinMessage(utf8.encode(message), privateKey: privateKey, bipPrive: priv.prive);
192 +
193 + return base64Encode(signature);
194 + }
195 +
196 + List<int> _magicPrefix(List<int> message, List<int> messagePrefix) {
197 + final encodeLength = IntUtils.encodeVarint(message.length);
198 +
199 + return [...messagePrefix, ...encodeLength, ...message];
200 + }
201 +
202 + List<int> signLitecoinMessage(List<int> message,
203 + {required ECDSAPrivateKey privateKey, required Bip32PrivateKey bipPrive}) {
204 + String messagePrefix = '\x19Litecoin Signed Message:\n';
205 + final messageHash = QuickCrypto.sha256Hash(magicMessage(message, messagePrefix));
206 + final signingKey = EcdsaSigningKey(privateKey);
207 + ECDSASignature ecdsaSign =
208 + signingKey.signDigestDeterminstic(digest: messageHash, hashFunc: () => SHA256());
209 + final n = Curves.generatorSecp256k1.order! >> 1;
210 + BigInt newS;
211 + if (ecdsaSign.s.compareTo(n) > 0) {
212 + newS = Curves.generatorSecp256k1.order! - ecdsaSign.s;
213 + } else {
214 + newS = ecdsaSign.s;
215 + }
216 + final rawSig = ECDSASignature(ecdsaSign.r, newS);
217 + final rawSigBytes = rawSig.toBytes(BitcoinSignerUtils.baselen);
218 +
219 + final pub = bipPrive.publicKey;
220 + final ECDomainParameters curve = ECCurve_secp256k1();
221 + final point = curve.curve.decodePoint(pub.point.toBytes());
222 +
223 + final rawSigEc = ECSignature(rawSig.r, rawSig.s);
224 +
225 + final recId = SignUtils.findRecoveryId(
226 + SignUtils.getHexString(messageHash, offset: 0, length: messageHash.length),
227 + rawSigEc,
228 + Uint8List.fromList(pub.uncompressed),
229 + );
230 +
231 + final v = recId + 27 + (point!.isCompressed ? 4 : 0);
232 +
233 + final combined = Uint8List.fromList([v, ...rawSigBytes]);
234 +
235 + return combined;
236 + }
237 +
238 + List<int> magicMessage(List<int> message, String messagePrefix) {
239 + final prefixBytes = StringUtils.encode(messagePrefix);
240 + final magic = _magicPrefix(message, prefixBytes);
241 + return QuickCrypto.sha256Hash(magic);
242 + }
243 +
244 + @override
245 + Future<bool> verifyMessage(String message, String signature, {String? address = null}) async {
246 + if (address == null) {
247 + return false;
248 + }
249 +
250 + List<int> sigDecodedBytes = [];
251 +
252 + if (signature.endsWith('=')) {
253 + sigDecodedBytes = base64.decode(signature);
254 + } else {
255 + sigDecodedBytes = hex.decode(signature);
256 + }
257 +
258 + if (sigDecodedBytes.length != 64 && sigDecodedBytes.length != 65) {
259 + throw ArgumentException(
260 + "litecoin signature must be 64 bytes without recover-id or 65 bytes with recover-id");
261 + }
262 +
263 + String messagePrefix = '\x19Litecoin Signed Message:\n';
264 + final messageHash = QuickCrypto.sha256Hash(magicMessage(utf8.encode(message), messagePrefix));
265 +
266 + List<int> correctSignature =
267 + sigDecodedBytes.length == 65 ? sigDecodedBytes.sublist(1) : List.from(sigDecodedBytes);
268 + List<int> rBytes = correctSignature.sublist(0, 32);
269 + List<int> sBytes = correctSignature.sublist(32);
270 + final sig = ECDSASignature(BigintUtils.fromBytes(rBytes), BigintUtils.fromBytes(sBytes));
271 +
272 + List<int> possibleRecoverIds = [0, 1];
273 +
274 + final baseAddress = addressTypeFromStr(address, network);
275 +
276 + for (int recoveryId in possibleRecoverIds) {
277 + final pubKey = sig.recoverPublicKey(messageHash, Curves.generatorSecp256k1, recoveryId);
278 + final recoveredPub = ECPublic.fromBytes(pubKey!.toBytes());
279 +
280 + String? recoveredAddress;
281 +
282 + if (baseAddress is P2pkAddress) {
283 + recoveredAddress = recoveredPub.toP2pkAddress().toAddress(network);
284 + } else if (baseAddress is P2pkhAddress) {
285 + recoveredAddress = recoveredPub.toP2pkhAddress().toAddress(network);
286 + } else if (baseAddress is P2wshAddress) {
287 + recoveredAddress = recoveredPub.toP2wshAddress().toAddress(network);
288 + } else if (baseAddress is P2wpkhAddress) {
289 + recoveredAddress = recoveredPub.toP2wpkhAddress().toAddress(network);
290 + }
291 +
292 + if (recoveredAddress == address) {
293 + return true;
294 + }
295 + }
296 +
297 + return false;
298 + }
299 }
cw_bitcoin/pubspec.yaml
+4 -4
@@ -25,10 +25,6 @@ dependencies:
25 ref: Add-Support-For-OP-Return-data
26 rxdart: ^0.27.5
27 cryptography: ^2.0.5
28 - bitcoin_base:
29 - git:
30 - url: https://github.com/cake-tech/bitcoin_base
31 - ref: cake-update-v4
28 blockchain_utils:
29 git:
30 url: https://github.com/cake-tech/blockchain_utils
@@ -57,6 +53,10 @@ dependency_overrides:
53 url: https://github.com/cake-tech/ledger-flutter.git
54 ref: cake-v3
55 watcher: ^1.1.0
56 + bitcoin_base:
57 + git:
58 + url: https://github.com/cake-tech/bitcoin_base
59 + ref: cake-update-v5
60
61 # For information on the generic Dart part of this file, see the
62 # following page: https://dart.dev/tools/pub/pubspec
cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart
+6 -5
@@ -202,11 +202,12 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
202
203 @override
204 Future<String> signMessage(String message, {String? address = null}) async {
205 - final index = address != null
206 - ? walletAddresses.allAddresses
207 - .firstWhere((element) => element.address == AddressUtils.toLegacyAddress(address))
208 - .index
209 - : null;
205 + int? index;
206 + try {
207 + index = address != null
208 + ? walletAddresses.allAddresses.firstWhere((element) => element.address == address).index
209 + : null;
210 + } catch (_) {}
211 final HD = index == null ? hd : hd.childKey(Bip32KeyIndex(index));
212 final priv = ECPrivate.fromWif(
213 WifEncoder.encode(HD.privateKey.raw, netVer: network.wifNetVer),
cw_bitcoin_cash/pubspec.yaml
+4 -4
@@ -25,10 +25,6 @@ dependencies:
25 git:
26 url: https://github.com/cake-tech/bitbox-flutter.git
27 ref: Add-Support-For-OP-Return-data
28 - bitcoin_base:
29 - git:
30 - url: https://github.com/cake-tech/bitcoin_base
31 - ref: cake-update-v4
28 blockchain_utils:
29 git:
30 url: https://github.com/cake-tech/blockchain_utils
@@ -43,6 +39,10 @@ dev_dependencies:
39
40 dependency_overrides:
41 watcher: ^1.1.0
42 + bitcoin_base:
43 + git:
44 + url: https://github.com/cake-tech/bitcoin_base
45 + ref: cake-update-v5
46
47 # For information on the generic Dart part of this file, see the
48 # following page: https://dart.dev/tools/pub/pubspec
cw_core/lib/wallet_base.dart
+3 -2
@@ -69,7 +69,6 @@ abstract class WalletBase<BalanceType extends Balance, HistoryType extends Trans
69
70 int calculateEstimatedFee(TransactionPriority priority, int? amount);
71
72 -
72 // void fetchTransactionsAsync(
73 // void Function(TransactionType transaction) onTransactionLoaded,
74 // {void Function() onFinished});
@@ -92,7 +91,9 @@ abstract class WalletBase<BalanceType extends Balance, HistoryType extends Trans
91
92 Future<void> renameWalletFiles(String newWalletName);
93
95 - Future<String> signMessage(String message, {String? address = null}) => throw UnimplementedError();
94 + Future<String> signMessage(String message, {String? address = null});
95 +
96 + Future<bool> verifyMessage(String message, String signature, {String? address = null});
97
98 bool? isTestnet;
99 }
cw_evm/lib/evm_chain_wallet.dart
+17 -3
@@ -35,6 +35,7 @@ import 'package:mobx/mobx.dart';
35 import 'package:shared_preferences/shared_preferences.dart';
36 import 'package:web3dart/crypto.dart';
37 import 'package:web3dart/web3dart.dart';
38 +import 'package:eth_sig_util/eth_sig_util.dart';
39
40 import 'evm_chain_transaction_info.dart';
41 import 'evm_erc20_balance.dart';
@@ -500,7 +501,7 @@ abstract class EVMChainWalletBase
501 }
502
503 final methodSignature =
503 - transactionInput.length >= 10 ? transactionInput.substring(0, 10) : null;
504 + transactionInput.length >= 10 ? transactionInput.substring(0, 10) : null;
505
506 return methodSignatureToType[methodSignature];
507 }
@@ -692,8 +693,21 @@ abstract class EVMChainWalletBase
693 }
694
695 @override
695 - Future<String> signMessage(String message, {String? address}) async =>
696 - bytesToHex(await _evmChainPrivateKey.signPersonalMessage(ascii.encode(message)));
696 + Future<String> signMessage(String message, {String? address}) async {
697 + return bytesToHex(await _evmChainPrivateKey.signPersonalMessage(ascii.encode(message)));
698 + }
699 +
700 + @override
701 + Future<bool> verifyMessage(String message, String signature, {String? address}) async {
702 + if (address == null) {
703 + return false;
704 + }
705 + final recoveredAddress = EthSigUtil.recoverPersonalSignature(
706 + message: ascii.encode(message),
707 + signature: signature,
708 + );
709 + return recoveredAddress.toUpperCase() == address.toUpperCase();
710 + }
711
712 Web3Client? getWeb3Client() => _client.getWeb3Client();
713
cw_evm/pubspec.yaml
+2
@@ -13,6 +13,8 @@ dependencies:
13 flutter:
14 sdk: flutter
15 web3dart: ^2.7.1
16 + eth_sig_util: ^0.0.9
17 + erc20: ^1.0.1
18 bip39: ^1.0.6
19 bip32: ^2.0.0
20 hex: ^0.2.0
cw_monero/lib/api/wallet.dart
+4
@@ -316,3 +316,7 @@ Future<bool> trustedDaemon() async => monero.Wallet_trustedDaemon(wptr!);
316 String signMessage(String message, {String address = ""}) {
317 return monero.Wallet_signMessage(wptr!, message: message, address: address);
318 }
319 +
320 +bool verifyMessage(String message, String address, String signature) {
321 + return monero.Wallet_verifySignedMessage(wptr!, message: message, address: address, signature: signature);
322 +}
\ No newline at end of file
cw_monero/lib/monero_wallet.dart
+8
@@ -783,4 +783,12 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
783 final useAddress = address ?? "";
784 return monero_wallet.signMessage(message, address: useAddress);
785 }
786 +
787 + @override
788 + Future<bool> verifyMessage(String message, String signature, {String? address = null}) async {
789 + if (address == null) return false;
790 +
791 + return monero_wallet.verifyMessage(message, address, signature);
792 + }
793 +
794 }
cw_nano/lib/nano_block_info_response.dart new
+37
@@ -0,0 +1,37 @@
1 +class BlockContentsResponse {
2 + String type;
3 + String account;
4 + String previous;
5 + String representative;
6 + String balance;
7 + String link;
8 + String linkAsAccount;
9 + String signature;
10 + String work;
11 +
12 + BlockContentsResponse({
13 + required this.type,
14 + required this.account,
15 + required this.previous,
16 + required this.representative,
17 + required this.balance,
18 + required this.link,
19 + required this.linkAsAccount,
20 + required this.signature,
21 + required this.work,
22 + });
23 +
24 + factory BlockContentsResponse.fromJson(Map<String, dynamic> json) {
25 + return BlockContentsResponse(
26 + type: json['type'] as String,
27 + account: json['account'] as String,
28 + previous: json['previous'] as String,
29 + representative: json['representative'] as String,
30 + balance: json['balance'] as String,
31 + link: json['link'] as String,
32 + linkAsAccount: json['link_as_account'] as String,
33 + signature: json['signature'] as String,
34 + work: json['work'] as String,
35 + );
36 + }
37 +}
cw_nano/lib/nano_client.dart
+73 -14
@@ -2,11 +2,11 @@ import 'dart:async';
2 import 'dart:convert';
3
4 import 'package:cw_core/nano_account_info_response.dart';
5 +import 'package:cw_nano/nano_block_info_response.dart';
6 import 'package:cw_core/n2_node.dart';
7 import 'package:cw_nano/nano_balance.dart';
8 import 'package:cw_nano/nano_transaction_model.dart';
9 import 'package:http/http.dart' as http;
9 -import 'package:nanodart/nanodart.dart';
10 import 'package:cw_core/node.dart';
11 import 'package:nanoutil/nanoutil.dart';
12 import 'package:shared_preferences/shared_preferences.dart';
@@ -111,6 +111,27 @@ class NanoClient {
111 }
112 }
113
114 + Future<BlockContentsResponse?> getBlockContents(String block) async {
115 + try {
116 + final response = await http.post(
117 + _node!.uri,
118 + headers: CAKE_HEADERS,
119 + body: jsonEncode(
120 + {
121 + "action": "block_info",
122 + "json_block": "true",
123 + "hash": block,
124 + },
125 + ),
126 + );
127 + final data = await jsonDecode(response.body);
128 + return BlockContentsResponse.fromJson(data["contents"] as Map<String, dynamic>);
129 + } catch (e) {
130 + print("error while getting block info $e");
131 + return null;
132 + }
133 + }
134 +
135 Future<String> changeRep({
136 required String privateKey,
137 required String repAddress,
@@ -135,8 +156,8 @@ class NanoClient {
156 };
157
158 // sign the change block:
138 - final String hash = NanoBlocks.computeStateHash(
139 - NanoAccountType.NANO,
159 + final String hash = NanoSignatures.computeStateHash(
160 + NanoBasedCurrency.NANO,
161 changeBlock["account"]!,
162 changeBlock["previous"]!,
163 changeBlock["representative"]!,
@@ -248,7 +269,7 @@ class NanoClient {
269 }
270 final String representative = infoResponse.representative;
271 // link = destination address:
251 - final String link = NanoAccounts.extractPublicKey(destinationAddress);
272 + final String link = NanoDerivations.addressToPublicKey(destinationAddress);
273 final String linkAsAccount = destinationAddress;
274
275 // construct the send block:
@@ -262,8 +283,8 @@ class NanoClient {
283 };
284
285 // sign the send block:
265 - final String hash = NanoBlocks.computeStateHash(
266 - NanoAccountType.NANO,
286 + final String hash = NanoSignatures.computeStateHash(
287 + NanoBasedCurrency.NANO,
288 sendBlock["account"]!,
289 sendBlock["previous"]!,
290 sendBlock["representative"]!,
@@ -285,7 +306,6 @@ class NanoClient {
306
307 Future<void> receiveBlock({
308 required String blockHash,
288 - required String source,
309 required String amountRaw,
310 required String destinationAddress,
311 required String privateKey,
@@ -310,15 +330,56 @@ class NanoClient {
330 representative = infoData.representative;
331 }
332
333 + if ((BigInt.tryParse(amountRaw) ?? BigInt.zero) <= BigInt.zero) {
334 + throw Exception("amountRaw must be greater than zero");
335 + }
336 +
337 + BlockContentsResponse? frontierContents;
338 +
339 + if (!openBlock) {
340 + // get the block info of the frontier block:
341 + frontierContents = await getBlockContents(frontier);
342 +
343 + if (frontierContents == null) {
344 + throw Exception("error while getting frontier block info");
345 + }
346 +
347 + final String frontierHash = NanoSignatures.computeStateHash(
348 + NanoBasedCurrency.NANO,
349 + frontierContents.account,
350 + frontierContents.previous,
351 + frontierContents.representative,
352 + BigInt.parse(frontierContents.balance),
353 + frontierContents.link,
354 + );
355 +
356 + bool valid = await NanoSignatures.verify(
357 + frontierHash,
358 + frontierContents.signature,
359 + destinationAddress,
360 + );
361 +
362 + if (!valid) {
363 + throw Exception(
364 + "Frontier block signature is invalid! Potentially malicious block detected!");
365 + }
366 + }
367 +
368 // first get the account balance:
314 - final BigInt currentBalance = (await getBalance(destinationAddress)).currentBalance;
369 + late BigInt currentBalance;
370 + if (!openBlock) {
371 + currentBalance = BigInt.parse(frontierContents!.balance);
372 + } else {
373 + currentBalance = BigInt.zero;
374 + }
375 final BigInt txAmount = BigInt.parse(amountRaw);
376 final BigInt balanceAfterTx = currentBalance + txAmount;
377
378 // link = send block hash:
379 final String link = blockHash;
380 // this "linkAsAccount" is meaningless:
321 - final String linkAsAccount = NanoAccounts.createAccount(NanoAccountType.NANO, blockHash);
381 + final String linkAsAccount =
382 + NanoDerivations.publicKeyToAddress(blockHash, currency: NanoBasedCurrency.NANO);
383
384 // construct the receive block:
385 Map<String, String> receiveBlock = {
@@ -332,8 +393,8 @@ class NanoClient {
393 };
394
395 // sign the receive block:
335 - final String hash = NanoBlocks.computeStateHash(
336 - NanoAccountType.NANO,
396 + final String hash = NanoSignatures.computeStateHash(
397 + NanoBasedCurrency.NANO,
398 receiveBlock["account"]!,
399 receiveBlock["previous"]!,
400 receiveBlock["representative"]!,
@@ -345,7 +406,7 @@ class NanoClient {
406 // get PoW for the receive block:
407 String? work;
408 if (openBlock) {
348 - work = await requestWork(NanoAccounts.extractPublicKey(destinationAddress));
409 + work = await requestWork(NanoDerivations.addressToPublicKey(destinationAddress));
410 } else {
411 work = await requestWork(frontier);
412 }
@@ -409,10 +470,8 @@ class NanoClient {
470 for (final blockHash in blocks.keys) {
471 final block = blocks[blockHash];
472 final String amountRaw = block["amount"] as String;
412 - final String source = block["source"] as String;
473 await receiveBlock(
474 blockHash: blockHash,
415 - source: source,
475 amountRaw: amountRaw,
476 privateKey: privateKey,
477 destinationAddress: destinationAddress,
cw_nano/lib/nano_wallet.dart
+19 -6
@@ -27,7 +27,6 @@ import 'package:cw_nano/nano_wallet_addresses.dart';
27 import 'package:cw_nano/nano_wallet_keys.dart';
28 import 'package:cw_nano/pending_nano_transaction.dart';
29 import 'package:mobx/mobx.dart';
30 -import 'package:nanodart/nanodart.dart';
30 import 'package:nanoutil/nanoutil.dart';
31
32 part 'nano_wallet.g.dart';
@@ -107,7 +106,6 @@ abstract class NanoWalletBase
106 if (_derivationType == DerivationType.unknown) {
107 _derivationType = DerivationType.nano;
108 }
110 - final String type = (_derivationType == DerivationType.nano) ? "standard" : "hd";
109
110 // our "mnemonic" is actually a hex form seed:
111 if (!_mnemonic.contains(' ')) {
@@ -122,8 +120,10 @@ abstract class NanoWalletBase
120 _hexSeed = await NanoDerivations.hdMnemonicListToSeed(_mnemonic.split(' '));
121 }
122 }
125 - NanoDerivationType derivationType =
126 - type == "standard" ? NanoDerivationType.STANDARD : NanoDerivationType.HD;
123 +
124 + final String type = (_derivationType == DerivationType.nano) ? "standard" : "hd";
125 + NanoDerivationType derivationType = NanoDerivations.stringToType(type);
126 +
127 _privateKey = await NanoDerivations.universalSeedToPrivate(
128 _hexSeed!,
129 index: 0,
@@ -216,8 +216,8 @@ abstract class NanoWalletBase
216 balanceAfterTx: runningBalance,
217 previousHash: previousHash,
218 );
219 - previousHash = NanoBlocks.computeStateHash(
220 - NanoAccountType.NANO,
219 + previousHash = NanoSignatures.computeStateHash(
220 + NanoBasedCurrency.NANO,
221 block["account"]!,
222 block["previous"]!,
223 block["representative"]!,
@@ -535,4 +535,17 @@ abstract class NanoWalletBase
535 // Delete old name's dir and files
536 await Directory(currentDirPath).delete(recursive: true);
537 }
538 +
539 + @override
540 + Future<String> signMessage(String message, {String? address = null}) async {
541 + return NanoSignatures.signMessage(message, privateKey!);
542 + }
543 +
544 + @override
545 + Future<bool> verifyMessage(String message, String signature, {String? address = null}) async {
546 + if (address == null) {
547 + return false;
548 + }
549 + return await NanoSignatures.verifyMessage(message, signature, address);
550 + }
551 }
cw_nano/pubspec.lock
+4 -4
@@ -513,7 +513,7 @@ packages:
513 source: hosted
514 version: "2.3.0"
515 nanodart:
516 - dependency: "direct main"
516 + dependency: transitive
517 description:
518 name: nanodart
519 sha256: "4b2f42d60307b54e8cf384d6193a567d07f8efd773858c0d5948246153c13282"
@@ -524,11 +524,11 @@ packages:
524 dependency: "direct main"
525 description:
526 path: "."
527 - ref: c37e72817cf0a28162f43124f79661d6c8e0098f
528 - resolved-ref: c37e72817cf0a28162f43124f79661d6c8e0098f
527 + ref: c01a9c552917008d8fbc6b540db657031625b04f
528 + resolved-ref: c01a9c552917008d8fbc6b540db657031625b04f
529 url: "https://github.com/perishllc/nanoutil.git"
530 source: git
531 - version: "1.0.0"
531 + version: "1.0.3"
532 package_config:
533 dependency: transitive
534 description:
cw_nano/pubspec.yaml
+1 -2
@@ -15,7 +15,6 @@ dependencies:
15 mobx: ^2.0.7+4
16 bip39: ^1.0.6
17 bip32: ^2.0.0
18 - nanodart: ^2.0.0
18 decimal: ^2.3.3
19 libcrypto: ^0.2.2
20 ed25519_hd_key: ^2.2.0
@@ -25,7 +24,7 @@ dependencies:
24 nanoutil:
25 git:
26 url: https://github.com/perishllc/nanoutil.git
28 - ref: c37e72817cf0a28162f43124f79661d6c8e0098f
27 + ref: c01a9c552917008d8fbc6b540db657031625b04f
28 cw_core:
29 path: ../cw_core
30
cw_solana/lib/solana_wallet.dart
+49 -5
@@ -32,6 +32,8 @@ import 'package:shared_preferences/shared_preferences.dart';
32 import 'package:solana/base58.dart';
33 import 'package:solana/metaplex.dart' as metaplex;
34 import 'package:solana/solana.dart';
35 +import 'package:solana/src/crypto/ed25519_hd_keypair.dart';
36 +import 'package:cryptography/cryptography.dart';
37
38 part 'solana_wallet.g.dart';
39
@@ -571,17 +573,59 @@ abstract class SolanaWalletBase
573 });
574 }
575
574 - Future<String> signSolanaMessage(String message) async {
576 + @override
577 + Future<String> signMessage(String message, {String? address}) async {
578 // Convert the message to bytes
579 final messageBytes = utf8.encode(message);
580
581 // Sign the message bytes with the wallet's private key
579 - final signature = await _walletKeyPair!.sign(messageBytes);
582 + final signature = (await _walletKeyPair!.sign(messageBytes)).toString();
583 +
584 + return HEX.encode(utf8.encode(signature)).toUpperCase();
585 + }
586 +
587 + List<List<int>> bytesFromSigString(String signatureString) {
588 + final regex = RegExp(r'Signature\(\[(.+)\], publicKey: (.+)\)');
589 + final match = regex.firstMatch(signatureString);
590 +
591 + if (match != null) {
592 + final bytesString = match.group(1)!;
593 + final base58EncodedPublicKeyString = match.group(2)!;
594 + final sigBytes = bytesString.split(', ').map(int.parse).toList();
595
581 - // Convert the signature to a hexadecimal string
582 - final hex = HEX.encode(signature.bytes);
596 + List<int> pubKeyBytes = base58decode(base58EncodedPublicKeyString);
597
584 - return hex;
598 + return [sigBytes, pubKeyBytes];
599 + } else {
600 + throw const FormatException('Invalid Signature string format');
601 + }
602 + }
603 +
604 + @override
605 + Future<bool> verifyMessage(String message, String signature, {String? address}) async {
606 + String signatureString = utf8.decode(HEX.decode(signature));
607 +
608 + List<List<int>> bytes = bytesFromSigString(signatureString);
609 +
610 + final messageBytes = utf8.encode(message);
611 + final sigBytes = bytes[0];
612 + final pubKeyBytes = bytes[1];
613 +
614 + if (address == null) {
615 + return false;
616 + }
617 +
618 + // make sure the address derived from the public key provided matches the one we expect
619 + final pub = Ed25519HDPublicKey(pubKeyBytes);
620 + if (address != pub.toBase58()) {
621 + return false;
622 + }
623 +
624 + return await verifySignature(
625 + message: messageBytes,
626 + signature: sigBytes,
627 + publicKey: Ed25519HDPublicKey(pubKeyBytes),
628 + );
629 }
630
631 SolanaClient? get solanaClient => _client.getSolanaClient;
cw_tron/lib/tron_wallet.dart
+12 -2
@@ -580,8 +580,18 @@ abstract class TronWalletBase
580 }
581
582 @override
583 - Future<String> signMessage(String message, {String? address}) async =>
584 - _tronPrivateKey.signPersonalMessage(ascii.encode(message));
583 + Future<String> signMessage(String message, {String? address}) async {
584 + return _tronPrivateKey.signPersonalMessage(ascii.encode(message));
585 + }
586 +
587 + @override
588 + Future<bool> verifyMessage(String message, String signature, {String? address}) async {
589 + if (address == null) {
590 + return false;
591 + }
592 + TronPublicKey pubKey = TronPublicKey.fromPersonalSignature(ascii.encode(message), signature)!;
593 + return pubKey.toAddress().toString() == address;
594 + }
595
596 String getTronBase58AddressFromHex(String hexAddress) => TronAddress(hexAddress).toAddress();
597
cw_wownero/lib/api/wallet.dart
+4
@@ -320,3 +320,7 @@ Future<bool> trustedDaemon() async => wownero.Wallet_trustedDaemon(wptr!);
320 String signMessage(String message, {String address = ""}) {
321 return wownero.Wallet_signMessage(wptr!, message: message, address: address);
322 }
323 +
324 +bool verifyMessage(String message, String address, String signature) {
325 + return wownero.Wallet_verifySignedMessage(wptr!, message: message, address: address, signature: signature);
326 +}
\ No newline at end of file
cw_wownero/lib/wownero_wallet.dart
+7
@@ -743,4 +743,11 @@ abstract class WowneroWalletBase
743 final useAddress = address ?? "";
744 return wownero_wallet.signMessage(message, address: useAddress);
745 }
746 +
747 + @override
748 + Future<bool> verifyMessage(String message, String signature, {String? address = null}) async {
749 + if (address == null) return false;
750 +
751 + return wownero_wallet.verifyMessage(message, address, signature);
752 + }
753 }
lib/bitcoin/cw_bitcoin.dart
+2 -2
@@ -275,7 +275,7 @@ class CWBitcoin extends Bitcoin {
275 return [DerivationType.bip39, DerivationType.electrum];
276 }
277
278 - int _countOccurrences(String str, String charToCount) {
278 + int _countCharOccurrences(String str, String charToCount) {
279 int count = 0;
280 for (int i = 0; i < str.length; i++) {
281 if (str[i] == charToCount) {
@@ -330,7 +330,7 @@ class CWBitcoin extends Bitcoin {
330 );
331
332 String balancePath = dInfoCopy.derivationPath!;
333 - int derivationDepth = _countOccurrences(balancePath, "/");
333 + int derivationDepth = _countCharOccurrences(balancePath, '/');
334
335 // for BIP44
336 if (derivationDepth == 3 || derivationDepth == 1) {
lib/buy/dfx/dfx_buy_provider.dart
+2 -2
@@ -124,12 +124,12 @@ class DFXBuyProvider extends BuyProvider {
124 switch (wallet.type) {
125 case WalletType.ethereum:
126 case WalletType.polygon:
127 - return wallet.signMessage(message);
127 + return await wallet.signMessage(message);
128 case WalletType.monero:
129 case WalletType.litecoin:
130 case WalletType.bitcoin:
131 case WalletType.bitcoinCash:
132 - return wallet.signMessage(message, address: walletAddress);
132 + return await wallet.signMessage(message, address: walletAddress);
133 default:
134 throw Exception("WalletType is not available for DFX ${wallet.type}");
135 }
lib/buy/robinhood/robinhood_buy_provider.dart
+3 -3
@@ -37,15 +37,15 @@ class RobinhoodBuyProvider extends BuyProvider {
37
38 String get _apiSecret => secrets.exchangeHelperApiKey;
39
40 - Future<String> getSignature(String message) {
40 + Future<String> getSignature(String message) async {
41 switch (wallet.type) {
42 case WalletType.ethereum:
43 case WalletType.polygon:
44 - return wallet.signMessage(message);
44 + return await wallet.signMessage(message);
45 case WalletType.litecoin:
46 case WalletType.bitcoin:
47 case WalletType.bitcoinCash:
48 - return wallet.signMessage(message, address: wallet.walletAddresses.address);
48 + return await wallet.signMessage(message, address: wallet.walletAddresses.address);
49 default:
50 throw Exception("WalletType is not available for Robinhood ${wallet.type}");
51 }
lib/di.dart
+11 -5
@@ -30,6 +30,12 @@ import 'package:cake_wallet/entities/contact.dart';
30 import 'package:cake_wallet/entities/contact_record.dart';
31 import 'package:cake_wallet/entities/exchange_api_mode.dart';
32 import 'package:cake_wallet/entities/parse_address_from_domain.dart';
33 +import 'package:cake_wallet/src/screens/receive/address_list_page.dart';
34 +import 'package:cake_wallet/view_model/link_view_model.dart';
35 +import 'package:cake_wallet/tron/tron.dart';
36 +import 'package:cake_wallet/src/screens/transaction_details/rbf_details_page.dart';
37 +import 'package:cake_wallet/view_model/dashboard/sign_view_model.dart';
38 +import 'package:cw_core/receive_page_option.dart';
39 import 'package:cake_wallet/entities/preferences_key.dart';
40 import 'package:cake_wallet/entities/qr_view_data.dart';
41 import 'package:cake_wallet/entities/template.dart';
@@ -159,7 +165,6 @@ import 'package:cw_core/wallet_service.dart';
165 import 'package:cw_core/transaction_info.dart';
166 import 'package:cw_core/node.dart';
167 import 'package:cake_wallet/src/screens/trade_details/trade_details_page.dart';
162 -import 'package:cake_wallet/src/screens/transaction_details/rbf_details_page.dart';
168 import 'package:cake_wallet/src/screens/transaction_details/transaction_details_page.dart';
169 import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_details_page.dart';
170 import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_list_page.dart';
@@ -179,7 +184,6 @@ import 'package:cake_wallet/store/templates/exchange_template_store.dart';
184 import 'package:cake_wallet/store/templates/send_template_store.dart';
185 import 'package:cake_wallet/store/wallet_list_store.dart';
186 import 'package:cake_wallet/store/yat/yat_store.dart';
182 -import 'package:cake_wallet/tron/tron.dart';
187 import 'package:cake_wallet/view_model/auth_view_model.dart';
188 import 'package:cake_wallet/view_model/backup_view_model.dart';
189 import 'package:cake_wallet/view_model/buy/buy_amount_view_model.dart';
@@ -193,7 +197,6 @@ import 'package:cake_wallet/view_model/edit_backup_password_view_model.dart';
197 import 'package:cake_wallet/view_model/exchange/exchange_trade_view_model.dart';
198 import 'package:cake_wallet/view_model/exchange/exchange_view_model.dart';
199 import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
196 -import 'package:cake_wallet/view_model/link_view_model.dart';
200 import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dart';
201 import 'package:cake_wallet/view_model/monero_account_list/monero_account_edit_or_create_view_model.dart';
202 import 'package:cake_wallet/view_model/monero_account_list/monero_account_list_view_model.dart';
@@ -224,7 +227,6 @@ import 'package:cake_wallet/view_model/wallet_unlock_loadable_view_model.dart';
227 import 'package:cake_wallet/view_model/wallet_unlock_verifiable_view_model.dart';
228 import 'package:cake_wallet/wownero/wownero.dart';
229 import 'package:cw_core/crypto_currency.dart';
227 -import 'package:cw_core/receive_page_option.dart';
230 import 'package:cw_core/wallet_info.dart';
231 import 'package:cw_core/wallet_type.dart';
232 import 'package:flutter/foundation.dart';
@@ -853,6 +855,8 @@ Future<void> setup({
855 getIt.registerFactoryParam<ContactPage, ContactRecord?, void>(
856 (ContactRecord? contact, _) => ContactPage(getIt.get<ContactViewModel>(param1: contact)));
857
858 + getIt.registerFactory(() => AddressListPage(getIt.get<WalletAddressListViewModel>()));
859 +
860 getIt.registerFactory(() {
861 final appStore = getIt.get<AppStore>();
862 return NodeListViewModel(_nodeSource, appStore);
@@ -1271,9 +1275,11 @@ Future<void> setup({
1275
1276 getIt.registerFactory(
1277 () => WalletConnectConnectionsView(web3walletService: getIt.get<Web3WalletService>()));
1274 -
1278 +
1279 getIt.registerFactory(() => NFTViewModel(appStore, getIt.get<BottomSheetService>()));
1280 getIt.registerFactory<TorPage>(() => TorPage(getIt.get<AppStore>()));
1281
1282 + getIt.registerFactory(() => SignViewModel(getIt.get<AppStore>().wallet!));
1283 +
1284 _isSetupFinished = true;
1285 }
lib/router.dart
+14
@@ -27,6 +27,7 @@ import 'package:cake_wallet/src/screens/dashboard/edit_token_page.dart';
27 import 'package:cake_wallet/src/screens/dashboard/home_settings_page.dart';
28 import 'package:cake_wallet/src/screens/dashboard/pages/address_page.dart';
29 import 'package:cake_wallet/src/screens/dashboard/pages/nft_details_page.dart';
30 +import 'package:cake_wallet/src/screens/dashboard/sign_page.dart';
31 import 'package:cake_wallet/src/screens/dashboard/pages/transactions_page.dart';
32 import 'package:cake_wallet/src/screens/disclaimer/disclaimer_page.dart';
33 import 'package:cake_wallet/src/screens/exchange/exchange_page.dart';
@@ -42,6 +43,8 @@ import 'package:cake_wallet/src/screens/new_wallet/new_wallet_page.dart';
43 import 'package:cake_wallet/src/screens/new_wallet/new_wallet_type_page.dart';
44 import 'package:cake_wallet/src/screens/nodes/node_create_or_edit_page.dart';
45 import 'package:cake_wallet/src/screens/nodes/pow_node_create_or_edit_page.dart';
46 +import 'package:cake_wallet/src/screens/receive/address_list_page.dart';
47 +import 'package:cake_wallet/src/screens/restore/sweeping_wallet_page.dart';
48 import 'package:cake_wallet/src/screens/order_details/order_details_page.dart';
49 import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart';
50 import 'package:cake_wallet/src/screens/receive/anonpay_invoice_page.dart';
@@ -99,6 +102,7 @@ import 'package:cake_wallet/utils/payment_request.dart';
102 import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart';
103 import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
104 import 'package:cake_wallet/view_model/dashboard/nft_view_model.dart';
105 +import 'package:cake_wallet/view_model/dashboard/sign_view_model.dart';
106 import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
107 import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dart';
108 import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
@@ -465,6 +469,9 @@ Route<dynamic> createRoute(RouteSettings settings) {
469 return MaterialPageRoute<void>(
470 builder: (_) => getIt.get<ContactListPage>(param1: selectedCurrency));
471
472 + case Routes.pickerWalletAddress:
473 + return MaterialPageRoute<void>(builder: (_) => getIt.get<AddressListPage>());
474 +
475 case Routes.addressBookAddContact:
476 return CupertinoPageRoute<void>(
477 builder: (_) => getIt.get<ContactPage>(param1: settings.arguments as ContactRecord?));
@@ -696,6 +703,13 @@ Route<dynamic> createRoute(RouteSettings settings) {
703 case Routes.torPage:
704 return MaterialPageRoute<void>(builder: (_) => getIt.get<TorPage>());
705
706 + case Routes.signPage:
707 + return MaterialPageRoute<void>(
708 + builder: (_) => SignPage(
709 + getIt.get<SignViewModel>(),
710 + ),
711 + );
712 +
713 case Routes.connectDevices:
714 final params = settings.arguments as ConnectDevicePageParams;
715 return MaterialPageRoute<void>(
lib/routes.dart
+2
@@ -31,6 +31,7 @@ class Routes {
31 static const nanoAccountCreation = '/nano_account_new';
32 static const addressBook = '/address_book';
33 static const pickerAddressBook = '/picker_address_book';
34 + static const pickerWalletAddress = '/picker_wallet_address';
35 static const addressBookAddContact = '/address_book_add_contact';
36 static const showKeys = '/show_keys';
37 static const exchangeConfirm = '/exchange_confirm';
@@ -103,5 +104,6 @@ class Routes {
104 static const nftDetailsPage = '/nft_details_page';
105 static const importNFTPage = '/import_nft_page';
106 static const torPage = '/tor_page';
107 + static const signPage = '/sign_page';
108 static const connectDevices = '/device/connect';
109 }
lib/src/screens/cake_pay/cards/cake_pay_confirm_purchase_card_page.dart
+9 -9
@@ -322,31 +322,31 @@ class CakePayBuyCardDetailPage extends BasePage {
322
323 await showPopUp<void>(
324 context: context,
325 - builder: (_) {
325 + builder: (popupContext) {
326 return Observer(
327 builder: (_) => ConfirmSendingAlert(
328 - alertTitle: S.of(context).confirm_sending,
329 - paymentId: S.of(context).payment_id,
328 + alertTitle: S.of(popupContext).confirm_sending,
329 + paymentId: S.of(popupContext).payment_id,
330 paymentIdValue: order?.orderId,
331 expirationTime: cakePayPurchaseViewModel.formattedRemainingTime,
332 onDispose: () => _handleDispose(disposer),
333 - amount: S.of(context).send_amount,
333 + amount: S.of(popupContext).send_amount,
334 amountValue: pendingTransaction.amountFormatted,
335 fiatAmountValue:
336 cakePayPurchaseViewModel.sendViewModel.pendingTransactionFiatAmountFormatted,
337 - fee: S.of(context).send_fee,
337 + fee: S.of(popupContext).send_fee,
338 feeValue: pendingTransaction.feeFormatted,
339 feeFiatAmount:
340 cakePayPurchaseViewModel.sendViewModel.pendingTransactionFeeFiatAmountFormatted,
341 feeRate: pendingTransaction.feeRate,
342 outputs: cakePayPurchaseViewModel.sendViewModel.outputs,
343 - rightButtonText: S.of(context).send,
344 - leftButtonText: S.of(context).cancel,
343 + rightButtonText: S.of(popupContext).send,
344 + leftButtonText: S.of(popupContext).cancel,
345 actionRightButton: () async {
346 - Navigator.of(context).pop();
346 + Navigator.of(popupContext).pop();
347 await cakePayPurchaseViewModel.sendViewModel.commitTransaction();
348 },
349 - actionLeftButton: () => Navigator.of(context).pop()));
349 + actionLeftButton: () => Navigator.of(popupContext).pop()));
350 },
351 );
352 }
lib/src/screens/dashboard/pages/cake_features_page.dart
+14
@@ -8,6 +8,7 @@ import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
8 import 'package:cw_core/wallet_type.dart';
9 import 'package:cake_wallet/view_model/dashboard/cake_features_view_model.dart';
10 import 'package:flutter/material.dart';
11 +import 'package:flutter_mobx/flutter_mobx.dart';
12 import 'package:url_launcher/url_launcher.dart';
13 import 'package:flutter_svg/flutter_svg.dart';
14
@@ -64,6 +65,19 @@ class CakeFeaturesPage extends StatelessWidget {
65 subTitle: S.of(context).nanogpt_subtitle,
66 onTap: () => _launchUrl("cake.nano-gpt.com"),
67 ),
68 + SizedBox(height: 20),
69 + Observer(
70 + builder: (context) {
71 + if (!dashboardViewModel.hasSignMessages) {
72 + return const SizedBox();
73 + }
74 + return DashBoardRoundedCardWidget(
75 + onTap: () => Navigator.of(context).pushNamed(Routes.signPage),
76 + title: S.current.sign_verify_message,
77 + subTitle: S.current.sign_verify_message_sub,
78 + );
79 + },
80 + ),
81 ],
82 ),
83 ),
lib/src/screens/dashboard/sign_page.dart new
+202
@@ -0,0 +1,202 @@
1 +import 'package:cake_wallet/core/execution_state.dart';
2 +import 'package:cake_wallet/generated/i18n.dart';
3 +import 'package:cake_wallet/src/screens/base_page.dart';
4 +import 'package:cake_wallet/src/screens/dashboard/widgets/sign_form.dart';
5 +import 'package:cake_wallet/src/screens/dashboard/widgets/verify_form.dart';
6 +import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
7 +import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
8 +import 'package:cake_wallet/src/widgets/primary_button.dart';
9 +import 'package:cake_wallet/themes/extensions/keyboard_theme.dart';
10 +import 'package:cake_wallet/themes/extensions/wallet_list_theme.dart';
11 +import 'package:cake_wallet/utils/responsive_layout_util.dart';
12 +import 'package:cake_wallet/utils/show_pop_up.dart';
13 +import 'package:cake_wallet/view_model/dashboard/sign_view_model.dart';
14 +import 'package:flutter/material.dart';
15 +import 'package:flutter_mobx/flutter_mobx.dart';
16 +import 'package:keyboard_actions/keyboard_actions.dart';
17 +import 'package:mobx/mobx.dart';
18 +import 'package:smooth_page_indicator/smooth_page_indicator.dart';
19 +
20 +class SignPage extends BasePage {
21 + SignPage(this.signViewModel)
22 + : signFormKey = GlobalKey<SignFormState>(),
23 + verifyFormKey = GlobalKey<VerifyFormState>(),
24 + _pages = [],
25 + _controller = PageController(initialPage: 0) {
26 + _pages.add(SignForm(
27 + key: signFormKey,
28 + type: signViewModel.wallet.type,
29 + includeAddress: signViewModel.signIncludesAddress,
30 + ));
31 + _pages.add(VerifyForm(
32 + key: verifyFormKey,
33 + type: signViewModel.wallet.type,
34 + ));
35 + }
36 +
37 + @override
38 + Widget middle(BuildContext context) => Observer(
39 + builder: (_) => Text(
40 + S.current.sign_verify_title,
41 + style: TextStyle(
42 + fontSize: 18.0,
43 + fontWeight: FontWeight.bold,
44 + fontFamily: 'Lato',
45 + color: titleColor(context),
46 + ),
47 + ));
48 +
49 + final SignViewModel signViewModel;
50 + final PageController _controller;
51 + final List<Widget> _pages;
52 + final GlobalKey<SignFormState> signFormKey;
53 + final GlobalKey<VerifyFormState> verifyFormKey;
54 + bool _isEffectsInstalled = false;
55 +
56 + @override
57 + Widget body(BuildContext context) {
58 + _setEffects(context);
59 +
60 + return KeyboardActions(
61 + config: KeyboardActionsConfig(
62 + keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
63 + keyboardBarColor: Theme.of(context).extension<KeyboardTheme>()!.keyboardBarColor,
64 + nextFocus: false,
65 + actions: [
66 + KeyboardActionsItem(
67 + focusNode: FocusNode(),
68 + toolbarButtons: [(_) => KeyboardDoneButton()],
69 + )
70 + ],
71 + ),
72 + child: Container(
73 + height: 0,
74 + color: Theme.of(context).colorScheme.background,
75 + child: Center(
76 + child: ConstrainedBox(
77 + constraints:
78 + BoxConstraints(maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint),
79 + child: Column(
80 + mainAxisAlignment: MainAxisAlignment.center,
81 + children: [
82 + Expanded(
83 + child: PageView.builder(
84 + onPageChanged: (page) {
85 + signViewModel.isSigning = page == 0;
86 + },
87 + controller: _controller,
88 + itemCount: _pages.length,
89 + itemBuilder: (_, index) => SingleChildScrollView(child: _pages[index]),
90 + ),
91 + ),
92 + if (_pages.length > 1)
93 + Padding(
94 + padding: EdgeInsets.only(top: 10),
95 + child: SmoothPageIndicator(
96 + controller: _controller,
97 + count: _pages.length,
98 + effect: ColorTransitionEffect(
99 + spacing: 6.0,
100 + radius: 6.0,
101 + dotWidth: 6.0,
102 + dotHeight: 6.0,
103 + dotColor: Theme.of(context).hintColor.withOpacity(0.5),
104 + activeDotColor: Theme.of(context).hintColor,
105 + ),
106 + ),
107 + ),
108 + Padding(
109 + padding: EdgeInsets.only(top: 20, bottom: 24, left: 24, right: 24),
110 + child: Column(
111 + children: [
112 + Observer(
113 + builder: (context) {
114 + return LoadingPrimaryButton(
115 + onPressed: () async {
116 + await _confirmForm(context);
117 + },
118 + text: signViewModel.isSigning
119 + ? S.current.sign_message
120 + : S.current.verify_message,
121 + color: Theme.of(context)
122 + .extension<WalletListTheme>()!
123 + .createNewWalletButtonBackgroundColor,
124 + textColor: Theme.of(context)
125 + .extension<WalletListTheme>()!
126 + .restoreWalletButtonTextColor,
127 + isLoading: signViewModel.state is IsExecutingState,
128 + isDisabled: signViewModel.state is IsExecutingState,
129 + );
130 + },
131 + ),
132 + ],
133 + ),
134 + )
135 + ],
136 + ),
137 + ),
138 + ),
139 + ),
140 + );
141 + }
142 +
143 + void _setEffects(BuildContext context) async {
144 + if (_isEffectsInstalled) {
145 + return;
146 + }
147 + _isEffectsInstalled = true;
148 +
149 + reaction((_) => signViewModel.state, (ExecutionState state) {
150 + if (state is FailureState) {
151 + WidgetsBinding.instance.addPostFrameCallback((_) {
152 + showPopUp<void>(
153 + context: context,
154 + builder: (_) {
155 + return AlertWithOneAction(
156 + alertTitle: S.current.error,
157 + alertContent: state.error,
158 + buttonText: S.of(context).ok,
159 + buttonAction: () => Navigator.of(context).pop(),
160 + );
161 + });
162 + });
163 + }
164 + if (state is ExecutedSuccessfullyState) {
165 + if (signViewModel.isSigning) {
166 + signFormKey.currentState!.signatureController.text = state.payload as String;
167 + } else {
168 + WidgetsBinding.instance.addPostFrameCallback((_) {
169 + showPopUp<void>(
170 + context: context,
171 + builder: (_) {
172 + return AlertWithOneAction(
173 + alertTitle: S.current.successful,
174 + alertContent: S.current.message_verified,
175 + buttonText: S.of(context).ok,
176 + buttonAction: () => Navigator.of(context).pop(),
177 + );
178 + });
179 + });
180 + }
181 + }
182 + });
183 + }
184 +
185 + Future<void> _confirmForm(BuildContext context) async {
186 + FocusManager.instance.primaryFocus?.unfocus();
187 +
188 + if (signViewModel.isSigning) {
189 + String message = signFormKey.currentState!.messageController.text;
190 + String? address;
191 + if (signViewModel.signIncludesAddress) {
192 + address = signFormKey.currentState!.addressController.text;
193 + }
194 + await signViewModel.sign(message, address: address);
195 + } else {
196 + String message = verifyFormKey.currentState!.messageController.text;
197 + String signature = verifyFormKey.currentState!.signatureController.text;
198 + String address = verifyFormKey.currentState!.addressController.text;
199 + await signViewModel.verify(message, signature, address: address);
200 + }
201 + }
202 +}
lib/src/screens/dashboard/widgets/sign_form.dart new
+98
@@ -0,0 +1,98 @@
1 +import 'package:cake_wallet/generated/i18n.dart';
2 +import 'package:cake_wallet/src/widgets/address_text_field.dart';
3 +import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
4 +import 'package:cake_wallet/utils/show_bar.dart';
5 +import 'package:cw_core/wallet_type.dart';
6 +import 'package:flutter/material.dart';
7 +import 'package:flutter/services.dart';
8 +
9 +class SignForm extends StatefulWidget {
10 + SignForm({
11 + Key? key,
12 + required this.type,
13 + required this.includeAddress,
14 + }) : super(key: key);
15 +
16 + final WalletType type;
17 + final bool includeAddress;
18 +
19 + @override
20 + SignFormState createState() => SignFormState();
21 +}
22 +
23 +class SignFormState extends State<SignForm> {
24 + SignFormState()
25 + : formKey = GlobalKey<FormState>(),
26 + messageController = TextEditingController(),
27 + addressController = TextEditingController(),
28 + signatureController = TextEditingController();
29 +
30 + final TextEditingController messageController;
31 + final TextEditingController addressController;
32 + final TextEditingController signatureController;
33 + final GlobalKey<FormState> formKey;
34 +
35 + @override
36 + void initState() {
37 + super.initState();
38 + }
39 +
40 + @override
41 + void dispose() {
42 + super.dispose();
43 + }
44 +
45 + @override
46 + Widget build(BuildContext context) {
47 + return Container(
48 + padding: EdgeInsets.only(left: 24, right: 24),
49 + child: Column(
50 + children: [
51 + Form(
52 + key: formKey,
53 + child: Column(
54 + children: [
55 + AddressTextField(
56 + controller: messageController,
57 + placeholder: S.current.message,
58 + options: [AddressTextFieldOption.paste],
59 + buttonColor: Theme.of(context).hintColor,
60 + ),
61 + if (widget.includeAddress) ...[
62 + const SizedBox(height: 20),
63 + AddressTextField(
64 + controller: addressController,
65 + options: [
66 + AddressTextFieldOption.paste,
67 + AddressTextFieldOption.walletAddresses
68 + ],
69 + buttonColor: Theme.of(context).hintColor,
70 + onSelectedContact: (contact) {
71 + addressController.text = contact.address;
72 + },
73 + selectedCurrency: walletTypeToCryptoCurrency(widget.type),
74 + ),
75 + ],
76 + ],
77 + )),
78 + const SizedBox(height: 20),
79 + GestureDetector(
80 + onTap: () async {
81 + final text = signatureController.text;
82 + if (text.isEmpty) {
83 + return;
84 + }
85 + Clipboard.setData(ClipboardData(text: text));
86 + showBar<void>(context, S.of(context).transaction_details_copied(text));
87 + },
88 + child: BaseTextFormField(
89 + enabled: false,
90 + controller: signatureController,
91 + hintText: S.current.signature,
92 + ),
93 + ),
94 + ],
95 + ),
96 + );
97 + }
98 +}
lib/src/screens/dashboard/widgets/verify_form.dart new
+92
@@ -0,0 +1,92 @@
1 +import 'package:cake_wallet/core/wallet_name_validator.dart';
2 +import 'package:cake_wallet/entities/generate_name.dart';
3 +import 'package:cake_wallet/entities/seed_type.dart';
4 +import 'package:cake_wallet/generated/i18n.dart';
5 +import 'package:cake_wallet/src/widgets/address_text_field.dart';
6 +import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
7 +import 'package:cake_wallet/src/widgets/blockchain_height_widget.dart';
8 +import 'package:cake_wallet/src/widgets/picker.dart';
9 +import 'package:cake_wallet/src/widgets/seed_language_picker.dart';
10 +import 'package:cake_wallet/src/widgets/seed_widget.dart';
11 +import 'package:cake_wallet/themes/extensions/address_theme.dart';
12 +import 'package:cake_wallet/themes/extensions/send_page_theme.dart';
13 +import 'package:cake_wallet/utils/show_bar.dart';
14 +import 'package:cake_wallet/utils/show_pop_up.dart';
15 +import 'package:cake_wallet/view_model/seed_type_view_model.dart';
16 +import 'package:cw_core/wallet_type.dart';
17 +import 'package:flutter/material.dart';
18 +import 'package:flutter/services.dart';
19 +import 'package:mobx/mobx.dart';
20 +import 'package:polyseed/polyseed.dart';
21 +
22 +class VerifyForm extends StatefulWidget {
23 + VerifyForm({
24 + Key? key,
25 + required this.type,
26 + }) : super(key: key);
27 +
28 + final WalletType type;
29 +
30 + @override
31 + VerifyFormState createState() => VerifyFormState();
32 +}
33 +
34 +class VerifyFormState extends State<VerifyForm> {
35 + VerifyFormState()
36 + : formKey = GlobalKey<FormState>(),
37 + messageController = TextEditingController(),
38 + addressController = TextEditingController(),
39 + signatureController = TextEditingController();
40 +
41 + final TextEditingController messageController;
42 + final TextEditingController addressController;
43 + final TextEditingController signatureController;
44 + final GlobalKey<FormState> formKey;
45 +
46 + @override
47 + void initState() {
48 + super.initState();
49 + }
50 +
51 + @override
52 + void dispose() {
53 + super.dispose();
54 + }
55 +
56 + @override
57 + Widget build(BuildContext context) {
58 + return Container(
59 + padding: EdgeInsets.only(left: 24, right: 24),
60 + child: Form(
61 + key: formKey,
62 + child: Column(
63 + children: [
64 + AddressTextField(
65 + controller: messageController,
66 + placeholder: S.current.message,
67 + options: [AddressTextFieldOption.paste],
68 + buttonColor: Theme.of(context).hintColor,
69 + ),
70 + const SizedBox(height: 20),
71 + AddressTextField(
72 + controller: addressController,
73 + options: [AddressTextFieldOption.paste, AddressTextFieldOption.walletAddresses],
74 + buttonColor: Theme.of(context).hintColor,
75 + onSelectedContact: (contact) {
76 + addressController.text = contact.address;
77 + },
78 + selectedCurrency: walletTypeToCryptoCurrency(widget.type),
79 + ),
80 + const SizedBox(height: 20),
81 + AddressTextField(
82 + controller: signatureController,
83 + placeholder: S.current.signature,
84 + options: [AddressTextFieldOption.paste],
85 + buttonColor: Theme.of(context).hintColor,
86 + ),
87 + ],
88 + ),
89 + ),
90 + );
91 + }
92 +}
lib/src/screens/receive/address_list_page.dart new
+31
@@ -0,0 +1,31 @@
1 +import 'package:cake_wallet/src/screens/receive/widgets/address_list.dart';
2 +import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
3 +import 'package:flutter/material.dart';
4 +import 'package:flutter/cupertino.dart';
5 +import 'package:cake_wallet/generated/i18n.dart';
6 +import 'package:cake_wallet/src/screens/base_page.dart';
7 +
8 +class AddressListPage extends BasePage {
9 + AddressListPage(this.addressListViewModel);
10 +
11 + final WalletAddressListViewModel addressListViewModel;
12 +
13 + @override
14 + String get title => S.current.accounts_subaddresses;
15 +
16 + @override
17 + Widget body(BuildContext context) {
18 + return SingleChildScrollView(
19 + child: Column(
20 + children: <Widget>[
21 + AddressList(
22 + addressListViewModel: addressListViewModel,
23 + onSelect: (String address) async {
24 + Navigator.of(context).pop(address);
25 + },
26 + ),
27 + ],
28 + ),
29 + );
30 + }
31 +}
lib/src/screens/receive/receive_page.dart
+2 -102
@@ -1,4 +1,5 @@
1 import 'package:cake_wallet/src/screens/nano_accounts/nano_account_list_page.dart';
2 +import 'package:cake_wallet/src/screens/receive/widgets/address_list.dart';
3 import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
4 import 'package:cake_wallet/themes/extensions/balance_page_theme.dart';
5 import 'package:cake_wallet/themes/extensions/keyboard_theme.dart';
@@ -122,108 +123,7 @@ class ReceivePage extends BasePage {
123 amountController: _amountController,
124 isLight: currentTheme.type == ThemeType.light),
125 ),
125 - Observer(
126 - builder: (_) => ListView.separated(
127 - padding: EdgeInsets.all(0),
128 - separatorBuilder: (context, _) => const HorizontalSectionDivider(),
129 - shrinkWrap: true,
130 - physics: NeverScrollableScrollPhysics(),
131 - itemCount: addressListViewModel.items.length,
132 - itemBuilder: (context, index) {
133 - final item = addressListViewModel.items[index];
134 - Widget cell = Container();
135 -
136 - if (item is WalletAccountListHeader) {
137 - cell = HeaderTile(
138 - showTrailingButton: true,
139 - walletAddressListViewModel: addressListViewModel,
140 - trailingButtonTap: () async {
141 - if (addressListViewModel.type == WalletType.monero ||
142 - addressListViewModel.type == WalletType.wownero ||
143 - addressListViewModel.type == WalletType.haven) {
144 - await showPopUp<void>(
145 - context: context,
146 - builder: (_) => getIt.get<MoneroAccountListPage>());
147 - } else {
148 - await showPopUp<void>(
149 - context: context,
150 - builder: (_) => getIt.get<NanoAccountListPage>());
151 - }
152 - },
153 - title: S.of(context).accounts,
154 - trailingIcon: Icon(
155 - Icons.arrow_forward_ios,
156 - size: 14,
157 - color: Theme.of(context).extension<ReceivePageTheme>()!.iconsColor,
158 - ));
159 - }
160 -
161 - if (item is WalletAddressListHeader) {
162 - final hasTitle = item.title != null;
163 -
164 - cell = HeaderTile(
165 - title: hasTitle ? item.title! : S.of(context).addresses,
166 - walletAddressListViewModel: addressListViewModel,
167 - showTrailingButton:
168 - !addressListViewModel.isAutoGenerateSubaddressEnabled && !hasTitle,
169 - showSearchButton: true,
170 - trailingButtonTap: () =>
171 - Navigator.of(context).pushNamed(Routes.newSubaddress),
172 - trailingIcon: hasTitle
173 - ? null
174 - : Icon(
175 - Icons.add,
176 - size: 20,
177 - color:
178 - Theme.of(context).extension<ReceivePageTheme>()!.iconsColor,
179 - ),
180 - );
181 - }
182 -
183 - if (item is WalletAddressListItem) {
184 - cell = Observer(builder: (_) {
185 - final isCurrent = item.address == addressListViewModel.address.address;
186 - final backgroundColor = isCurrent
187 - ? Theme.of(context)
188 - .extension<ReceivePageTheme>()!
189 - .currentTileBackgroundColor
190 - : Theme.of(context)
191 - .extension<ReceivePageTheme>()!
192 - .tilesBackgroundColor;
193 - final textColor = isCurrent
194 - ? Theme.of(context)
195 - .extension<ReceivePageTheme>()!
196 - .currentTileTextColor
197 - : Theme.of(context).extension<ReceivePageTheme>()!.tilesTextColor;
198 -
199 - return AddressCell.fromItem(
200 - item,
201 - isCurrent: isCurrent,
202 - hasBalance: addressListViewModel.isElectrumWallet,
203 - backgroundColor: backgroundColor,
204 - textColor: textColor,
205 - onTap: item.isOneTimeReceiveAddress == true
206 - ? null
207 - : (_) => addressListViewModel.setAddress(item),
208 - onEdit: item.isOneTimeReceiveAddress == true || item.isPrimary
209 - ? null
210 - : () => Navigator.of(context)
211 - .pushNamed(Routes.newSubaddress, arguments: item),
212 - onDelete: !addressListViewModel.isSilentPayments || item.isPrimary
213 - ? null
214 - : () => addressListViewModel.deleteAddress(item),
215 - );
216 - });
217 - }
218 -
219 - return index != 0
220 - ? cell
221 - : ClipRRect(
222 - borderRadius: BorderRadius.only(
223 - topLeft: Radius.circular(30), topRight: Radius.circular(30)),
224 - child: cell,
225 - );
226 - })),
126 + AddressList(addressListViewModel: addressListViewModel),
127 Padding(
128 padding: EdgeInsets.fromLTRB(24, 24, 24, 32),
129 child: Text(
lib/src/screens/receive/widgets/address_list.dart new
+120
@@ -0,0 +1,120 @@
1 +import 'package:cake_wallet/di.dart';
2 +import 'package:cake_wallet/generated/i18n.dart';
3 +import 'package:cake_wallet/routes.dart';
4 +import 'package:cake_wallet/src/screens/monero_accounts/monero_account_list_page.dart';
5 +import 'package:cake_wallet/src/screens/nano_accounts/nano_account_list_page.dart';
6 +import 'package:cake_wallet/src/screens/receive/widgets/address_cell.dart';
7 +import 'package:cake_wallet/src/screens/receive/widgets/header_tile.dart';
8 +import 'package:cake_wallet/src/widgets/section_divider.dart';
9 +import 'package:cake_wallet/themes/extensions/receive_page_theme.dart';
10 +import 'package:cake_wallet/utils/show_pop_up.dart';
11 +import 'package:cake_wallet/view_model/wallet_address_list/wallet_account_list_header.dart';
12 +import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_header.dart';
13 +import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart';
14 +import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
15 +import 'package:cw_core/wallet_type.dart';
16 +import 'package:flutter/material.dart';
17 +import 'package:flutter_mobx/flutter_mobx.dart';
18 +
19 +class AddressList extends StatelessWidget {
20 + const AddressList({
21 + super.key,
22 + required this.addressListViewModel,
23 + this.onSelect,
24 + });
25 +
26 + final WalletAddressListViewModel addressListViewModel;
27 + final Function(String)? onSelect;
28 +
29 + @override
30 + Widget build(BuildContext context) {
31 + bool editable = onSelect == null;
32 + return Observer(
33 + builder: (_) => ListView.separated(
34 + padding: EdgeInsets.all(0),
35 + separatorBuilder: (context, _) => const HorizontalSectionDivider(),
36 + shrinkWrap: true,
37 + physics: NeverScrollableScrollPhysics(),
38 + itemCount: addressListViewModel.items.length,
39 + itemBuilder: (context, index) {
40 + final item = addressListViewModel.items[index];
41 + Widget cell = Container();
42 +
43 + if (item is WalletAccountListHeader) {
44 + cell = HeaderTile(
45 + showTrailingButton: true,
46 + walletAddressListViewModel: addressListViewModel,
47 + trailingButtonTap: () async {
48 + if (addressListViewModel.type == WalletType.monero ||
49 + addressListViewModel.type == WalletType.haven) {
50 + await showPopUp<void>(
51 + context: context, builder: (_) => getIt.get<MoneroAccountListPage>());
52 + } else {
53 + await showPopUp<void>(
54 + context: context, builder: (_) => getIt.get<NanoAccountListPage>());
55 + }
56 + },
57 + title: S.of(context).accounts,
58 + trailingIcon: Icon(
59 + Icons.arrow_forward_ios,
60 + size: 14,
61 + color: Theme.of(context).extension<ReceivePageTheme>()!.iconsColor,
62 + ));
63 + }
64 +
65 + if (item is WalletAddressListHeader) {
66 + cell = HeaderTile(
67 + title: S.of(context).addresses,
68 + walletAddressListViewModel: addressListViewModel,
69 + showTrailingButton: !addressListViewModel.isAutoGenerateSubaddressEnabled,
70 + showSearchButton: true,
71 + trailingButtonTap: () => Navigator.of(context).pushNamed(Routes.newSubaddress),
72 + trailingIcon: Icon(
73 + Icons.add,
74 + size: 20,
75 + color: Theme.of(context).extension<ReceivePageTheme>()!.iconsColor,
76 + ));
77 + }
78 +
79 + if (item is WalletAddressListItem) {
80 + cell = Observer(builder: (_) {
81 + final isCurrent = item.address == addressListViewModel.address.address && editable;
82 + final backgroundColor = isCurrent
83 + ? Theme.of(context).extension<ReceivePageTheme>()!.currentTileBackgroundColor
84 + : Theme.of(context).extension<ReceivePageTheme>()!.tilesBackgroundColor;
85 + final textColor = isCurrent
86 + ? Theme.of(context).extension<ReceivePageTheme>()!.currentTileTextColor
87 + : Theme.of(context).extension<ReceivePageTheme>()!.tilesTextColor;
88 +
89 + return AddressCell.fromItem(
90 + item,
91 + isCurrent: isCurrent,
92 + hasBalance: addressListViewModel.isElectrumWallet,
93 + backgroundColor: backgroundColor,
94 + textColor: textColor,
95 + onTap: (_) {
96 + if (onSelect != null) {
97 + onSelect!(item.address);
98 + return;
99 + }
100 + addressListViewModel.setAddress(item);
101 + },
102 + onEdit: editable
103 + ? () => Navigator.of(context).pushNamed(Routes.newSubaddress, arguments: item)
104 + : null,
105 + );
106 + });
107 + }
108 +
109 + return index != 0
110 + ? cell
111 + : ClipRRect(
112 + borderRadius: BorderRadius.only(
113 + topLeft: Radius.circular(30), topRight: Radius.circular(30)),
114 + child: cell,
115 + );
116 + },
117 + ),
118 + );
119 + }
120 +}
lib/src/widgets/address_text_field.dart
+116 -76
@@ -12,7 +12,7 @@ import 'package:cake_wallet/themes/extensions/send_page_theme.dart';
12 import 'package:cake_wallet/utils/permission_handler.dart';
13 import 'package:permission_handler/permission_handler.dart';
14
15 -enum AddressTextFieldOption { paste, qrCode, addressBook }
15 +enum AddressTextFieldOption { paste, qrCode, addressBook, walletAddresses }
16
17 class AddressTextField extends StatelessWidget {
18 AddressTextField(
@@ -34,6 +34,7 @@ class AddressTextField extends StatelessWidget {
34 this.validator,
35 this.onPushPasteButton,
36 this.onPushAddressBookButton,
37 + this.onPushAddressPickerButton,
38 this.onSelectedContact,
39 this.selectedCurrency});
40
@@ -56,6 +57,7 @@ class AddressTextField extends StatelessWidget {
57 final FocusNode? focusNode;
58 final Function(BuildContext context)? onPushPasteButton;
59 final Function(BuildContext context)? onPushAddressBookButton;
60 + final Function(BuildContext context)? onPushAddressPickerButton;
61 final Function(ContactBase contact)? onSelectedContact;
62 final CryptoCurrency? selectedCurrency;
63
@@ -70,16 +72,13 @@ class AddressTextField extends StatelessWidget {
72 enabled: isActive,
73 controller: controller,
74 focusNode: focusNode,
73 -
75 style: textStyle ??
76 TextStyle(
77 fontSize: 16, color: Theme.of(context).extension<CakeTextTheme>()!.titleColor),
78 decoration: InputDecoration(
78 -
79 suffixIcon: SizedBox(
80 width: prefixIconWidth * options.length + (spaceBetweenPrefixIcons * options.length),
81 ),
82 -
82 hintStyle: hintStyle ?? TextStyle(fontSize: 16, color: Theme.of(context).hintColor),
83 hintText: placeholder ?? S.current.widgets_address,
84 focusedBorder: isBorderExist
@@ -104,90 +103,122 @@ class AddressTextField extends StatelessWidget {
103 top: 2,
104 right: 0,
105 child: SizedBox(
107 - width: prefixIconWidth * options.length + (spaceBetweenPrefixIcons * options.length),
106 + width:
107 + (prefixIconWidth * options.length) + (spaceBetweenPrefixIcons * options.length),
108 child: Row(
109 mainAxisAlignment: responsiveLayoutUtil.shouldRenderMobileUI
110 ? MainAxisAlignment.spaceBetween
111 : MainAxisAlignment.end,
112 children: [
113 - SizedBox(width: 5),
113 if (this.options.contains(AddressTextFieldOption.paste)) ...[
114 + SizedBox(width: 5),
115 Container(
116 - width: prefixIconWidth,
117 - height: prefixIconHeight,
118 - padding: EdgeInsets.only(top: 0),
119 - child: Semantics(
120 - label: S.of(context).paste,
121 - child: InkWell(
122 - onTap: () async => _pasteAddress(context),
123 - child: Container(
124 - padding: EdgeInsets.all(8),
125 - decoration: BoxDecoration(
126 - color: buttonColor ??
127 - Theme.of(context).dialogTheme.backgroundColor,
128 - borderRadius: BorderRadius.all(Radius.circular(6))),
129 - child: Image.asset(
130 - 'assets/images/paste_ios.png',
131 - color: iconColor ??
132 - Theme.of(context)
133 - .extension<SendPageTheme>()!
134 - .textFieldButtonIconColor,
135 - )),
136 - ),
137 - )),
116 + width: prefixIconWidth,
117 + height: prefixIconHeight,
118 + padding: EdgeInsets.only(top: 0),
119 + child: Semantics(
120 + label: S.of(context).paste,
121 + child: InkWell(
122 + onTap: () async => _pasteAddress(context),
123 + child: Container(
124 + padding: EdgeInsets.all(8),
125 + decoration: BoxDecoration(
126 + color:
127 + buttonColor ?? Theme.of(context).dialogTheme.backgroundColor,
128 + borderRadius: BorderRadius.all(Radius.circular(6))),
129 + child: Image.asset(
130 + 'assets/images/paste_ios.png',
131 + color: iconColor ??
132 + Theme.of(context)
133 + .extension<SendPageTheme>()!
134 + .textFieldButtonIconColor,
135 + )),
136 + ),
137 + ),
138 + ),
139 ],
140 if (this.options.contains(AddressTextFieldOption.qrCode) &&
141 DeviceInfo.instance.isMobile) ...[
141 - Container(
142 - width: prefixIconWidth,
143 - height: prefixIconHeight,
144 - padding: EdgeInsets.only(top: 0),
145 - child: Semantics(
146 - label: S.of(context).scan_qr_code,
147 - child: InkWell(
148 - onTap: () async => _presentQRScanner(context),
149 - child: Container(
150 - padding: EdgeInsets.all(8),
151 - decoration: BoxDecoration(
152 - color: buttonColor ??
153 - Theme.of(context).dialogTheme.backgroundColor,
154 - borderRadius: BorderRadius.all(Radius.circular(6))),
155 - child: Image.asset(
156 - 'assets/images/qr_code_icon.png',
157 - color: iconColor ??
158 - Theme.of(context)
159 - .extension<SendPageTheme>()!
160 - .textFieldButtonIconColor,
161 - )),
162 - ),
163 - ))
164 - ] else
142 SizedBox(width: 5),
143 + Container(
144 + width: prefixIconWidth,
145 + height: prefixIconHeight,
146 + padding: EdgeInsets.only(top: 0),
147 + child: Semantics(
148 + label: S.of(context).scan_qr_code,
149 + child: InkWell(
150 + onTap: () async => _presentQRScanner(context),
151 + child: Container(
152 + padding: EdgeInsets.all(8),
153 + decoration: BoxDecoration(
154 + color:
155 + buttonColor ?? Theme.of(context).dialogTheme.backgroundColor,
156 + borderRadius: BorderRadius.all(Radius.circular(6))),
157 + child: Image.asset(
158 + 'assets/images/qr_code_icon.png',
159 + color: iconColor ??
160 + Theme.of(context)
161 + .extension<SendPageTheme>()!
162 + .textFieldButtonIconColor,
163 + )),
164 + ),
165 + ),
166 + ),
167 + ],
168 if (this.options.contains(AddressTextFieldOption.addressBook)) ...[
169 + SizedBox(width: 5),
170 Container(
168 - width: prefixIconWidth,
169 - height: prefixIconHeight,
170 - padding: EdgeInsets.only(top: 0),
171 - child: Semantics(
172 - label: S.of(context).address_book,
173 - child: InkWell(
174 - onTap: () async => _presetAddressBookPicker(context),
175 - child: Container(
176 - padding: EdgeInsets.all(8),
177 - decoration: BoxDecoration(
178 - color: buttonColor ??
179 - Theme.of(context).dialogTheme.backgroundColor,
180 - borderRadius: BorderRadius.all(Radius.circular(6))),
181 - child: Image.asset(
182 - 'assets/images/open_book.png',
183 - color: iconColor ??
184 - Theme.of(context)
185 - .extension<SendPageTheme>()!
186 - .textFieldButtonIconColor,
187 - )),
188 - ),
189 - ))
190 - ]
171 + width: prefixIconWidth,
172 + height: prefixIconHeight,
173 + padding: EdgeInsets.only(top: 0),
174 + child: Semantics(
175 + label: S.of(context).address_book,
176 + child: InkWell(
177 + onTap: () async => _presetAddressBookPicker(context),
178 + child: Container(
179 + padding: EdgeInsets.all(8),
180 + decoration: BoxDecoration(
181 + color:
182 + buttonColor ?? Theme.of(context).dialogTheme.backgroundColor,
183 + borderRadius: BorderRadius.all(Radius.circular(6))),
184 + child: Image.asset(
185 + 'assets/images/open_book.png',
186 + color: iconColor ??
187 + Theme.of(context)
188 + .extension<SendPageTheme>()!
189 + .textFieldButtonIconColor,
190 + )),
191 + ),
192 + ),
193 + ),
194 + ],
195 + if (this.options.contains(AddressTextFieldOption.walletAddresses)) ...[
196 + SizedBox(width: 5),
197 + Container(
198 + width: prefixIconWidth,
199 + height: prefixIconHeight,
200 + padding: EdgeInsets.only(top: 0),
201 + child: Semantics(
202 + label: S.of(context).address_book,
203 + child: InkWell(
204 + onTap: () async => _presetWalletAddressPicker(context),
205 + child: Container(
206 + padding: EdgeInsets.all(8),
207 + decoration: BoxDecoration(
208 + color:
209 + buttonColor ?? Theme.of(context).dialogTheme.backgroundColor,
210 + borderRadius: BorderRadius.all(Radius.circular(6))),
211 + child: Image.asset(
212 + 'assets/images/open_book.png',
213 + color: iconColor ??
214 + Theme.of(context)
215 + .extension<SendPageTheme>()!
216 + .textFieldButtonIconColor,
217 + )),
218 + ),
219 + ),
220 + )
221 + ],
222 ],
223 ),
224 ))
@@ -197,7 +228,7 @@ class AddressTextField extends StatelessWidget {
228
229 Future<void> _presentQRScanner(BuildContext context) async {
230 bool isCameraPermissionGranted =
200 - await PermissionHandler.checkPermission(Permission.camera, context);
231 + await PermissionHandler.checkPermission(Permission.camera, context);
232 if (!isCameraPermissionGranted) return;
233 final code = await presentQRScanner();
234 if (code.isEmpty) {
@@ -224,6 +255,15 @@ class AddressTextField extends StatelessWidget {
255 }
256 }
257
258 + Future<void> _presetWalletAddressPicker(BuildContext context) async {
259 + final address = await Navigator.of(context).pushNamed(Routes.pickerWalletAddress);
260 +
261 + if (address is String) {
262 + controller?.text = address;
263 + onPushAddressPickerButton?.call(context);
264 + }
265 + }
266 +
267 Future<void> _pasteAddress(BuildContext context) async {
268 final clipboard = await Clipboard.getData('text/plain');
269 final address = clipboard?.text ?? '';
lib/view_model/dashboard/dashboard_view_model.dart
+30 -6
@@ -182,7 +182,8 @@ abstract class DashboardViewModelBase with Store {
182
183 final _accountTransactions = _wallet.transactionHistory.transactions.values
184 .where((tx) =>
185 - wow.wownero!.getTransactionInfoAccountId(tx) == wow.wownero!.getCurrentAccount(wallet).id)
185 + wow.wownero!.getTransactionInfoAccountId(tx) ==
186 + wow.wownero!.getCurrentAccount(wallet).id)
187 .toList();
188
189 final sortedTransactions = [..._accountTransactions];
@@ -482,6 +483,27 @@ abstract class DashboardViewModelBase with Store {
483 @computed
484 bool get hasPowNodes => wallet.type == WalletType.nano || wallet.type == WalletType.banano;
485
486 + @computed
487 + bool get hasSignMessages {
488 + switch (wallet.type) {
489 + case WalletType.monero:
490 + case WalletType.litecoin:
491 + case WalletType.bitcoin:
492 + case WalletType.bitcoinCash:
493 + case WalletType.ethereum:
494 + case WalletType.polygon:
495 + case WalletType.solana:
496 + case WalletType.nano:
497 + case WalletType.banano:
498 + case WalletType.tron:
499 + case WalletType.wownero:
500 + return true;
501 + case WalletType.haven:
502 + case WalletType.none:
503 + return false;
504 + }
505 + }
506 +
507 bool get showRepWarning {
508 if (wallet.type != WalletType.nano) {
509 return false;
@@ -575,7 +597,8 @@ abstract class DashboardViewModelBase with Store {
597 }
598
599 if (wallet.type == WalletType.wownero) {
578 - return wow.wownero!.getTransactionInfoAccountId(tx) == wow.wownero!.getCurrentAccount(wallet).id;
600 + return wow.wownero!.getTransactionInfoAccountId(tx) ==
601 + wow.wownero!.getCurrentAccount(wallet).id;
602 }
603
604 return true;
@@ -600,8 +623,8 @@ abstract class DashboardViewModelBase with Store {
623 .getTransactionHistory(wallet)
624 .transactions
625 .values
603 - .where(
604 - (tx) => monero!.getTransactionInfoAccountId(tx) == monero!.getCurrentAccount(wallet).id)
626 + .where((tx) =>
627 + monero!.getTransactionInfoAccountId(tx) == monero!.getCurrentAccount(wallet).id)
628 .toList();
629
630 transactions.addAll(_accountTransactions.map((transaction) => TransactionListItem(
@@ -613,8 +636,9 @@ abstract class DashboardViewModelBase with Store {
636 .getTransactionHistory(wallet)
637 .transactions
638 .values
616 - .where(
617 - (tx) => wow.wownero!.getTransactionInfoAccountId(tx) == wow.wownero!.getCurrentAccount(wallet).id)
639 + .where((tx) =>
640 + wow.wownero!.getTransactionInfoAccountId(tx) ==
641 + wow.wownero!.getCurrentAccount(wallet).id)
642 .toList();
643
644 transactions.addAll(_accountTransactions.map((transaction) => TransactionListItem(
lib/view_model/dashboard/sign_view_model.dart new
+55
@@ -0,0 +1,55 @@
1 +import 'package:cake_wallet/core/execution_state.dart';
2 +import 'package:cake_wallet/generated/i18n.dart';
3 +import 'package:cw_core/wallet_base.dart';
4 +import 'package:cw_core/wallet_type.dart';
5 +import 'package:mobx/mobx.dart';
6 +
7 +part 'sign_view_model.g.dart';
8 +
9 +class SignViewModel = SignViewModelBase with _$SignViewModel;
10 +
11 +abstract class SignViewModelBase with Store {
12 + SignViewModelBase(this.wallet) : state = InitialExecutionState();
13 +
14 + final WalletBase wallet;
15 +
16 + @observable
17 + ExecutionState state;
18 +
19 + @observable
20 + bool isSigning = true;
21 +
22 + bool get signIncludesAddress => [
23 + WalletType.monero,
24 + WalletType.bitcoin,
25 + WalletType.bitcoinCash,
26 + WalletType.litecoin,
27 + WalletType.haven,
28 + ].contains(wallet.type);
29 +
30 + @action
31 + Future<void> sign(String message, {String? address}) async {
32 + state = IsExecutingState();
33 + try {
34 + final signature = await wallet.signMessage(message, address: address);
35 + state = ExecutedSuccessfullyState(payload: signature);
36 + } catch (e) {
37 + state = FailureState(e.toString());
38 + }
39 + }
40 +
41 + @action
42 + Future<void> verify(String message, String signature, {String? address}) async {
43 + state = IsExecutingState();
44 + try {
45 + final sig = await wallet.verifyMessage(message, signature, address: address);
46 + if (sig) {
47 + state = ExecutedSuccessfullyState();
48 + } else {
49 + state = FailureState(S.current.signature_invalid_error);
50 + }
51 + } catch (e) {
52 + state = FailureState(e.toString());
53 + }
54 + }
55 +}
lib/view_model/wallet_address_list/wallet_address_list_view_model.dart
+9
@@ -6,6 +6,7 @@ import 'package:cake_wallet/ethereum/ethereum.dart';
6 import 'package:cake_wallet/generated/i18n.dart';
7 import 'package:cake_wallet/haven/haven.dart';
8 import 'package:cake_wallet/monero/monero.dart';
9 +import 'package:cake_wallet/nano/nano.dart';
10 import 'package:cake_wallet/polygon/polygon.dart';
11 import 'package:cake_wallet/solana/solana.dart';
12 import 'package:cake_wallet/store/app_store.dart';
@@ -438,6 +439,14 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
439 addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress));
440 }
441
442 + if (wallet.type == WalletType.nano) {
443 + addressList.add(WalletAddressListItem(
444 + isPrimary: true,
445 + name: null,
446 + address: wallet.walletAddresses.address,
447 + ));
448 + }
449 +
450 if (wallet.type == WalletType.tron) {
451 final primaryAddress = tron!.getAddress(wallet);
452
lib/view_model/wallet_creation_vm.dart
+2 -6
@@ -117,9 +117,7 @@ abstract class WalletCreationVMBase with Store {
117 DerivationInfo? getDefaultDerivation() {
118 switch (this.type) {
119 case WalletType.nano:
120 - return DerivationInfo(
121 - derivationType: DerivationType.nano,
122 - );
120 + return DerivationInfo(derivationType: DerivationType.nano);
121 case WalletType.bitcoin:
122 case WalletType.litecoin:
123 return bitcoin!.getElectrumDerivations()[DerivationType.electrum]!.first;
@@ -131,9 +129,7 @@ abstract class WalletCreationVMBase with Store {
129 DerivationInfo? getCommonRestoreDerivation() {
130 switch (this.type) {
131 case WalletType.nano:
134 - return DerivationInfo(
135 - derivationType: DerivationType.nano,
136 - );
132 + return DerivationInfo(derivationType: DerivationType.nano);
133 case WalletType.bitcoin:
134 return DerivationInfo(
135 derivationType: DerivationType.bip39,
pubspec_base.yaml
+5 -1
@@ -103,7 +103,7 @@ dependencies:
103 bitcoin_base:
104 git:
105 url: https://github.com/cake-tech/bitcoin_base
106 - ref: cake-update-v4
106 + ref: cake-update-v5
107 ledger_flutter: ^1.0.1
108 hashlib: 1.12.0
109
@@ -138,6 +138,10 @@ dependency_overrides:
138 url: https://github.com/cake-tech/web3dart.git
139 ref: cake
140 flutter_secure_storage_platform_interface: 1.0.2
141 + bitcoin_base:
142 + git:
143 + url: https://github.com/cake-tech/bitcoin_base
144 + ref: cake-update-v5
145
146 flutter_icons:
147 image_path: "assets/images/app_logo.png"
res/values/strings_ar.arb
+8
@@ -370,6 +370,7 @@
370 "max_value": "الحد الأقصى: ${value} ${currency}",
371 "memo": "مذكرة:",
372 "message": "ﺔﻟﺎﺳﺭ",
373 + "message_verified": "تم التحقق من الرسالة بنجاح",
374 "methods": " ﻕﺮﻃُ",
375 "min_amount": "الحد الأدنى: ${value}",
376 "min_value": "الحد الأدنى: ${value} ${currency}",
@@ -661,7 +662,13 @@
662 "show_keys": "اظهار السييد / المفاتيح",
663 "show_market_place": "إظهار السوق",
664 "show_seed": "عرض السييد",
665 + "sign_message": "تسجيل رسالة",
666 "sign_up": "اشتراك",
667 + "sign_verify_message": "توقيع أو التحقق من الرسالة",
668 + "sign_verify_message_sub": "قم بتوقيع أو التحقق من رسالة باستخدام المفتاح الخاص بك",
669 + "sign_verify_title": "تسجيل / تحقق",
670 + "signature": "إمضاء",
671 + "signature_invalid_error": "التوقيع غير صالح للرسالة المقدمة",
672 "signTransaction": " ﺔﻠﻣﺎﻌﻤﻟﺍ ﻊﻴﻗﻮﺗ",
673 "signup_for_card_accept_terms": "قم بالتسجيل للحصول على البطاقة وقبول الشروط.",
674 "silent_payments": "مدفوعات صامتة",
@@ -822,6 +829,7 @@
829 "value_type": "نوع القيمة",
830 "variable_pair_not_supported": "هذا الزوج المتغير غير مدعوم في التبادلات المحددة",
831 "verification": "تَحَقّق",
832 + "verify_message": "تحقق من الرسالة",
833 "verify_with_2fa": "تحقق مع Cake 2FA",
834 "version": "الإصدار ${currentVersion}",
835 "view_all": "مشاهدة الكل",
res/values/strings_bg.arb
+8
@@ -370,6 +370,7 @@
370 "max_value": "Макс: ${value} ${currency}",
371 "memo": "Мемо:",
372 "message": "Съобщение",
373 + "message_verified": "Съобщението беше успешно проверено",
374 "methods": "Методи",
375 "min_amount": "Мин: ${value}",
376 "min_value": "Мин: ${value} ${currency}",
@@ -661,7 +662,13 @@
662 "show_keys": "Покажи seed/keys",
663 "show_market_place": "Покажи пазар",
664 "show_seed": "Покажи seed",
665 + "sign_message": "Съобщение за подписване",
666 "sign_up": "Регистрация",
667 + "sign_verify_message": "Подпишете или проверете съобщението",
668 + "sign_verify_message_sub": "Подпишете или проверете съобщение с помощта на вашия личен ключ",
669 + "sign_verify_title": "Подпишете / проверете",
670 + "signature": "Подпис",
671 + "signature_invalid_error": "Подписът не е валиден за даденото съобщение",
672 "signTransaction": "Подпишете транзакция",
673 "signup_for_card_accept_terms": "Регистрайте се за картата и приемете условията.",
674 "silent_payments": "Мълчаливи плащания",
@@ -822,6 +829,7 @@
829 "value_type": "Тип стойност",
830 "variable_pair_not_supported": "Този variable pair не се поддържа от избраната борса",
831 "verification": "Потвърждаване",
832 + "verify_message": "Проверете съобщението",
833 "verify_with_2fa": "Проверете с Cake 2FA",
834 "version": "Версия ${currentVersion}",
835 "view_all": "Виж всички",
res/values/strings_cs.arb
+8
@@ -370,6 +370,7 @@
370 "max_value": "Max: ${value} ${currency}",
371 "memo": "Memo:",
372 "message": "Zpráva",
373 + "message_verified": "Zpráva byla úspěšně ověřena",
374 "methods": "Metody",
375 "min_amount": "Min: ${value}",
376 "min_value": "Min: ${value} ${currency}",
@@ -661,7 +662,13 @@
662 "show_keys": "Zobrazit seed/klíče",
663 "show_market_place": "Zobrazit trh",
664 "show_seed": "Zobrazit seed",
665 + "sign_message": "Podepsat zprávu",
666 "sign_up": "Registrovat se",
667 + "sign_verify_message": "Podepište nebo ověřte zprávu",
668 + "sign_verify_message_sub": "Podepište nebo ověřte zprávu pomocí soukromého klíče",
669 + "sign_verify_title": "Podepsat / ověřit",
670 + "signature": "Podpis",
671 + "signature_invalid_error": "Podpis není platný pro danou zprávu",
672 "signTransaction": "Podepsat transakci",
673 "signup_for_card_accept_terms": "Zaregistrujte se pro kartu a souhlaste s podmínkami.",
674 "silent_payments": "Tiché platby",
@@ -822,6 +829,7 @@
829 "value_type": "Typ hodnoty",
830 "variable_pair_not_supported": "Tento pár s tržním kurzem není ve zvolené směnárně podporován",
831 "verification": "Ověření",
832 + "verify_message": "Ověřit zprávu",
833 "verify_with_2fa": "Ověřte pomocí Cake 2FA",
834 "version": "Verze ${currentVersion}",
835 "view_all": "Zobrazit vše",
res/values/strings_de.arb
+8
@@ -370,6 +370,7 @@
370 "max_value": "Max: ${value} ${currency}",
371 "memo": "Memo:",
372 "message": "Nachricht",
373 + "message_verified": "Die Nachricht wurde erfolgreich überprüft",
374 "methods": "Methoden",
375 "min_amount": "Min: ${value}",
376 "min_value": "Min: ${value} ${currency}",
@@ -662,7 +663,13 @@
663 "show_keys": "Seed/Schlüssel anzeigen",
664 "show_market_place": "Marktplatz anzeigen",
665 "show_seed": "Seed zeigen",
666 + "sign_message": "Nachricht unterschreiben",
667 "sign_up": "Anmelden",
668 + "sign_verify_message": "Nachricht unterschreiben oder überprüfen",
669 + "sign_verify_message_sub": "Unterschreiben oder überprüfen Sie eine Nachricht mit Ihrem privaten Schlüssel",
670 + "sign_verify_title": "Zeichen / überprüfen",
671 + "signature": "Signatur",
672 + "signature_invalid_error": "Die Signatur gilt nicht für die angegebene Nachricht",
673 "signTransaction": "Transaktion unterzeichnen",
674 "signup_for_card_accept_terms": "Melden Sie sich für die Karte an und akzeptieren Sie die Bedingungen.",
675 "silent_payments": "Stille Zahlungen",
@@ -824,6 +831,7 @@
831 "value_type": "Werttyp",
832 "variable_pair_not_supported": "Dieses Variablenpaar wird von den ausgewählten Börsen nicht unterstützt",
833 "verification": "Verifizierung",
834 + "verify_message": "Nachricht überprüfen",
835 "verify_with_2fa": "Verifizieren Sie mit Cake 2FA",
836 "version": "Version ${currentVersion}",
837 "view_all": "Alle anzeigen",
res/values/strings_en.arb
+8
@@ -370,6 +370,7 @@
370 "max_value": "Max: ${value} ${currency}",
371 "memo": "Memo:",
372 "message": "Message",
373 + "message_verified": "The message was successfully verified",
374 "methods": "Methods",
375 "min_amount": "Min: ${value}",
376 "min_value": "Min: ${value} ${currency}",
@@ -662,7 +663,13 @@
663 "show_keys": "Show seed/keys",
664 "show_market_place": "Show Marketplace",
665 "show_seed": "Show seed",
666 + "sign_message": "Sign Message",
667 "sign_up": "Sign Up",
668 + "sign_verify_message": "Sign or verify message",
669 + "sign_verify_message_sub": "Sign or verify a message using your private key",
670 + "sign_verify_title": "Sign / Verify",
671 + "signature": "Signature",
672 + "signature_invalid_error": "The signature is not valid for the message given",
673 "signTransaction": "Sign Transaction",
674 "signup_for_card_accept_terms": "Sign up for the card and accept the terms.",
675 "silent_payments": "Silent Payments",
@@ -823,6 +830,7 @@
830 "value_type": "Value Type",
831 "variable_pair_not_supported": "This variable pair is not supported with the selected exchanges",
832 "verification": "Verification",
833 + "verify_message": "Verify Message",
834 "verify_with_2fa": "Verify with Cake 2FA",
835 "version": "Version ${currentVersion}",
836 "view_all": "View all",
res/values/strings_es.arb
+8
@@ -370,6 +370,7 @@
370 "max_value": "Max: ${value} ${currency}",
371 "memo": "Memorándum:",
372 "message": "Mensaje",
373 + "message_verified": "El mensaje fue verificado con éxito",
374 "methods": "Métodos",
375 "min_amount": "Mínimo: ${value}",
376 "min_value": "Min: ${value} ${currency}",
@@ -662,7 +663,13 @@
663 "show_keys": "Mostrar semilla/claves",
664 "show_market_place": "Mostrar mercado",
665 "show_seed": "Mostrar semilla",
666 + "sign_message": "Mensaje de firma",
667 "sign_up": "Registrarse",
668 + "sign_verify_message": "Firmar o verificar el mensaje",
669 + "sign_verify_message_sub": "Firmar o verificar un mensaje usando su clave privada",
670 + "sign_verify_title": "Firmar / verificar",
671 + "signature": "Firma",
672 + "signature_invalid_error": "La firma no es válida para el mensaje dado",
673 "signTransaction": "Firmar transacción",
674 "signup_for_card_accept_terms": "Regístrese para obtener la tarjeta y acepte los términos.",
675 "silent_payments": "Pagos silenciosos",
@@ -823,6 +830,7 @@
830 "value_type": "Tipo de valor",
831 "variable_pair_not_supported": "Este par de variables no es compatible con los intercambios seleccionados",
832 "verification": "Verificación",
833 + "verify_message": "Mensaje de verificación",
834 "verify_with_2fa": "Verificar con Cake 2FA",
835 "version": "Versión ${currentVersion}",
836 "view_all": "Ver todo",
res/values/strings_fr.arb
+8
@@ -370,6 +370,7 @@
370 "max_value": "Max: ${value} ${currency}",
371 "memo": "Mémo :",
372 "message": "Message",
373 + "message_verified": "Le message a été vérifié avec succès",
374 "methods": "Méthodes",
375 "min_amount": "Min : ${value}",
376 "min_value": "Min: ${value} ${currency}",
@@ -661,7 +662,13 @@
662 "show_keys": "Visualiser la phrase secrète (seed) et les clefs",
663 "show_market_place": "Afficher la place de marché",
664 "show_seed": "Visualiser la phrase secrète (seed)",
665 + "sign_message": "Signer le message",
666 "sign_up": "S'inscrire",
667 + "sign_verify_message": "Signer ou vérifier le message",
668 + "sign_verify_message_sub": "Signez ou vérifiez un message en utilisant votre clé privée",
669 + "sign_verify_title": "Signe / vérifier",
670 + "signature": "Signature",
671 + "signature_invalid_error": "La signature n'est pas valable pour le message donné",
672 "signTransaction": "Signer une transaction",
673 "signup_for_card_accept_terms": "Inscrivez-vous pour la carte et acceptez les conditions.",
674 "silent_payments": "Paiements silencieux",
@@ -822,6 +829,7 @@
829 "value_type": "Type de valeur",
830 "variable_pair_not_supported": "Cette paire variable n'est pas prise en charge avec les échanges sélectionnés",
831 "verification": "Vérification",
832 + "verify_message": "Vérifier le message",
833 "verify_with_2fa": "Vérifier avec Cake 2FA",
834 "version": "Version ${currentVersion}",
835 "view_all": "Voir tout",
res/values/strings_ha.arb
+8
@@ -370,6 +370,7 @@
370 "max_value": "Max: ${value} ${currency}",
371 "memo": "Memo:",
372 "message": "Sako",
373 + "message_verified": "An yi nasarar tabbatar da sakon",
374 "methods": "Hanyoyin",
375 "min_amount": "Min: ${value}",
376 "min_value": "Min: ${value} ${currency}",
@@ -663,7 +664,13 @@
664 "show_keys": "Nuna iri/maɓallai",
665 "show_market_place": "Nuna dan kasuwa",
666 "show_seed": "Nuna iri",
667 + "sign_message": "Sa hannu",
668 "sign_up": "Shiga",
669 + "sign_verify_message": "Shiga ko Tabbatar da Saƙo",
670 + "sign_verify_message_sub": "Shiga ko tabbatar da saƙo ta amfani da Maɓallinku na sirri",
671 + "sign_verify_title": "Sa hannu / Tabbatar",
672 + "signature": "Sa hannu",
673 + "signature_invalid_error": "Sa hannu ba shi da inganci ga sakon da aka bayar",
674 "signTransaction": "Sa hannu Ma'amala",
675 "signup_for_card_accept_terms": "Yi rajista don katin kuma karɓi sharuɗɗan.",
676 "silent_payments": "Biya silent",
@@ -824,6 +831,7 @@
831 "value_type": "Nau'in darajar",
832 "variable_pair_not_supported": "Ba a samun goyan bayan wannan m biyu tare da zaɓaɓɓun musayar",
833 "verification": "tabbatar",
834 + "verify_message": "Tabbatar saƙon",
835 "verify_with_2fa": "Tabbatar da Cake 2FA",
836 "version": "Sigar ${currentVersion}",
837 "view_all": "Duba duka",
res/values/strings_hi.arb
+8
@@ -370,6 +370,7 @@
370 "max_value": "मैक्स: ${value} ${currency}",
371 "memo": "ज्ञापन:",
372 "message": "संदेश",
373 + "message_verified": "संदेश को सफलतापूर्वक सत्यापित किया गया था",
374 "methods": "तरीकों",
375 "min_amount": "न्यूनतम: ${value}",
376 "min_value": "मिन: ${value} ${currency}",
@@ -663,7 +664,13 @@
664 "show_keys": "बीज / कुंजियाँ दिखाएँ",
665 "show_market_place": "बाज़ार दिखाएँ",
666 "show_seed": "बीज दिखाओ",
667 + "sign_message": "हस्ताक्षर संदेश",
668 "sign_up": "साइन अप करें",
669 + "sign_verify_message": "संदेश पर हस्ताक्षर या सत्यापित करें",
670 + "sign_verify_message_sub": "अपनी निजी कुंजी का उपयोग करके किसी संदेश पर हस्ताक्षर या सत्यापित करें",
671 + "sign_verify_title": "हस्ताक्षर / सत्यापित करें",
672 + "signature": "हस्ताक्षर",
673 + "signature_invalid_error": "हस्ताक्षर दिए गए संदेश के लिए मान्य नहीं है",
674 "signTransaction": "लेन-देन पर हस्ताक्षर करें",
675 "signup_for_card_accept_terms": "कार्ड के लिए साइन अप करें और शर्तें स्वीकार करें।",
676 "silent_payments": "मूक भुगतान",
@@ -824,6 +831,7 @@
831 "value_type": "मान प्रकार",
832 "variable_pair_not_supported": "यह परिवर्तनीय जोड़ी चयनित एक्सचेंजों के साथ समर्थित नहीं है",
833 "verification": "सत्यापन",
834 + "verify_message": "संदेश सत्यापित करें",
835 "verify_with_2fa": "केक 2FA के साथ सत्यापित करें",
836 "version": "संस्करण ${currentVersion}",
837 "view_all": "सभी देखें",
res/values/strings_hr.arb
+8
@@ -370,6 +370,7 @@
370 "max_value": "Maks.: ${value} ${currency}",
371 "memo": "Memo:",
372 "message": "Poruka",
373 + "message_verified": "Poruka je uspješno provjerena",
374 "methods": "Metode",
375 "min_amount": "Minimalno: ${value}",
376 "min_value": "Min.: ${value} ${currency}",
@@ -661,7 +662,13 @@
662 "show_keys": "Prikaži pristupni izraz/ključ",
663 "show_market_place": "Prikaži tržište",
664 "show_seed": "Prikaži pristupni izraz",
665 + "sign_message": "Poruka",
666 "sign_up": "Prijavite se",
667 + "sign_verify_message": "Potpisati ili provjeriti poruku",
668 + "sign_verify_message_sub": "Potpišite ili provjerite poruku pomoću privatnog ključa",
669 + "sign_verify_title": "Potpisati / provjeriti",
670 + "signature": "Potpis",
671 + "signature_invalid_error": "Potpis ne vrijedi za danu poruku",
672 "signTransaction": "Potpišite transakciju",
673 "signup_for_card_accept_terms": "Prijavite se za karticu i prihvatite uvjete.",
674 "silent_payments": "Tiha plaćanja",
@@ -822,6 +829,7 @@
829 "value_type": "Tipa vrijednosti",
830 "variable_pair_not_supported": "Ovaj par varijabli nije podržan s odabranim burzama",
831 "verification": "Potvrda",
832 + "verify_message": "Provjerite poruku",
833 "verify_with_2fa": "Provjerite s Cake 2FA",
834 "version": "Verzija ${currentVersion}",
835 "view_all": "Prikaži sve",
res/values/strings_id.arb
+8
@@ -370,6 +370,7 @@
370 "max_value": "Max: ${value} ${currency}",
371 "memo": "Memo:",
372 "message": "Pesan",
373 + "message_verified": "Pesan itu berhasil diverifikasi",
374 "methods": "Metode",
375 "min_amount": "Min: ${value}",
376 "min_value": "Min: ${value} ${currency}",
@@ -664,7 +665,13 @@
665 "show_keys": "Tampilkan seed/kunci",
666 "show_market_place": "Tampilkan Pasar",
667 "show_seed": "Tampilkan seed",
668 + "sign_message": "Pesan tanda",
669 "sign_up": "Daftar",
670 + "sign_verify_message": "Tanda tangan atau verifikasi pesan",
671 + "sign_verify_message_sub": "Menandatangani atau memverifikasi pesan menggunakan kunci pribadi Anda",
672 + "sign_verify_title": "Tanda / verifikasi",
673 + "signature": "Tanda tangan",
674 + "signature_invalid_error": "Tanda tangan tidak valid untuk pesan yang diberikan",
675 "signTransaction": "Tandatangani Transaksi",
676 "signup_for_card_accept_terms": "Daftar untuk kartu dan terima syarat dan ketentuan.",
677 "silent_payments": "Pembayaran diam",
@@ -825,6 +832,7 @@
832 "value_type": "Jenis Nilai",
833 "variable_pair_not_supported": "Pasangan variabel ini tidak didukung dengan bursa yang dipilih",
834 "verification": "Verifikasi",
835 + "verify_message": "Verifikasi pesan",
836 "verify_with_2fa": "Verifikasi dengan Cake 2FA",
837 "version": "Versi ${currentVersion}",
838 "view_all": "Lihat Semua",
res/values/strings_it.arb
+8
@@ -371,6 +371,7 @@
371 "max_value": "Max: ${value} ${currency}",
372 "memo": "Memo:",
373 "message": "Messaggio",
374 + "message_verified": "Il messaggio è stato verificato con successo",
375 "methods": "Metodi",
376 "min_amount": "Min: ${value}",
377 "min_value": "Min: ${value} ${currency}",
@@ -663,7 +664,13 @@
664 "show_keys": "Mostra seme/chiavi",
665 "show_market_place": "Mostra mercato",
666 "show_seed": "Mostra seme",
667 + "sign_message": "Messaggio di firma",
668 "sign_up": "Registrati",
669 + "sign_verify_message": "Firmare o verificare il messaggio",
670 + "sign_verify_message_sub": "Firma o verifica un messaggio utilizzando la chiave privata",
671 + "sign_verify_title": "Firmare / verificare",
672 + "signature": "Firma",
673 + "signature_invalid_error": "La firma non è valida per il messaggio dato",
674 "signTransaction": "Firma la transazione",
675 "signup_for_card_accept_terms": "Registrati per la carta e accetta i termini.",
676 "silent_payments": "Pagamenti silenziosi",
@@ -824,6 +831,7 @@
831 "value_type": "Tipo di valore",
832 "variable_pair_not_supported": "Questa coppia di variabili non è supportata con gli scambi selezionati",
833 "verification": "Verifica",
834 + "verify_message": "Verificare il messaggio",
835 "verify_with_2fa": "Verifica con Cake 2FA",
836 "version": "Versione ${currentVersion}",
837 "view_all": "Visualizza tutto",
res/values/strings_ja.arb
+8
@@ -371,6 +371,7 @@
371 "max_value": "マックス: ${value} ${currency}",
372 "memo": "メモ:",
373 "message": "メッセージ",
374 + "message_verified": "メッセージは正常に検証されました",
375 "methods": "メソッド",
376 "min_amount": "最小: ${value}",
377 "min_value": "分: ${value} ${currency}",
@@ -662,7 +663,13 @@
663 "show_keys": "シード/キーを表示する",
664 "show_market_place": "マーケットプレイスを表示",
665 "show_seed": "シードを表示",
666 + "sign_message": "署名メッセージ",
667 "sign_up": "サインアップ",
668 + "sign_verify_message": "メッセージに署名または確認します",
669 + "sign_verify_message_sub": "秘密鍵を使用してメッセージに署名または確認します",
670 + "sign_verify_title": "署名 /検証",
671 + "signature": "サイン",
672 + "signature_invalid_error": "署名は、指定されたメッセージに対して無効です",
673 "signTransaction": "トランザクションに署名する",
674 "signup_for_card_accept_terms": "カードにサインアップして、利用規約に同意してください。",
675 "silent_payments": "サイレント支払い",
@@ -823,6 +830,7 @@
830 "value_type": "値タイプ",
831 "variable_pair_not_supported": "この変数ペアは、選択した取引所ではサポートされていません",
832 "verification": "検証",
833 + "verify_message": "メッセージを確認します",
834 "verify_with_2fa": "Cake 2FA で検証する",
835 "version": "バージョン ${currentVersion}",
836 "view_all": "すべて表示",
res/values/strings_ko.arb
+8
@@ -370,6 +370,7 @@
370 "max_value": "맥스: ${value} ${currency}",
371 "memo": "메모:",
372 "message": "메시지",
373 + "message_verified": "메시지가 성공적으로 확인되었습니다",
374 "methods": "행동 양식",
375 "min_amount": "최소: ${value}",
376 "min_value": "최소: ${value} ${currency}",
@@ -662,7 +663,13 @@
663 "show_keys": "시드 / 키 표시",
664 "show_market_place": "마켓플레이스 표시",
665 "show_seed": "종자 표시",
666 + "sign_message": "서명 메시지",
667 "sign_up": "가입",
668 + "sign_verify_message": "메시지에 서명하거나 확인하십시오",
669 + "sign_verify_message_sub": "개인 키를 사용하여 메시지에 서명하거나 확인하십시오",
670 + "sign_verify_title": "서명 / 확인",
671 + "signature": "서명",
672 + "signature_invalid_error": "서명은 주어진 메시지에 유효하지 않습니다",
673 "signTransaction": "거래 서명",
674 "signup_for_card_accept_terms": "카드에 가입하고 약관에 동의합니다.",
675 "silent_payments": "조용한 지불",
@@ -823,6 +830,7 @@
830 "value_type": "가치 유형",
831 "variable_pair_not_supported": "이 변수 쌍은 선택한 교환에서 지원되지 않습니다.",
832 "verification": "검증",
833 + "verify_message": "메시지를 확인하십시오",
834 "verify_with_2fa": "케이크 2FA로 확인",
835 "version": "버전 ${currentVersion}",
836 "view_all": "모두 보기",
res/values/strings_my.arb
+8
@@ -370,6 +370,7 @@
370 "max_value": "အများဆုံး- ${value} ${currency}",
371 "memo": "မှတ်စုတို:",
372 "message": "မက်ဆေ့ချ်",
373 + "message_verified": "မက်ဆေ့ခ်ျကိုအောင်မြင်စွာအတည်ပြုခဲ့သည်",
374 "methods": "နည်းလမ်းများ",
375 "min_amount": "အနည်းဆုံး- ${value}",
376 "min_value": "အနည်းဆုံး- ${value} ${currency}",
@@ -661,7 +662,13 @@
662 "show_keys": "မျိုးစေ့ /သော့များကို ပြပါ။",
663 "show_market_place": "စျေးကွက်ကိုပြသပါ။",
664 "show_seed": "မျိုးစေ့ကိုပြပါ။",
665 + "sign_message": "လက်မှတ်စာ",
666 "sign_up": "ဆိုင်းအပ်",
667 + "sign_verify_message": "မက်ဆေ့ခ်ျကိုလက်မှတ်ထိုးသို့မဟုတ်အတည်ပြုရန်",
668 + "sign_verify_message_sub": "သင်၏ကိုယ်ပိုင်သော့ကို သုံး. မက်ဆေ့ခ်ျကိုလက်မှတ်ထိုးပါ",
669 + "sign_verify_title": "လက်မှတ်ထိုး / အတည်ပြုရန်",
670 + "signature": "လက်မှတ်",
671 + "signature_invalid_error": "အဆိုပါလက်မှတ်ပေးထားသောမက်ဆေ့ခ်ျကိုများအတွက်မမှန်ကန်ပါ",
672 "signTransaction": "ငွေလွှဲဝင်ပါ။",
673 "signup_for_card_accept_terms": "ကတ်အတွက် စာရင်းသွင်းပြီး စည်းကမ်းချက်များကို လက်ခံပါ။",
674 "silent_payments": "အသံတိတ်ငွေပေးချေမှု",
@@ -822,6 +829,7 @@
829 "value_type": "Value အမျိုးအစား",
830 "variable_pair_not_supported": "ရွေးချယ်ထားသော ဖလှယ်မှုများဖြင့် ဤပြောင်းလဲနိုင်သောအတွဲကို ပံ့ပိုးမထားပါ။",
831 "verification": "စိစစ်ခြင်း။",
832 + "verify_message": "မက်ဆေ့ခ်ျကိုအတည်ပြုရန်",
833 "verify_with_2fa": "Cake 2FA ဖြင့် စစ်ဆေးပါ။",
834 "version": "ဗားရှင်း ${currentVersion}",
835 "view_all": "အားလုံးကိုကြည့်ရှုပါ။",
res/values/strings_nl.arb
+8
@@ -370,6 +370,7 @@
370 "max_value": "Max: ${value} ${currency}",
371 "memo": "Memo:",
372 "message": "Bericht",
373 + "message_verified": "Het bericht is succesvol geverifieerd",
374 "methods": "Methoden",
375 "min_amount": "Min: ${value}",
376 "min_value": "Min: ${value} ${currency}",
@@ -661,7 +662,13 @@
662 "show_keys": "Toon zaad/sleutels",
663 "show_market_place": "Toon Marktplaats",
664 "show_seed": "Toon zaad",
665 + "sign_message": "Aanmeldingsbericht",
666 "sign_up": "Aanmelden",
667 + "sign_verify_message": "Teken of verifieer bericht",
668 + "sign_verify_message_sub": "Teken of verifieer een bericht met uw privésleutel",
669 + "sign_verify_title": "Ondertekenen / verifiëren",
670 + "signature": "Handtekening",
671 + "signature_invalid_error": "De handtekening is niet geldig voor het gegeven bericht",
672 "signTransaction": "Transactie ondertekenen",
673 "signup_for_card_accept_terms": "Meld je aan voor de kaart en accepteer de voorwaarden.",
674 "silent_payments": "Stille betalingen",
@@ -822,6 +829,7 @@
829 "value_type": "Waarde type",
830 "variable_pair_not_supported": "Dit variabelenpaar wordt niet ondersteund met de geselecteerde uitwisselingen",
831 "verification": "Verificatie",
832 + "verify_message": "Verifieer bericht",
833 "verify_with_2fa": "Controleer met Cake 2FA",
834 "version": "Versie ${currentVersion}",
835 "view_all": "Alles bekijken",
res/values/strings_pl.arb
+8
@@ -370,6 +370,7 @@
370 "max_value": "Max: ${value} ${currency}",
371 "memo": "Notatka:",
372 "message": "Wiadomość",
373 + "message_verified": "Wiadomość została pomyślnie zweryfikowana",
374 "methods": "Metody",
375 "min_amount": "Min: ${value}",
376 "min_value": "Min: ${value} ${currency}",
@@ -661,7 +662,13 @@
662 "show_keys": "Pokaż seed/klucze",
663 "show_market_place": "Pokaż rynek",
664 "show_seed": "Pokaż frazy seed",
665 + "sign_message": "Podpisuj wiadomość",
666 "sign_up": "Zarejestruj się",
667 + "sign_verify_message": "Podpisz lub zweryfikuj wiadomość",
668 + "sign_verify_message_sub": "Podpisz lub zweryfikuj wiadomość za pomocą klucza prywatnego",
669 + "sign_verify_title": "Podpisać / weryfikować",
670 + "signature": "Podpis",
671 + "signature_invalid_error": "Podpis nie jest ważny dla podanej wiadomości",
672 "signTransaction": "Podpisz transakcję",
673 "signup_for_card_accept_terms": "Zarejestruj się, aby otrzymać kartę i zaakceptuj warunki.",
674 "silent_payments": "Ciche płatności",
@@ -822,6 +829,7 @@
829 "value_type": "Typ wartości",
830 "variable_pair_not_supported": "Ta para zmiennych nie jest obsługiwana na wybranych giełdach",
831 "verification": "Weryfikacja",
832 + "verify_message": "Sprawdź wiadomość",
833 "verify_with_2fa": "Sprawdź za pomocą Cake 2FA",
834 "version": "Wersja ${currentVersion}",
835 "view_all": "Wyświetl wszystko",
res/values/strings_pt.arb
+8
@@ -371,6 +371,7 @@
371 "max_value": "Máx: ${value} ${currency}",
372 "memo": "Memorando:",
373 "message": "Mensagem",
374 + "message_verified": "A mensagem foi verificada com sucesso",
375 "methods": "Métodos",
376 "min_amount": "Mínimo: ${valor}",
377 "min_value": "Mín: ${value} ${currency}",
@@ -663,7 +664,13 @@
664 "show_keys": "Mostrar semente/chaves",
665 "show_market_place": "Mostrar mercado",
666 "show_seed": "Mostrar semente",
667 + "sign_message": "Mensagem de assinar",
668 "sign_up": "Inscrever-se",
669 + "sign_verify_message": "Assinar ou verificar mensagem",
670 + "sign_verify_message_sub": "Assine ou verifique uma mensagem usando sua chave privada",
671 + "sign_verify_title": "Assinar / verificar",
672 + "signature": "Assinatura",
673 + "signature_invalid_error": "A assinatura não é válida para a mensagem dada",
674 "signTransaction": "Assinar transação",
675 "signup_for_card_accept_terms": "Cadastre-se no cartão e aceite os termos.",
676 "silent_payments": "Pagamentos silenciosos",
@@ -824,6 +831,7 @@
831 "value_type": "Tipo de valor",
832 "variable_pair_not_supported": "Este par de variáveis não é compatível com as trocas selecionadas",
833 "verification": "Verificação",
834 + "verify_message": "Verifique a mensagem",
835 "verify_with_2fa": "Verificar com Cake 2FA",
836 "version": "Versão ${currentVersion}",
837 "view_all": "Ver todos",
res/values/strings_ru.arb
+8
@@ -370,6 +370,7 @@
370 "max_value": "Макс: ${value} ${currency}",
371 "memo": "Памятка:",
372 "message": "Сообщение",
373 + "message_verified": "Сообщение было успешно проверено",
374 "methods": "Методы",
375 "min_amount": "Минимум: ${value}",
376 "min_value": "Мин: ${value} ${currency}",
@@ -662,7 +663,13 @@
663 "show_keys": "Показать мнемоническую фразу/ключи",
664 "show_market_place": "Показать торговую площадку",
665 "show_seed": "Показать мнемоническую фразу",
666 + "sign_message": "Сообщение о знаке",
667 "sign_up": "Зарегистрироваться",
668 + "sign_verify_message": "Подписать или проверить сообщение",
669 + "sign_verify_message_sub": "Подписать или проверить сообщение, используя свой закрытый ключ",
670 + "sign_verify_title": "Знак / проверка",
671 + "signature": "Подпись",
672 + "signature_invalid_error": "Подпись недопустима для данного сообщения",
673 "signTransaction": "Подписать транзакцию",
674 "signup_for_card_accept_terms": "Подпишитесь на карту и примите условия.",
675 "silent_payments": "Молчаливые платежи",
@@ -823,6 +830,7 @@
830 "value_type": "Тип значения",
831 "variable_pair_not_supported": "Эта пара переменных не поддерживается выбранными биржами.",
832 "verification": "Проверка",
833 + "verify_message": "Проверьте сообщение",
834 "verify_with_2fa": "Подтвердить с помощью Cake 2FA",
835 "version": "Версия ${currentVersion}",
836 "view_all": "Просмотреть все",
res/values/strings_th.arb
+8
@@ -370,6 +370,7 @@
370 "max_value": "ขั้นสูง: ${value} ${currency}",
371 "memo": "หมายเหตุ:",
372 "message": "ข้อความ",
373 + "message_verified": "ข้อความได้รับการตรวจสอบอย่างประสบความสำเร็จ",
374 "methods": "วิธีการ",
375 "min_amount": "จำนวนขั้นต่ำ: ${value}",
376 "min_value": "ขั้นต่ำ: ${value} ${currency}",
@@ -661,7 +662,13 @@
662 "show_keys": "แสดงซีด/คีย์",
663 "show_market_place": "แสดงตลาดกลาง",
664 "show_seed": "แสดงซีด",
665 + "sign_message": "ลงนามข้อความ",
666 "sign_up": "สมัครสมาชิก",
667 + "sign_verify_message": "ลงชื่อเข้าใช้หรือตรวจสอบข้อความ",
668 + "sign_verify_message_sub": "ลงชื่อเข้าใช้หรือตรวจสอบข้อความโดยใช้คีย์ส่วนตัวของคุณ",
669 + "sign_verify_title": "ลงชื่อ / ตรวจสอบ",
670 + "signature": "ลายเซ็น",
671 + "signature_invalid_error": "ลายเซ็นไม่ถูกต้องสำหรับข้อความที่ให้ไว้",
672 "signTransaction": "ลงนามในการทำธุรกรรม",
673 "signup_for_card_accept_terms": "ลงทะเบียนสำหรับบัตรและยอมรับเงื่อนไข",
674 "silent_payments": "การชำระเงินเงียบ",
@@ -822,6 +829,7 @@
829 "value_type": "ประเภทค่า",
830 "variable_pair_not_supported": "คู่ความสัมพันธ์ที่เปลี่ยนแปลงได้นี้ไม่สนับสนุนกับหุ้นที่เลือก",
831 "verification": "การตรวจสอบ",
832 + "verify_message": "ยืนยันข้อความ",
833 "verify_with_2fa": "ตรวจสอบกับ Cake 2FA",
834 "version": "เวอร์ชัน ${currentVersion}",
835 "view_all": "ดูทั้งหมด",
res/values/strings_tl.arb
+8
@@ -370,6 +370,7 @@
370 "max_value": "Max: ${value} ${currency}",
371 "memo": "Memo:",
372 "message": "Mensahe",
373 + "message_verified": "Ang mensahe ay matagumpay na na -verify",
374 "methods": "Mga Paraan",
375 "min_amount": "Min: ${value}",
376 "min_value": "Min: ${value} ${currency}",
@@ -660,6 +661,12 @@
661 "show_details": "Ipakita ang mga detalye",
662 "show_keys": "Ipakita ang mga seed/key",
663 "show_market_place": "Ipakita ang Marketplace",
664 + "sign_message": "Mag -sign Message",
665 + "sign_verify_message": "Mag -sign o i -verify ang mensahe",
666 + "sign_verify_message_sub": "Mag -sign o i -verify ang isang mensahe gamit ang iyong pribadong key",
667 + "sign_verify_title": "Mag -sign / Mag -verify",
668 + "signature": "Lagda",
669 + "signature_invalid_error": "Ang lagda ay hindi wasto para sa ibinigay na mensahe",
670 "show_seed": "Ipakita ang seed",
671 "sign_up": "Mag-sign Up",
672 "signTransaction": "Mag-sign ang Transaksyon",
@@ -820,6 +827,7 @@
827 "use_testnet": "Gumamit ng testnet",
828 "value": "Halaga",
829 "value_type": "Uri ng halaga",
830 + "verify_message": "I -verify ang mensahe",
831 "variable_pair_not_supported": "Ang variable na pares na ito ay hindi suportado sa mga napiling exchange",
832 "verification": "Pag-verify",
833 "verify_with_2fa": "Mag-verify sa Cake 2FA",
res/values/strings_tr.arb
+8
@@ -370,6 +370,7 @@
370 "max_value": "En fazla: ${value} ${currency}",
371 "memo": "Memo:",
372 "message": "İleti",
373 + "message_verified": "Mesaj başarıyla doğrulandı",
374 "methods": "Yöntemler",
375 "min_amount": "Min: ${value}",
376 "min_value": "En az: ${value} ${currency}",
@@ -661,7 +662,13 @@
662 "show_keys": "Tohumları/anahtarları göster",
663 "show_market_place": "Pazar Yerini Göster",
664 "show_seed": "Tohumları göster",
665 + "sign_message": "İşaret mesajı",
666 "sign_up": "Kaydol",
667 + "sign_verify_message": "Mesajı işaretleyin veya doğrulayın",
668 + "sign_verify_message_sub": "Özel anahtarınızı kullanarak bir mesajı imzalayın veya doğrulayın",
669 + "sign_verify_title": "İşaretle / Doğrula",
670 + "signature": "İmza",
671 + "signature_invalid_error": "İmza verilen mesaj için geçerli değil",
672 "signTransaction": "İşlem İmzala",
673 "signup_for_card_accept_terms": "Kart için kaydol ve koşulları kabul et.",
674 "silent_payments": "Sessiz ödemeler",
@@ -822,6 +829,7 @@
829 "value_type": "Değer türü",
830 "variable_pair_not_supported": "Bu değişken paritesi seçilen borsalarda desteklenmemekte",
831 "verification": "Doğrulama",
832 + "verify_message": "Mesajı Doğrula",
833 "verify_with_2fa": "Cake 2FA ile Doğrulayın",
834 "version": "Sürüm ${currentVersion}",
835 "view_all": "Hepsini göster",
res/values/strings_uk.arb
+8
@@ -370,6 +370,7 @@
370 "max_value": "Макс: ${value} ${currency}",
371 "memo": "Пам’ятка:",
372 "message": "повідомлення",
373 + "message_verified": "Повідомлення було успішно перевірено",
374 "methods": "методи",
375 "min_amount": "Мінімум: ${value}",
376 "min_value": "Мін: ${value} ${currency}",
@@ -662,7 +663,13 @@
663 "show_keys": "Показати мнемонічну фразу/ключі",
664 "show_market_place": "Відображати маркетплейс",
665 "show_seed": "Показати мнемонічну фразу",
666 + "sign_message": "Підпишіть повідомлення",
667 "sign_up": "Зареєструватися",
668 + "sign_verify_message": "Підпишіть або перевірити повідомлення",
669 + "sign_verify_message_sub": "Підпишіть або перевірте повідомлення за допомогою вашого приватного ключа",
670 + "sign_verify_title": "Знак / Перевірка",
671 + "signature": "Підпис",
672 + "signature_invalid_error": "Підпис не є дійсним для наведеного повідомлення",
673 "signTransaction": "Підписати транзакцію",
674 "signup_for_card_accept_terms": "Зареєструйтеся на картку та прийміть умови.",
675 "silent_payments": "Мовчазні платежі",
@@ -823,6 +830,7 @@
830 "value_type": "Тип значення",
831 "variable_pair_not_supported": "Ця пара змінних не підтримується вибраними біржами",
832 "verification": "Перевірка",
833 + "verify_message": "Перевірте повідомлення",
834 "verify_with_2fa": "Перевірте за допомогою Cake 2FA",
835 "version": "Версія ${currentVersion}",
836 "view_all": "Переглянути все",
res/values/strings_ur.arb
+8
@@ -370,6 +370,7 @@
370 "max_value": "زیادہ سے زیادہ: ${value} ${currency}",
371 "memo": "میمو:",
372 "message": "ﻡﺎﻐﯿﭘ",
373 + "message_verified": "پیغام کی کامیابی کے ساتھ تصدیق کی گئی",
374 "methods": "ﮯﻘﯾﺮﻃ",
375 "min_amount": "کم سے کم: ${value}",
376 "min_value": "کم سے کم: ${value} ${currency}",
@@ -663,7 +664,13 @@
664 "show_keys": "بیج / چابیاں دکھائیں۔",
665 "show_market_place": "بازار دکھائیں۔",
666 "show_seed": "بیج دکھائیں۔",
667 + "sign_message": "سائن پیغام",
668 "sign_up": "سائن اپ",
669 + "sign_verify_message": "پیغام پر دستخط کریں یا تصدیق کریں",
670 + "sign_verify_message_sub": "اپنی نجی کلید کا استعمال کرتے ہوئے کسی پیغام پر دستخط کریں یا اس کی تصدیق کریں",
671 + "sign_verify_title": "سائن / تصدیق کریں",
672 + "signature": "دستخط",
673 + "signature_invalid_error": "دستخط دیئے گئے پیغام کے لئے درست نہیں ہے",
674 "signTransaction": "۔ﮟﯾﺮﮐ ﻂﺨﺘﺳﺩ ﺮﭘ ﻦﯾﺩ ﻦﯿﻟ",
675 "signup_for_card_accept_terms": "کارڈ کے لیے سائن اپ کریں اور شرائط کو قبول کریں۔",
676 "silent_payments": "خاموش ادائیگی",
@@ -824,6 +831,7 @@
831 "value_type": "قدر کی قسم",
832 "variable_pair_not_supported": "یہ متغیر جوڑا منتخب ایکسچینجز کے ساتھ تعاون یافتہ نہیں ہے۔",
833 "verification": "تصدیق",
834 + "verify_message": "پیغام کی تصدیق کریں",
835 "verify_with_2fa": "کیک 2FA سے تصدیق کریں۔",
836 "version": "ورژن ${currentVersion}",
837 "view_all": "سب دیکھیں",
res/values/strings_yo.arb
+8
@@ -371,6 +371,7 @@
371 "max_value": "kò gbọ́dọ̀ tóbi ju ${value} ${currency}",
372 "memo": "Àkọsílẹ̀:",
373 "message": "Ifiranṣẹ",
374 + "message_verified": "Ifiranṣẹ naa ni aṣeyọri ni ifijišẹ",
375 "methods": "Awọn ọna",
376 "min_amount": "kò kéré ju: ${value}",
377 "min_value": "kò gbọ́dọ̀ kéré ju ${value} ${currency}",
@@ -662,7 +663,13 @@
663 "show_keys": "Wo hóró / àwọn kọ́kọ́rọ́",
664 "show_market_place": "Wa Sopọ Pataki",
665 "show_seed": "Wo hóró",
666 + "sign_message": "Ifiranṣẹ Ami",
667 "sign_up": "Forúkọ sílẹ̀",
668 + "sign_verify_message": "Ami tabi ṣayẹwo ifiranṣẹ",
669 + "sign_verify_message_sub": "Wọle tabi ṣayẹwo ifiranṣẹ kan nipa lilo bọtini ikọkọ rẹ",
670 + "sign_verify_title": "Ami / Daju",
671 + "signature": "Ibọwọlu",
672 + "signature_invalid_error": "Ibuwọlu ko wulo fun ifiranṣẹ ti a fun",
673 "signTransaction": "Wole Idunadura",
674 "signup_for_card_accept_terms": "Ẹ f'orúkọ sílẹ̀ láti gba káàdì àti àjọrò.",
675 "silent_payments": "Awọn sisanwo ipalọlọ",
@@ -823,6 +830,7 @@
830 "value_type": "Iru iye",
831 "variable_pair_not_supported": "A kì í ṣe k'á fi àwọn ilé pàṣípààrọ̀ yìí ṣe pàṣípààrọ̀ irú owó méji yìí",
832 "verification": "Ìjẹ́rìísí",
833 + "verify_message": "Daju ifiranṣẹ",
834 "verify_with_2fa": "Ṣeẹda pẹlu Cake 2FA",
835 "version": "Àtúnse ${currentVersion}",
836 "view_all": "Wo gbogbo nǹkan kan",
res/values/strings_zh.arb
+8
@@ -370,6 +370,7 @@
370 "max_value": "最大: ${value} ${currency}",
371 "memo": "备忘录:",
372 "message": "信息",
373 + "message_verified": "该消息已成功验证",
374 "methods": "方法",
375 "min_amount": "最小值: ${value}",
376 "min_value": "最小: ${value} ${currency}",
@@ -661,7 +662,13 @@
662 "show_keys": "显示种子/密钥",
663 "show_market_place": "显示市场",
664 "show_seed": "显示种子",
665 + "sign_message": "标志消息",
666 "sign_up": "注册",
667 + "sign_verify_message": "签名或验证消息",
668 + "sign_verify_message_sub": "使用您的私钥签名或验证消息",
669 + "sign_verify_title": "签名 /验证",
670 + "signature": "签名",
671 + "signature_invalid_error": "签名对于给出的消息无效",
672 "signTransaction": "签署交易",
673 "signup_for_card_accept_terms": "注册卡并接受条款。",
674 "silent_payments": "无声付款",
@@ -822,6 +829,7 @@
829 "value_type": "值类型",
830 "variable_pair_not_supported": "所选交易所不支持此变量对",
831 "verification": "验证",
832 + "verify_message": "验证消息",
833 "verify_with_2fa": "用 Cake 2FA 验证",
834 "version": "版本 ${currentVersion}",
835 "view_all": "查看全部",