mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2024-11-16 17:27:39 +00:00
Merge remote-tracking branch 'origin/staging' into arti
This commit is contained in:
commit
38502e41d3
9 changed files with 4255 additions and 113 deletions
|
@ -96,7 +96,8 @@ class _NameYourWalletViewState extends ConsumerState<NameYourWalletView> {
|
|||
|
||||
if (mounted) {
|
||||
ref.read(mnemonicWordCountStateProvider.state).state =
|
||||
coin.possibleMnemonicLengths.last;
|
||||
coin.defaultSeedPhraseLength;
|
||||
|
||||
ref.read(pNewWalletOptions.notifier).state = null;
|
||||
|
||||
switch (widget.addWalletType) {
|
||||
|
|
|
@ -170,6 +170,11 @@ class _RestoreOptionsViewState extends ConsumerState<RestoreOptionsView> {
|
|||
|
||||
final lengths = coin.possibleMnemonicLengths;
|
||||
|
||||
final isMoneroAnd25 = coin is Monero &&
|
||||
ref.watch(mnemonicWordCountStateProvider.state).state == 25;
|
||||
final isWowneroAnd25 = coin is Wownero &&
|
||||
ref.watch(mnemonicWordCountStateProvider.state).state == 25;
|
||||
|
||||
return MasterScaffold(
|
||||
isDesktop: isDesktop,
|
||||
appBar: isDesktop
|
||||
|
@ -222,13 +227,7 @@ class _RestoreOptionsViewState extends ConsumerState<RestoreOptionsView> {
|
|||
SizedBox(
|
||||
height: isDesktop ? 40 : 24,
|
||||
),
|
||||
if ((coin is Monero &&
|
||||
ref.watch(mnemonicWordCountStateProvider.state).state ==
|
||||
25) ||
|
||||
coin is Epiccash ||
|
||||
(coin is Wownero &&
|
||||
ref.watch(mnemonicWordCountStateProvider.state).state ==
|
||||
25))
|
||||
if (isMoneroAnd25 || coin is Epiccash || isWowneroAnd25)
|
||||
Text(
|
||||
"Choose start date",
|
||||
style: isDesktop
|
||||
|
@ -240,58 +239,28 @@ class _RestoreOptionsViewState extends ConsumerState<RestoreOptionsView> {
|
|||
: STextStyles.smallMed12(context),
|
||||
textAlign: TextAlign.left,
|
||||
),
|
||||
if ((coin is Monero &&
|
||||
ref.watch(mnemonicWordCountStateProvider.state).state ==
|
||||
25) ||
|
||||
coin is Epiccash ||
|
||||
(coin is Wownero &&
|
||||
ref.watch(mnemonicWordCountStateProvider.state).state ==
|
||||
25))
|
||||
if (isMoneroAnd25 || coin is Epiccash || isWowneroAnd25)
|
||||
SizedBox(
|
||||
height: isDesktop ? 16 : 8,
|
||||
),
|
||||
if ((coin is Monero &&
|
||||
ref.watch(mnemonicWordCountStateProvider.state).state ==
|
||||
25) ||
|
||||
coin is Epiccash ||
|
||||
(coin is Wownero &&
|
||||
ref.watch(mnemonicWordCountStateProvider.state).state ==
|
||||
25))
|
||||
if (isMoneroAnd25 || coin is Epiccash || isWowneroAnd25)
|
||||
if (!isDesktop)
|
||||
RestoreFromDatePicker(
|
||||
onTap: chooseDate,
|
||||
controller: _dateController,
|
||||
),
|
||||
if ((coin is Monero &&
|
||||
ref.watch(mnemonicWordCountStateProvider.state).state ==
|
||||
25) ||
|
||||
coin is Epiccash ||
|
||||
(coin is Wownero &&
|
||||
ref.watch(mnemonicWordCountStateProvider.state).state ==
|
||||
25))
|
||||
if (isMoneroAnd25 || coin is Epiccash || isWowneroAnd25)
|
||||
if (isDesktop)
|
||||
// TODO desktop date picker
|
||||
RestoreFromDatePicker(
|
||||
onTap: chooseDesktopDate,
|
||||
controller: _dateController,
|
||||
),
|
||||
if ((coin is Monero &&
|
||||
ref.watch(mnemonicWordCountStateProvider.state).state ==
|
||||
25) ||
|
||||
coin is Epiccash ||
|
||||
(coin is Wownero &&
|
||||
ref.watch(mnemonicWordCountStateProvider.state).state ==
|
||||
25))
|
||||
if (isMoneroAnd25 || coin is Epiccash || isWowneroAnd25)
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
if ((coin is Monero &&
|
||||
ref.watch(mnemonicWordCountStateProvider.state).state ==
|
||||
25) ||
|
||||
coin is Epiccash ||
|
||||
(coin is Wownero &&
|
||||
ref.watch(mnemonicWordCountStateProvider.state).state ==
|
||||
25))
|
||||
if (isMoneroAnd25 || coin is Epiccash || isWowneroAnd25)
|
||||
RoundedWhiteContainer(
|
||||
child: Center(
|
||||
child: Text(
|
||||
|
@ -308,13 +277,7 @@ class _RestoreOptionsViewState extends ConsumerState<RestoreOptionsView> {
|
|||
),
|
||||
),
|
||||
),
|
||||
if ((coin is Monero &&
|
||||
ref.watch(mnemonicWordCountStateProvider.state).state ==
|
||||
25) ||
|
||||
coin is Epiccash ||
|
||||
(coin is Wownero &&
|
||||
ref.watch(mnemonicWordCountStateProvider.state).state ==
|
||||
25))
|
||||
if (isMoneroAnd25 || coin is Epiccash || isWowneroAnd25)
|
||||
SizedBox(
|
||||
height: isDesktop ? 24 : 16,
|
||||
),
|
||||
|
|
|
@ -179,8 +179,14 @@ class _RestoreWalletViewState extends ConsumerState<RestoreWalletView> {
|
|||
bool _isValidMnemonicWord(String word) {
|
||||
// TODO: get the actual language
|
||||
if (widget.coin is Monero) {
|
||||
final moneroWordList = libxmr.monero.getMoneroWordList("English");
|
||||
return moneroWordList.contains(word);
|
||||
switch (widget.seedWordsLength) {
|
||||
case 25:
|
||||
return libxmr.monero.getMoneroWordList("English").contains(word);
|
||||
case 16:
|
||||
return Monero.sixteenWordsWordList.contains(word);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (widget.coin is Wownero) {
|
||||
final wowneroWordList = libwow.wownero.getWowneroWordList(
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:nanodart/nanodart.dart';
|
||||
|
||||
import '../networking/http.dart';
|
||||
import 'tor_service.dart';
|
||||
import '../utilities/prefs.dart';
|
||||
import 'tor_service.dart';
|
||||
|
||||
class NanoAPI {
|
||||
static Future<
|
||||
|
@ -14,6 +15,7 @@ class NanoAPI {
|
|||
required Uri server,
|
||||
required bool representative,
|
||||
required String account,
|
||||
required Map<String, String> headers,
|
||||
}) async {
|
||||
NAccountInfo? accountInfo;
|
||||
Exception? exception;
|
||||
|
@ -23,9 +25,7 @@ class NanoAPI {
|
|||
try {
|
||||
final response = await client.post(
|
||||
url: server,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
headers: headers,
|
||||
body: jsonEncode({
|
||||
"action": "account_info",
|
||||
"representative": "true",
|
||||
|
@ -64,6 +64,7 @@ class NanoAPI {
|
|||
required String balance,
|
||||
required String privateKey,
|
||||
required String work,
|
||||
required Map<String, String> headers,
|
||||
}) async {
|
||||
final Map<String, String> block = {
|
||||
"type": "state",
|
||||
|
@ -98,7 +99,11 @@ class NanoAPI {
|
|||
|
||||
block["signature"] = signature;
|
||||
|
||||
final map = await postBlock(server: server, block: block);
|
||||
final map = await postBlock(
|
||||
server: server,
|
||||
block: block,
|
||||
headers: headers,
|
||||
);
|
||||
|
||||
if (map is Map && map["error"] != null) {
|
||||
throw Exception(map["error"].toString());
|
||||
|
@ -111,14 +116,13 @@ class NanoAPI {
|
|||
static Future<dynamic> postBlock({
|
||||
required Uri server,
|
||||
required Map<String, dynamic> block,
|
||||
required Map<String, String> headers,
|
||||
}) async {
|
||||
final HTTP client = HTTP();
|
||||
|
||||
final response = await client.post(
|
||||
url: server,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
headers: headers,
|
||||
body: jsonEncode({
|
||||
"action": "process",
|
||||
"json_block": "true",
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:nanodart/nanodart.dart';
|
||||
|
||||
import '../../../models/isar/models/blockchain_data/address.dart';
|
||||
import '../../../models/node_model.dart';
|
||||
import '../../../utilities/default_nodes.dart';
|
||||
|
@ -66,7 +67,8 @@ class Banano extends NanoCurrency {
|
|||
switch (network) {
|
||||
case CryptoCurrencyNetwork.main:
|
||||
return NodeModel(
|
||||
host: "https://kaliumapi.appditto.com/api",
|
||||
// host: "https://kaliumapi.appditto.com/api",
|
||||
host: "https://nodes.nanswap.com/BAN",
|
||||
port: 443,
|
||||
name: DefaultNodes.defaultName,
|
||||
id: DefaultNodes.buildId(this),
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,4 +1,5 @@
|
|||
import 'package:nanodart/nanodart.dart';
|
||||
|
||||
import '../../../models/isar/models/isar_models.dart';
|
||||
import '../../../models/node_model.dart';
|
||||
import '../../../utilities/default_nodes.dart';
|
||||
|
@ -66,7 +67,8 @@ class Nano extends NanoCurrency {
|
|||
switch (network) {
|
||||
case CryptoCurrencyNetwork.main:
|
||||
return NodeModel(
|
||||
host: "https://rainstorm.city/api",
|
||||
// host: "https://rainstorm.city/api",
|
||||
host: "https://nodes.nanswap.com/XNO",
|
||||
port: 443,
|
||||
name: DefaultNodes.defaultName,
|
||||
id: DefaultNodes.buildId(this),
|
||||
|
|
|
@ -3,6 +3,9 @@ import 'dart:convert';
|
|||
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:nanodart/nanodart.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
import '../../../external_api_keys.dart';
|
||||
import '../../../models/balance.dart';
|
||||
import '../../../models/isar/models/blockchain_data/address.dart';
|
||||
import '../../../models/isar/models/blockchain_data/transaction.dart';
|
||||
|
@ -18,9 +21,20 @@ import '../../../utilities/logger.dart';
|
|||
import '../../crypto_currency/intermediate/nano_currency.dart';
|
||||
import '../../models/tx_data.dart';
|
||||
import '../intermediate/bip39_wallet.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
|
||||
const _kWorkServer = "https://rpc.nano.to";
|
||||
// const _kWorkServer = "https://rpc.nano.to";
|
||||
const _kWorkServer = "https://nodes.nanswap.com/XNO";
|
||||
|
||||
Map<String, String> _buildHeaders(String url) {
|
||||
final result = {
|
||||
'Content-type': 'application/json',
|
||||
};
|
||||
if (url
|
||||
case "https://nodes.nanswap.com/XNO" || "https://nodes.nanswap.com/BAN") {
|
||||
result["nodes-api-key"] = kNanoSwapRpcApiKey;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
mixin NanoInterface<T extends NanoCurrency> on Bip39Wallet<T> {
|
||||
// since nano based coins only have a single address/account we can cache
|
||||
|
@ -36,7 +50,7 @@ mixin NanoInterface<T extends NanoCurrency> on Bip39Wallet<T> {
|
|||
return _httpClient
|
||||
.post(
|
||||
url: Uri.parse(_kWorkServer), // this should be a
|
||||
headers: {'Content-type': 'application/json'},
|
||||
headers: _buildHeaders(_kWorkServer),
|
||||
body: json.encode(
|
||||
{
|
||||
"action": "work_generate",
|
||||
|
@ -93,10 +107,6 @@ mixin NanoInterface<T extends NanoCurrency> on Bip39Wallet<T> {
|
|||
// TODO: the opening block of an account is a special case
|
||||
bool openBlock = false;
|
||||
|
||||
final headers = {
|
||||
"Content-Type": "application/json",
|
||||
};
|
||||
|
||||
// first check if the account is open:
|
||||
// get the account info (we need the frontier and representative):
|
||||
final infoBody = jsonEncode({
|
||||
|
@ -104,9 +114,10 @@ mixin NanoInterface<T extends NanoCurrency> on Bip39Wallet<T> {
|
|||
"representative": "true",
|
||||
"account": publicAddress,
|
||||
});
|
||||
final node = getCurrentNode();
|
||||
final infoResponse = await _httpClient.post(
|
||||
url: Uri.parse(getCurrentNode().host),
|
||||
headers: headers,
|
||||
url: Uri.parse(node.host),
|
||||
headers: _buildHeaders(node.host),
|
||||
body: infoBody,
|
||||
proxyInfo: prefs.useTor ? TorService.sharedInstance.getProxyInfo() : null,
|
||||
);
|
||||
|
@ -124,8 +135,8 @@ mixin NanoInterface<T extends NanoCurrency> on Bip39Wallet<T> {
|
|||
});
|
||||
|
||||
final balanceResponse = await _httpClient.post(
|
||||
url: Uri.parse(getCurrentNode().host),
|
||||
headers: headers,
|
||||
url: Uri.parse(node.host),
|
||||
headers: _buildHeaders(node.host),
|
||||
body: balanceBody,
|
||||
proxyInfo: prefs.useTor ? TorService.sharedInstance.getProxyInfo() : null,
|
||||
);
|
||||
|
@ -198,8 +209,8 @@ mixin NanoInterface<T extends NanoCurrency> on Bip39Wallet<T> {
|
|||
"block": receiveBlock,
|
||||
});
|
||||
final processResponse = await _httpClient.post(
|
||||
url: Uri.parse(getCurrentNode().host),
|
||||
headers: headers,
|
||||
url: Uri.parse(node.host),
|
||||
headers: _buildHeaders(node.host),
|
||||
body: processBody,
|
||||
proxyInfo: prefs.useTor ? TorService.sharedInstance.getProxyInfo() : null,
|
||||
);
|
||||
|
@ -212,14 +223,14 @@ mixin NanoInterface<T extends NanoCurrency> on Bip39Wallet<T> {
|
|||
}
|
||||
|
||||
Future<void> _confirmAllReceivable(String accountAddress) async {
|
||||
final node = getCurrentNode();
|
||||
final receivableResponse = await _httpClient.post(
|
||||
url: Uri.parse(getCurrentNode().host),
|
||||
headers: {"Content-Type": "application/json"},
|
||||
url: Uri.parse(node.host),
|
||||
headers: _buildHeaders(node.host),
|
||||
body: jsonEncode({
|
||||
"action": "receivable",
|
||||
"source": "true",
|
||||
"account": accountAddress,
|
||||
"count": "-1",
|
||||
}),
|
||||
proxyInfo: prefs.useTor ? TorService.sharedInstance.getProxyInfo() : null,
|
||||
);
|
||||
|
@ -247,10 +258,12 @@ mixin NanoInterface<T extends NanoCurrency> on Bip39Wallet<T> {
|
|||
final address =
|
||||
(_cachedAddress ?? await getCurrentReceivingAddress())!.value;
|
||||
|
||||
final node = getCurrentNode();
|
||||
final response = await NanoAPI.getAccountInfo(
|
||||
server: serverURI,
|
||||
representative: true,
|
||||
account: address,
|
||||
headers: _buildHeaders(node.host),
|
||||
);
|
||||
|
||||
return response.accountInfo?.representative ??
|
||||
|
@ -259,7 +272,8 @@ mixin NanoInterface<T extends NanoCurrency> on Bip39Wallet<T> {
|
|||
|
||||
Future<bool> changeRepresentative(String newRepresentative) async {
|
||||
try {
|
||||
final serverURI = Uri.parse(getCurrentNode().host);
|
||||
final node = getCurrentNode();
|
||||
final serverURI = Uri.parse(node.host);
|
||||
await updateBalance();
|
||||
final balance = info.cachedBalance.spendable.raw.toString();
|
||||
final String privateKey = await _getPrivateKeyFromMnemonic();
|
||||
|
@ -270,6 +284,7 @@ mixin NanoInterface<T extends NanoCurrency> on Bip39Wallet<T> {
|
|||
server: serverURI,
|
||||
representative: true,
|
||||
account: address,
|
||||
headers: _buildHeaders(node.host),
|
||||
);
|
||||
|
||||
if (response.accountInfo == null) {
|
||||
|
@ -287,6 +302,7 @@ mixin NanoInterface<T extends NanoCurrency> on Bip39Wallet<T> {
|
|||
balance: balance,
|
||||
privateKey: privateKey,
|
||||
work: work!,
|
||||
headers: _buildHeaders(node.host),
|
||||
);
|
||||
} catch (_) {
|
||||
rethrow;
|
||||
|
@ -331,11 +347,11 @@ mixin NanoInterface<T extends NanoCurrency> on Bip39Wallet<T> {
|
|||
|
||||
@override
|
||||
Future<bool> pingCheck() async {
|
||||
final uri = Uri.parse(getCurrentNode().host);
|
||||
|
||||
final node = getCurrentNode();
|
||||
final uri = Uri.parse(node.host);
|
||||
final response = await _httpClient.post(
|
||||
url: uri,
|
||||
headers: {"Content-Type": "application/json"},
|
||||
headers: _buildHeaders(node.host),
|
||||
body: jsonEncode(
|
||||
{
|
||||
"action": "version",
|
||||
|
@ -384,13 +400,10 @@ mixin NanoInterface<T extends NanoCurrency> on Bip39Wallet<T> {
|
|||
"account": publicAddress,
|
||||
});
|
||||
|
||||
final headers = {
|
||||
"Content-Type": "application/json",
|
||||
};
|
||||
|
||||
final node = getCurrentNode();
|
||||
final infoResponse = await _httpClient.post(
|
||||
url: Uri.parse(getCurrentNode().host),
|
||||
headers: headers,
|
||||
url: Uri.parse(node.host),
|
||||
headers: _buildHeaders(node.host),
|
||||
body: infoBody,
|
||||
proxyInfo:
|
||||
prefs.useTor ? TorService.sharedInstance.getProxyInfo() : null,
|
||||
|
@ -443,8 +456,8 @@ mixin NanoInterface<T extends NanoCurrency> on Bip39Wallet<T> {
|
|||
"block": sendBlock,
|
||||
});
|
||||
final processResponse = await _httpClient.post(
|
||||
url: Uri.parse(getCurrentNode().host),
|
||||
headers: headers,
|
||||
url: Uri.parse(node.host),
|
||||
headers: _buildHeaders(node.host),
|
||||
body: processBody,
|
||||
proxyInfo:
|
||||
prefs.useTor ? TorService.sharedInstance.getProxyInfo() : null,
|
||||
|
@ -485,6 +498,49 @@ mixin NanoInterface<T extends NanoCurrency> on Bip39Wallet<T> {
|
|||
}
|
||||
}
|
||||
|
||||
// recurse over api calls if required
|
||||
// (if more than 200 history items)
|
||||
Future<Map<String, dynamic>> _fetchAll(
|
||||
String publicAddress,
|
||||
String? previous,
|
||||
Map<String, dynamic>? data,
|
||||
) async {
|
||||
final node = getCurrentNode();
|
||||
final body = {
|
||||
"action": "account_history",
|
||||
"account": publicAddress,
|
||||
"count": "200",
|
||||
};
|
||||
|
||||
if (previous is String) {
|
||||
body["head"] = previous;
|
||||
}
|
||||
|
||||
final response = await _httpClient.post(
|
||||
url: Uri.parse(node.host),
|
||||
headers: _buildHeaders(node.host),
|
||||
body: jsonEncode(body),
|
||||
proxyInfo: prefs.useTor ? TorService.sharedInstance.getProxyInfo() : null,
|
||||
);
|
||||
|
||||
// this should really have proper type checking and error propagation but I'm out of time
|
||||
final newData =
|
||||
Map<String, dynamic>.from((await jsonDecode(response.body)) as Map);
|
||||
|
||||
if (newData["previous"] is String) {
|
||||
if (data?["history"] is List) {
|
||||
(newData["history"] as List).addAll(data!["history"] as List);
|
||||
}
|
||||
return await _fetchAll(
|
||||
publicAddress,
|
||||
newData["previous"] as String,
|
||||
newData,
|
||||
);
|
||||
}
|
||||
|
||||
return newData;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateTransactions() async {
|
||||
await updateChainHeight();
|
||||
|
@ -492,17 +548,9 @@ mixin NanoInterface<T extends NanoCurrency> on Bip39Wallet<T> {
|
|||
(_cachedAddress ?? await getCurrentReceivingAddress())!;
|
||||
final String publicAddress = receivingAddress.value;
|
||||
await _confirmAllReceivable(publicAddress);
|
||||
final response = await _httpClient.post(
|
||||
url: Uri.parse(getCurrentNode().host),
|
||||
headers: {"Content-Type": "application/json"},
|
||||
body: jsonEncode({
|
||||
"action": "account_history",
|
||||
"account": publicAddress,
|
||||
"count": "-1",
|
||||
}),
|
||||
proxyInfo: prefs.useTor ? TorService.sharedInstance.getProxyInfo() : null,
|
||||
);
|
||||
final data = await jsonDecode(response.body);
|
||||
|
||||
final data = await _fetchAll(publicAddress, null, null);
|
||||
|
||||
final transactions = data["history"] is List
|
||||
? data["history"] as List<dynamic>
|
||||
: <dynamic>[];
|
||||
|
@ -571,13 +619,11 @@ mixin NanoInterface<T extends NanoCurrency> on Bip39Wallet<T> {
|
|||
"action": "account_balance",
|
||||
"account": addressString,
|
||||
});
|
||||
final headers = {
|
||||
"Content-Type": "application/json",
|
||||
};
|
||||
|
||||
final node = getCurrentNode();
|
||||
final response = await _httpClient.post(
|
||||
url: Uri.parse(getCurrentNode().host),
|
||||
headers: headers,
|
||||
url: Uri.parse(node.host),
|
||||
headers: _buildHeaders(node.host),
|
||||
body: body,
|
||||
proxyInfo:
|
||||
prefs.useTor ? TorService.sharedInstance.getProxyInfo() : null,
|
||||
|
@ -622,12 +668,11 @@ mixin NanoInterface<T extends NanoCurrency> on Bip39Wallet<T> {
|
|||
"action": "account_info",
|
||||
"account": publicAddress,
|
||||
});
|
||||
final headers = {
|
||||
"Content-Type": "application/json",
|
||||
};
|
||||
|
||||
final node = getCurrentNode();
|
||||
final infoResponse = await _httpClient.post(
|
||||
url: Uri.parse(getCurrentNode().host),
|
||||
headers: headers,
|
||||
url: Uri.parse(node.host),
|
||||
headers: _buildHeaders(node.host),
|
||||
body: infoBody,
|
||||
proxyInfo:
|
||||
prefs.useTor ? TorService.sharedInstance.getProxyInfo() : null,
|
||||
|
|
2068
test/sixteen_word_list_sanity_test.dart
Normal file
2068
test/sixteen_word_list_sanity_test.dart
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue