mirror of
https://github.com/cypherstack/stack_wallet.git
synced 2025-01-11 05:04:35 +00:00
Merge pull request #558 from fossephate/add-nano
nano - balance fixes, sync status works, transaction details page fixes
This commit is contained in:
commit
d9d86c29bf
5 changed files with 154 additions and 63 deletions
|
@ -123,7 +123,8 @@ enum AddressType {
|
||||||
mimbleWimble,
|
mimbleWimble,
|
||||||
unknown,
|
unknown,
|
||||||
nonWallet,
|
nonWallet,
|
||||||
ethereum;
|
ethereum,
|
||||||
|
nano;
|
||||||
|
|
||||||
String get readableName {
|
String get readableName {
|
||||||
switch (this) {
|
switch (this) {
|
||||||
|
@ -143,6 +144,8 @@ enum AddressType {
|
||||||
return "Non wallet/unknown";
|
return "Non wallet/unknown";
|
||||||
case AddressType.ethereum:
|
case AddressType.ethereum:
|
||||||
return "Ethereum";
|
return "Ethereum";
|
||||||
|
case AddressType.nano:
|
||||||
|
return "Nano";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -261,6 +261,7 @@ const _AddresstypeEnumValueMap = {
|
||||||
'unknown': 5,
|
'unknown': 5,
|
||||||
'nonWallet': 6,
|
'nonWallet': 6,
|
||||||
'ethereum': 7,
|
'ethereum': 7,
|
||||||
|
'nano': 8,
|
||||||
};
|
};
|
||||||
const _AddresstypeValueEnumMap = {
|
const _AddresstypeValueEnumMap = {
|
||||||
0: AddressType.p2pkh,
|
0: AddressType.p2pkh,
|
||||||
|
@ -271,6 +272,7 @@ const _AddresstypeValueEnumMap = {
|
||||||
5: AddressType.unknown,
|
5: AddressType.unknown,
|
||||||
6: AddressType.nonWallet,
|
6: AddressType.nonWallet,
|
||||||
7: AddressType.ethereum,
|
7: AddressType.ethereum,
|
||||||
|
8: AddressType.nano,
|
||||||
};
|
};
|
||||||
|
|
||||||
Id _addressGetId(Address object) {
|
Id _addressGetId(Address object) {
|
||||||
|
|
|
@ -9,6 +9,8 @@ import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart'
|
||||||
import 'package:stackwallet/models/isar/models/blockchain_data/utxo.dart';
|
import 'package:stackwallet/models/isar/models/blockchain_data/utxo.dart';
|
||||||
import 'package:stackwallet/models/paymint/fee_object_model.dart';
|
import 'package:stackwallet/models/paymint/fee_object_model.dart';
|
||||||
import 'package:stackwallet/services/coins/coin_service.dart';
|
import 'package:stackwallet/services/coins/coin_service.dart';
|
||||||
|
import 'package:stackwallet/services/event_bus/events/global/wallet_sync_status_changed_event.dart';
|
||||||
|
import 'package:stackwallet/services/event_bus/global_event_bus.dart';
|
||||||
import 'package:stackwallet/services/mixins/coin_control_interface.dart';
|
import 'package:stackwallet/services/mixins/coin_control_interface.dart';
|
||||||
import 'package:stackwallet/services/mixins/wallet_cache.dart';
|
import 'package:stackwallet/services/mixins/wallet_cache.dart';
|
||||||
import 'package:stackwallet/services/mixins/wallet_db.dart';
|
import 'package:stackwallet/services/mixins/wallet_db.dart';
|
||||||
|
@ -16,6 +18,7 @@ import 'package:stackwallet/utilities/amount/amount.dart';
|
||||||
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
import 'package:stackwallet/utilities/enums/coin_enum.dart';
|
||||||
import 'package:stackwallet/utilities/enums/log_level_enum.dart';
|
import 'package:stackwallet/utilities/enums/log_level_enum.dart';
|
||||||
import 'package:stackwallet/utilities/logger.dart';
|
import 'package:stackwallet/utilities/logger.dart';
|
||||||
|
import 'package:tuple/tuple.dart';
|
||||||
|
|
||||||
import '../../../db/isar/main_db.dart';
|
import '../../../db/isar/main_db.dart';
|
||||||
import '../../../models/isar/models/blockchain_data/address.dart';
|
import '../../../models/isar/models/blockchain_data/address.dart';
|
||||||
|
@ -31,6 +34,8 @@ import 'dart:async';
|
||||||
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
import 'package:stackwallet/models/isar/models/isar_models.dart';
|
||||||
|
|
||||||
const int MINIMUM_CONFIRMATIONS = 1;
|
const int MINIMUM_CONFIRMATIONS = 1;
|
||||||
|
const String DEFAULT_REPRESENTATIVE =
|
||||||
|
"nano_38713x95zyjsqzx6nm1dsom1jmm668owkeb9913ax6nfgj15az3nu8xkx579";
|
||||||
|
|
||||||
class NanoWallet extends CoinServiceAPI
|
class NanoWallet extends CoinServiceAPI
|
||||||
with WalletCache, WalletDB, CoinControlInterface {
|
with WalletCache, WalletDB, CoinControlInterface {
|
||||||
|
@ -135,7 +140,7 @@ class NanoWallet extends CoinServiceAPI
|
||||||
Future<String?> requestWork(String hash) async {
|
Future<String?> requestWork(String hash) async {
|
||||||
return http
|
return http
|
||||||
.post(
|
.post(
|
||||||
Uri.parse("https://rpc.nano.to"),
|
Uri.parse("https://rpc.nano.to"), // this should be a
|
||||||
headers: {'Content-type': 'application/json'},
|
headers: {'Content-type': 'application/json'},
|
||||||
body: json.encode(
|
body: json.encode(
|
||||||
{
|
{
|
||||||
|
@ -177,8 +182,8 @@ class NanoWallet extends CoinServiceAPI
|
||||||
headers: headers,
|
headers: headers,
|
||||||
body: balanceBody,
|
body: balanceBody,
|
||||||
);
|
);
|
||||||
|
|
||||||
final balanceData = jsonDecode(balanceResponse.body);
|
final balanceData = jsonDecode(balanceResponse.body);
|
||||||
|
|
||||||
final BigInt currentBalance =
|
final BigInt currentBalance =
|
||||||
BigInt.parse(balanceData["balance"].toString());
|
BigInt.parse(balanceData["balance"].toString());
|
||||||
final BigInt txAmount = txData["recipientAmt"].raw as BigInt;
|
final BigInt txAmount = txData["recipientAmt"].raw as BigInt;
|
||||||
|
@ -270,7 +275,8 @@ class NanoWallet extends CoinServiceAPI
|
||||||
@override
|
@override
|
||||||
Future<Amount> estimateFeeFor(Amount amount, int feeRate) {
|
Future<Amount> estimateFeeFor(Amount amount, int feeRate) {
|
||||||
// fees are always 0 :)
|
// fees are always 0 :)
|
||||||
return Future.value(Amount(rawValue: BigInt.from(0), fractionDigits: 7));
|
return Future.value(
|
||||||
|
Amount(rawValue: BigInt.from(0), fractionDigits: coin.decimals));
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -295,19 +301,17 @@ class NanoWallet extends CoinServiceAPI
|
||||||
final data = jsonDecode(response.body);
|
final data = jsonDecode(response.body);
|
||||||
_balance = Balance(
|
_balance = Balance(
|
||||||
total: Amount(
|
total: Amount(
|
||||||
rawValue: (BigInt.parse(data["balance"]
|
rawValue: (BigInt.parse(data["balance"].toString()) +
|
||||||
.toString()) /*+ BigInt.parse(data["receivable"].toString())*/) ~/
|
BigInt.parse(data["receivable"].toString())),
|
||||||
BigInt.from(10).pow(23),
|
fractionDigits: coin.decimals),
|
||||||
fractionDigits: 7),
|
|
||||||
spendable: Amount(
|
spendable: Amount(
|
||||||
rawValue: BigInt.parse(data["balance"].toString()) ~/
|
rawValue: BigInt.parse(data["balance"].toString()),
|
||||||
BigInt.from(10).pow(23),
|
fractionDigits: coin.decimals),
|
||||||
fractionDigits: 7),
|
blockedTotal:
|
||||||
blockedTotal: Amount(rawValue: BigInt.parse("0"), fractionDigits: 30),
|
Amount(rawValue: BigInt.parse("0"), fractionDigits: coin.decimals),
|
||||||
pendingSpendable: Amount(
|
pendingSpendable: Amount(
|
||||||
rawValue: BigInt.parse(data["receivable"].toString()) ~/
|
rawValue: BigInt.parse(data["receivable"].toString()),
|
||||||
BigInt.from(10).pow(23),
|
fractionDigits: coin.decimals),
|
||||||
fractionDigits: 7),
|
|
||||||
);
|
);
|
||||||
await updateCachedBalance(_balance!);
|
await updateCachedBalance(_balance!);
|
||||||
}
|
}
|
||||||
|
@ -317,17 +321,38 @@ class NanoWallet extends CoinServiceAPI
|
||||||
// TODO: the opening block of an account is a special case
|
// TODO: the opening block of an account is a special case
|
||||||
bool openBlock = false;
|
bool openBlock = false;
|
||||||
|
|
||||||
|
final headers = {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
};
|
||||||
|
|
||||||
// our address:
|
// our address:
|
||||||
final String publicAddress = await getAddressFromMnemonic();
|
final String publicAddress = await getAddressFromMnemonic();
|
||||||
|
|
||||||
|
// first check if the account is open:
|
||||||
|
// get the account info (we need the frontier and representative):
|
||||||
|
final infoBody = jsonEncode({
|
||||||
|
"action": "account_info",
|
||||||
|
"representative": "true",
|
||||||
|
"account": publicAddress,
|
||||||
|
});
|
||||||
|
final infoResponse = await http.post(
|
||||||
|
Uri.parse(getCurrentNode().host),
|
||||||
|
headers: headers,
|
||||||
|
body: infoBody,
|
||||||
|
);
|
||||||
|
final infoData = jsonDecode(infoResponse.body);
|
||||||
|
|
||||||
|
if (infoData["error"] != null) {
|
||||||
|
// account is not open yet, we need to create an open block:
|
||||||
|
openBlock = true;
|
||||||
|
}
|
||||||
|
|
||||||
// first get the account balance:
|
// first get the account balance:
|
||||||
final balanceBody = jsonEncode({
|
final balanceBody = jsonEncode({
|
||||||
"action": "account_balance",
|
"action": "account_balance",
|
||||||
"account": publicAddress,
|
"account": publicAddress,
|
||||||
});
|
});
|
||||||
final headers = {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
};
|
|
||||||
final balanceResponse = await http.post(
|
final balanceResponse = await http.post(
|
||||||
Uri.parse(getCurrentNode().host),
|
Uri.parse(getCurrentNode().host),
|
||||||
headers: headers,
|
headers: headers,
|
||||||
|
@ -340,21 +365,13 @@ class NanoWallet extends CoinServiceAPI
|
||||||
final BigInt txAmount = BigInt.parse(amountRaw);
|
final BigInt txAmount = BigInt.parse(amountRaw);
|
||||||
final BigInt balanceAfterTx = currentBalance + txAmount;
|
final BigInt balanceAfterTx = currentBalance + txAmount;
|
||||||
|
|
||||||
// get the account info (we need the frontier and representative):
|
String frontier = infoData["frontier"].toString();
|
||||||
final infoBody = jsonEncode({
|
String representative = infoData["representative"].toString();
|
||||||
"action": "account_info",
|
|
||||||
"representative": "true",
|
|
||||||
"account": publicAddress,
|
|
||||||
});
|
|
||||||
final infoResponse = await http.post(
|
|
||||||
Uri.parse(getCurrentNode().host),
|
|
||||||
headers: headers,
|
|
||||||
body: infoBody,
|
|
||||||
);
|
|
||||||
|
|
||||||
String frontier = jsonDecode(infoResponse.body)["frontier"].toString();
|
if (openBlock) {
|
||||||
final String representative =
|
// we don't have a representative set yet:
|
||||||
jsonDecode(infoResponse.body)["representative"].toString();
|
representative = DEFAULT_REPRESENTATIVE;
|
||||||
|
}
|
||||||
|
|
||||||
// link = send block hash:
|
// link = send block hash:
|
||||||
final String link = blockHash;
|
final String link = blockHash;
|
||||||
|
@ -450,11 +467,12 @@ class NanoWallet extends CoinServiceAPI
|
||||||
|
|
||||||
Future<void> updateTransactions() async {
|
Future<void> updateTransactions() async {
|
||||||
await confirmAllReceivable();
|
await confirmAllReceivable();
|
||||||
|
final String publicAddress = await getAddressFromMnemonic();
|
||||||
final response = await http.post(Uri.parse(getCurrentNode().host),
|
final response = await http.post(Uri.parse(getCurrentNode().host),
|
||||||
headers: {"Content-Type": "application/json"},
|
headers: {"Content-Type": "application/json"},
|
||||||
body: jsonEncode({
|
body: jsonEncode({
|
||||||
"action": "account_history",
|
"action": "account_history",
|
||||||
"account": await getAddressFromMnemonic(),
|
"account": publicAddress,
|
||||||
"count": "-1",
|
"count": "-1",
|
||||||
}));
|
}));
|
||||||
final data = await jsonDecode(response.body);
|
final data = await jsonDecode(response.body);
|
||||||
|
@ -462,14 +480,14 @@ class NanoWallet extends CoinServiceAPI
|
||||||
if (transactions.isEmpty) {
|
if (transactions.isEmpty) {
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
List<Transaction> transactionList = [];
|
List<Tuple2<Transaction, Address?>> transactionList = [];
|
||||||
for (var tx in transactions) {
|
for (var tx in transactions) {
|
||||||
var typeString = tx["type"].toString();
|
var typeString = tx["type"].toString();
|
||||||
TransactionType type = TransactionType.unknown;
|
TransactionType transactionType = TransactionType.unknown;
|
||||||
if (typeString == "send") {
|
if (typeString == "send") {
|
||||||
type = TransactionType.outgoing;
|
transactionType = TransactionType.outgoing;
|
||||||
} else if (typeString == "receive") {
|
} else if (typeString == "receive") {
|
||||||
type = TransactionType.incoming;
|
transactionType = TransactionType.incoming;
|
||||||
}
|
}
|
||||||
final amount = Amount(
|
final amount = Amount(
|
||||||
rawValue: BigInt.parse(tx["amount"].toString()),
|
rawValue: BigInt.parse(tx["amount"].toString()),
|
||||||
|
@ -477,25 +495,45 @@ class NanoWallet extends CoinServiceAPI
|
||||||
);
|
);
|
||||||
|
|
||||||
var transaction = Transaction(
|
var transaction = Transaction(
|
||||||
walletId: walletId,
|
walletId: walletId,
|
||||||
txid: tx["hash"].toString(),
|
txid: tx["hash"].toString(),
|
||||||
timestamp: int.parse(tx["local_timestamp"].toString()),
|
timestamp: int.parse(tx["local_timestamp"].toString()),
|
||||||
type: type,
|
type: transactionType,
|
||||||
subType: TransactionSubType.none,
|
subType: TransactionSubType.none,
|
||||||
amount: 0,
|
amount: 0,
|
||||||
amountString: amount.toJsonString(),
|
amountString: amount.toJsonString(),
|
||||||
fee: 0,
|
fee: 0,
|
||||||
height: int.parse(tx["height"].toString()),
|
height: int.parse(tx["height"].toString()),
|
||||||
isCancelled: false,
|
isCancelled: false,
|
||||||
isLelantus: false,
|
isLelantus: false,
|
||||||
slateId: "",
|
slateId: "",
|
||||||
otherData: "",
|
otherData: "",
|
||||||
inputs: [],
|
inputs: [],
|
||||||
outputs: [],
|
outputs: [],
|
||||||
nonce: 0);
|
nonce: 0,
|
||||||
transactionList.add(transaction);
|
);
|
||||||
|
|
||||||
|
Address address = Address(
|
||||||
|
walletId: walletId,
|
||||||
|
publicKey: [],
|
||||||
|
value: transactionType == TransactionType.incoming
|
||||||
|
? publicAddress
|
||||||
|
: tx["account"].toString(),
|
||||||
|
derivationIndex: 0,
|
||||||
|
derivationPath: null,
|
||||||
|
type: transactionType == TransactionType.incoming
|
||||||
|
? AddressType.nonWallet
|
||||||
|
: AddressType.nano,
|
||||||
|
subType: transactionType == TransactionType.incoming
|
||||||
|
? AddressSubType.receiving
|
||||||
|
: AddressSubType.nonWallet,
|
||||||
|
);
|
||||||
|
Tuple2<Transaction, Address> tuple = Tuple2(transaction, address);
|
||||||
|
transactionList.add(tuple);
|
||||||
}
|
}
|
||||||
await db.putTransactions(transactionList);
|
|
||||||
|
await db.addNewTransactionData(transactionList, walletId);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -504,8 +542,8 @@ class NanoWallet extends CoinServiceAPI
|
||||||
Future<void> fullRescan(
|
Future<void> fullRescan(
|
||||||
int maxUnusedAddressGap, int maxNumberOfIndexesToCheck) async {
|
int maxUnusedAddressGap, int maxNumberOfIndexesToCheck) async {
|
||||||
await _prefs.init();
|
await _prefs.init();
|
||||||
await updateBalance();
|
|
||||||
await updateTransactions();
|
await updateTransactions();
|
||||||
|
await updateBalance();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -552,7 +590,7 @@ class NanoWallet extends CoinServiceAPI
|
||||||
value: publicAddress,
|
value: publicAddress,
|
||||||
publicKey: [], // TODO: add public key
|
publicKey: [], // TODO: add public key
|
||||||
derivationIndex: 0,
|
derivationIndex: 0,
|
||||||
derivationPath: DerivationPath(),
|
derivationPath: null,
|
||||||
type: AddressType.unknown,
|
type: AddressType.unknown,
|
||||||
subType: AddressSubType.receiving,
|
subType: AddressSubType.receiving,
|
||||||
);
|
);
|
||||||
|
@ -650,8 +688,7 @@ class NanoWallet extends CoinServiceAPI
|
||||||
value: publicAddress,
|
value: publicAddress,
|
||||||
publicKey: [], // TODO: add public key
|
publicKey: [], // TODO: add public key
|
||||||
derivationIndex: 0,
|
derivationIndex: 0,
|
||||||
derivationPath: DerivationPath()
|
derivationPath: null,
|
||||||
..value = "0/0", // TODO: Check if this is true
|
|
||||||
type: AddressType.unknown,
|
type: AddressType.unknown,
|
||||||
subType: AddressSubType.receiving,
|
subType: AddressSubType.receiving,
|
||||||
);
|
);
|
||||||
|
@ -668,8 +705,36 @@ class NanoWallet extends CoinServiceAPI
|
||||||
@override
|
@override
|
||||||
Future<void> refresh() async {
|
Future<void> refresh() async {
|
||||||
await _prefs.init();
|
await _prefs.init();
|
||||||
await updateBalance();
|
|
||||||
await updateTransactions();
|
GlobalEventBus.instance.fire(
|
||||||
|
WalletSyncStatusChangedEvent(
|
||||||
|
WalletSyncStatus.syncing,
|
||||||
|
walletId,
|
||||||
|
coin,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await updateChainHeight();
|
||||||
|
await updateTransactions();
|
||||||
|
await updateBalance();
|
||||||
|
|
||||||
|
GlobalEventBus.instance.fire(
|
||||||
|
WalletSyncStatusChangedEvent(
|
||||||
|
WalletSyncStatus.synced,
|
||||||
|
walletId,
|
||||||
|
coin,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
GlobalEventBus.instance.fire(
|
||||||
|
WalletSyncStatusChangedEvent(
|
||||||
|
WalletSyncStatus.unableToSync,
|
||||||
|
walletId,
|
||||||
|
coin,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -723,4 +788,25 @@ class NanoWallet extends CoinServiceAPI
|
||||||
bool validateAddress(String address) {
|
bool validateAddress(String address) {
|
||||||
return NanoAccounts.isValid(NanoAccountType.NANO, address);
|
return NanoAccounts.isValid(NanoAccountType.NANO, address);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> updateChainHeight() async {
|
||||||
|
final String publicAddress = await getAddressFromMnemonic();
|
||||||
|
// first get the account balance:
|
||||||
|
final infoBody = jsonEncode({
|
||||||
|
"action": "account_info",
|
||||||
|
"account": publicAddress,
|
||||||
|
});
|
||||||
|
final headers = {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
};
|
||||||
|
final infoResponse = await http.post(
|
||||||
|
Uri.parse(getCurrentNode().host),
|
||||||
|
headers: headers,
|
||||||
|
body: infoBody,
|
||||||
|
);
|
||||||
|
final infoData = jsonDecode(infoResponse.body);
|
||||||
|
|
||||||
|
final int height = int.parse(infoData["confirmation_height"].toString());
|
||||||
|
await updateCachedChainHeight(height);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,7 +90,7 @@ class PriceAPI {
|
||||||
Uri.parse("https://api.coingecko.com/api/v3/coins/markets?vs_currency"
|
Uri.parse("https://api.coingecko.com/api/v3/coins/markets?vs_currency"
|
||||||
"=${baseCurrency.toLowerCase()}"
|
"=${baseCurrency.toLowerCase()}"
|
||||||
"&ids=monero,bitcoin,litecoin,ecash,epic-cash,zcoin,dogecoin,"
|
"&ids=monero,bitcoin,litecoin,ecash,epic-cash,zcoin,dogecoin,"
|
||||||
"bitcoin-cash,namecoin,wownero,ethereum,particl"
|
"bitcoin-cash,namecoin,wownero,ethereum,particl,nano"
|
||||||
"&order=market_cap_desc&per_page=50&page=1&sparkline=false");
|
"&order=market_cap_desc&per_page=50&page=1&sparkline=false");
|
||||||
|
|
||||||
final coinGeckoResponse = await client.get(
|
final coinGeckoResponse = await client.get(
|
||||||
|
|
|
@ -33,7 +33,7 @@ abstract class Constants {
|
||||||
BigInt.parse("1000000000000000000000000000000");
|
BigInt.parse("1000000000000000000000000000000");
|
||||||
static final BigInt _satsPerCoin = BigInt.from(100000000);
|
static final BigInt _satsPerCoin = BigInt.from(100000000);
|
||||||
static const int _decimalPlaces = 8;
|
static const int _decimalPlaces = 8;
|
||||||
static const int _decimalPlacesNano = 6;
|
static const int _decimalPlacesNano = 30;
|
||||||
static const int _decimalPlacesWownero = 11;
|
static const int _decimalPlacesWownero = 11;
|
||||||
static const int _decimalPlacesMonero = 12;
|
static const int _decimalPlacesMonero = 12;
|
||||||
static const int _decimalPlacesEthereum = 18;
|
static const int _decimalPlacesEthereum = 18;
|
||||||
|
|
Loading…
Reference in a new issue