feat: add nano

This commit is contained in:
detherminal 2023-05-19 13:20:16 +03:00
parent 524f4c5514
commit 8f89f19b91
17 changed files with 1012 additions and 71 deletions

View file

@ -2008,6 +2008,7 @@ class ThemeAssets {
late final String wownero;
late final String namecoin;
late final String particl;
late final String nano;
late final String bitcoinImage;
late final String bitcoincashImage;
late final String dogecoinImage;
@ -2019,6 +2020,7 @@ class ThemeAssets {
late final String wowneroImage;
late final String namecoinImage;
late final String particlImage;
late final String nanoImage;
late final String bitcoinImageSecondary;
late final String bitcoincashImageSecondary;
late final String dogecoinImageSecondary;
@ -2030,6 +2032,7 @@ class ThemeAssets {
late final String wowneroImageSecondary;
late final String namecoinImageSecondary;
late final String particlImageSecondary;
late final String nanoImageSecondary;
late final String? loadingGif;
late final String? background;
@ -2101,6 +2104,8 @@ class ThemeAssets {
"$applicationThemesDirectoryPath/$themeId/assets/${json["namecoin"] as String}"
..particl =
"$applicationThemesDirectoryPath/$themeId/assets/${json["particl"] as String}"
..nano =
"$applicationThemesDirectoryPath/$themeId/assets/${json["bitcoin"] as String}" // TODO: Change this to nano
..bitcoinImage =
"$applicationThemesDirectoryPath/$themeId/assets/${json["bitcoin_image"] as String}"
..bitcoincashImage =
@ -2123,6 +2128,8 @@ class ThemeAssets {
"$applicationThemesDirectoryPath/$themeId/assets/${json["namecoin_image"] as String}"
..particlImage =
"$applicationThemesDirectoryPath/$themeId/assets/${json["particl_image"] as String}"
..nanoImage =
"$applicationThemesDirectoryPath/$themeId/assets/${json["bitcoin_image"] as String}" // TODO: Change this to nano
..bitcoinImageSecondary =
"$applicationThemesDirectoryPath/$themeId/assets/${json["bitcoin_image_secondary"] as String}"
..bitcoincashImageSecondary =
@ -2145,6 +2152,8 @@ class ThemeAssets {
"$applicationThemesDirectoryPath/$themeId/assets/${json["namecoin_image_secondary"] as String}"
..particlImageSecondary =
"$applicationThemesDirectoryPath/$themeId/assets/${json["particl_image_secondary"] as String}"
..nanoImageSecondary =
"$applicationThemesDirectoryPath/$themeId/assets/${json["bitcoin_image_secondary"] as String}" // TODO: Change this to nano
..loadingGif = json["loading_gif"] is String
? "$applicationThemesDirectoryPath/$themeId/assets/${json["loading_gif"] as String}"
: null

View file

@ -17807,108 +17807,123 @@ const ThemeAssetsSchema = Schema(
name: r'namecoinImageSecondary',
type: IsarType.string,
),
r'particl': PropertySchema(
r'nano': PropertySchema(
id: 32,
name: r'nano',
type: IsarType.string,
),
r'nanoImage': PropertySchema(
id: 33,
name: r'nanoImage',
type: IsarType.string,
),
r'nanoImageSecondary': PropertySchema(
id: 34,
name: r'nanoImageSecondary',
type: IsarType.string,
),
r'particl': PropertySchema(
id: 35,
name: r'particl',
type: IsarType.string,
),
r'particlImage': PropertySchema(
id: 33,
id: 36,
name: r'particlImage',
type: IsarType.string,
),
r'particlImageSecondary': PropertySchema(
id: 34,
id: 37,
name: r'particlImageSecondary',
type: IsarType.string,
),
r'personaEasy': PropertySchema(
id: 35,
id: 38,
name: r'personaEasy',
type: IsarType.string,
),
r'personaIncognito': PropertySchema(
id: 36,
id: 39,
name: r'personaIncognito',
type: IsarType.string,
),
r'receive': PropertySchema(
id: 37,
id: 40,
name: r'receive',
type: IsarType.string,
),
r'receiveCancelled': PropertySchema(
id: 38,
id: 41,
name: r'receiveCancelled',
type: IsarType.string,
),
r'receivePending': PropertySchema(
id: 39,
id: 42,
name: r'receivePending',
type: IsarType.string,
),
r'send': PropertySchema(
id: 40,
id: 43,
name: r'send',
type: IsarType.string,
),
r'sendCancelled': PropertySchema(
id: 41,
id: 44,
name: r'sendCancelled',
type: IsarType.string,
),
r'sendPending': PropertySchema(
id: 42,
id: 45,
name: r'sendPending',
type: IsarType.string,
),
r'stack': PropertySchema(
id: 43,
id: 46,
name: r'stack',
type: IsarType.string,
),
r'stackIcon': PropertySchema(
id: 44,
id: 47,
name: r'stackIcon',
type: IsarType.string,
),
r'themePreview': PropertySchema(
id: 45,
id: 48,
name: r'themePreview',
type: IsarType.string,
),
r'themeSelector': PropertySchema(
id: 46,
id: 49,
name: r'themeSelector',
type: IsarType.string,
),
r'txExchange': PropertySchema(
id: 47,
id: 50,
name: r'txExchange',
type: IsarType.string,
),
r'txExchangeFailed': PropertySchema(
id: 48,
id: 51,
name: r'txExchangeFailed',
type: IsarType.string,
),
r'txExchangePending': PropertySchema(
id: 49,
id: 52,
name: r'txExchangePending',
type: IsarType.string,
),
r'wownero': PropertySchema(
id: 50,
id: 53,
name: r'wownero',
type: IsarType.string,
),
r'wowneroImage': PropertySchema(
id: 51,
id: 54,
name: r'wowneroImage',
type: IsarType.string,
),
r'wowneroImageSecondary': PropertySchema(
id: 52,
id: 55,
name: r'wowneroImageSecondary',
type: IsarType.string,
)
@ -17967,6 +17982,9 @@ int _themeAssetsEstimateSize(
bytesCount += 3 + object.namecoin.length * 3;
bytesCount += 3 + object.namecoinImage.length * 3;
bytesCount += 3 + object.namecoinImageSecondary.length * 3;
bytesCount += 3 + object.nano.length * 3;
bytesCount += 3 + object.nanoImage.length * 3;
bytesCount += 3 + object.nanoImageSecondary.length * 3;
bytesCount += 3 + object.particl.length * 3;
bytesCount += 3 + object.particlImage.length * 3;
bytesCount += 3 + object.particlImageSecondary.length * 3;
@ -18029,27 +18047,30 @@ void _themeAssetsSerialize(
writer.writeString(offsets[29], object.namecoin);
writer.writeString(offsets[30], object.namecoinImage);
writer.writeString(offsets[31], object.namecoinImageSecondary);
writer.writeString(offsets[32], object.particl);
writer.writeString(offsets[33], object.particlImage);
writer.writeString(offsets[34], object.particlImageSecondary);
writer.writeString(offsets[35], object.personaEasy);
writer.writeString(offsets[36], object.personaIncognito);
writer.writeString(offsets[37], object.receive);
writer.writeString(offsets[38], object.receiveCancelled);
writer.writeString(offsets[39], object.receivePending);
writer.writeString(offsets[40], object.send);
writer.writeString(offsets[41], object.sendCancelled);
writer.writeString(offsets[42], object.sendPending);
writer.writeString(offsets[43], object.stack);
writer.writeString(offsets[44], object.stackIcon);
writer.writeString(offsets[45], object.themePreview);
writer.writeString(offsets[46], object.themeSelector);
writer.writeString(offsets[47], object.txExchange);
writer.writeString(offsets[48], object.txExchangeFailed);
writer.writeString(offsets[49], object.txExchangePending);
writer.writeString(offsets[50], object.wownero);
writer.writeString(offsets[51], object.wowneroImage);
writer.writeString(offsets[52], object.wowneroImageSecondary);
writer.writeString(offsets[32], object.nano);
writer.writeString(offsets[33], object.nanoImage);
writer.writeString(offsets[34], object.nanoImageSecondary);
writer.writeString(offsets[35], object.particl);
writer.writeString(offsets[36], object.particlImage);
writer.writeString(offsets[37], object.particlImageSecondary);
writer.writeString(offsets[38], object.personaEasy);
writer.writeString(offsets[39], object.personaIncognito);
writer.writeString(offsets[40], object.receive);
writer.writeString(offsets[41], object.receiveCancelled);
writer.writeString(offsets[42], object.receivePending);
writer.writeString(offsets[43], object.send);
writer.writeString(offsets[44], object.sendCancelled);
writer.writeString(offsets[45], object.sendPending);
writer.writeString(offsets[46], object.stack);
writer.writeString(offsets[47], object.stackIcon);
writer.writeString(offsets[48], object.themePreview);
writer.writeString(offsets[49], object.themeSelector);
writer.writeString(offsets[50], object.txExchange);
writer.writeString(offsets[51], object.txExchangeFailed);
writer.writeString(offsets[52], object.txExchangePending);
writer.writeString(offsets[53], object.wownero);
writer.writeString(offsets[54], object.wowneroImage);
writer.writeString(offsets[55], object.wowneroImageSecondary);
}
ThemeAssets _themeAssetsDeserialize(
@ -18091,27 +18112,30 @@ ThemeAssets _themeAssetsDeserialize(
object.namecoin = reader.readString(offsets[29]);
object.namecoinImage = reader.readString(offsets[30]);
object.namecoinImageSecondary = reader.readString(offsets[31]);
object.particl = reader.readString(offsets[32]);
object.particlImage = reader.readString(offsets[33]);
object.particlImageSecondary = reader.readString(offsets[34]);
object.personaEasy = reader.readString(offsets[35]);
object.personaIncognito = reader.readString(offsets[36]);
object.receive = reader.readString(offsets[37]);
object.receiveCancelled = reader.readString(offsets[38]);
object.receivePending = reader.readString(offsets[39]);
object.send = reader.readString(offsets[40]);
object.sendCancelled = reader.readString(offsets[41]);
object.sendPending = reader.readString(offsets[42]);
object.stack = reader.readString(offsets[43]);
object.stackIcon = reader.readString(offsets[44]);
object.themePreview = reader.readString(offsets[45]);
object.themeSelector = reader.readString(offsets[46]);
object.txExchange = reader.readString(offsets[47]);
object.txExchangeFailed = reader.readString(offsets[48]);
object.txExchangePending = reader.readString(offsets[49]);
object.wownero = reader.readString(offsets[50]);
object.wowneroImage = reader.readString(offsets[51]);
object.wowneroImageSecondary = reader.readString(offsets[52]);
object.nano = reader.readString(offsets[32]);
object.nanoImage = reader.readString(offsets[33]);
object.nanoImageSecondary = reader.readString(offsets[34]);
object.particl = reader.readString(offsets[35]);
object.particlImage = reader.readString(offsets[36]);
object.particlImageSecondary = reader.readString(offsets[37]);
object.personaEasy = reader.readString(offsets[38]);
object.personaIncognito = reader.readString(offsets[39]);
object.receive = reader.readString(offsets[40]);
object.receiveCancelled = reader.readString(offsets[41]);
object.receivePending = reader.readString(offsets[42]);
object.send = reader.readString(offsets[43]);
object.sendCancelled = reader.readString(offsets[44]);
object.sendPending = reader.readString(offsets[45]);
object.stack = reader.readString(offsets[46]);
object.stackIcon = reader.readString(offsets[47]);
object.themePreview = reader.readString(offsets[48]);
object.themeSelector = reader.readString(offsets[49]);
object.txExchange = reader.readString(offsets[50]);
object.txExchangeFailed = reader.readString(offsets[51]);
object.txExchangePending = reader.readString(offsets[52]);
object.wownero = reader.readString(offsets[53]);
object.wowneroImage = reader.readString(offsets[54]);
object.wowneroImageSecondary = reader.readString(offsets[55]);
return object;
}
@ -18228,6 +18252,12 @@ P _themeAssetsDeserializeProp<P>(
return (reader.readString(offset)) as P;
case 52:
return (reader.readString(offset)) as P;
case 53:
return (reader.readString(offset)) as P;
case 54:
return (reader.readString(offset)) as P;
case 55:
return (reader.readString(offset)) as P;
default:
throw IsarError('Unknown property with id $propertyId');
}
@ -22614,6 +22644,409 @@ extension ThemeAssetsQueryFilter
});
}
QueryBuilder<ThemeAssets, ThemeAssets, QAfterFilterCondition> nanoEqualTo(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'nano',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<ThemeAssets, ThemeAssets, QAfterFilterCondition> nanoGreaterThan(
String value, {
bool include = false,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.greaterThan(
include: include,
property: r'nano',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<ThemeAssets, ThemeAssets, QAfterFilterCondition> nanoLessThan(
String value, {
bool include = false,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.lessThan(
include: include,
property: r'nano',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<ThemeAssets, ThemeAssets, QAfterFilterCondition> nanoBetween(
String lower,
String upper, {
bool includeLower = true,
bool includeUpper = true,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.between(
property: r'nano',
lower: lower,
includeLower: includeLower,
upper: upper,
includeUpper: includeUpper,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<ThemeAssets, ThemeAssets, QAfterFilterCondition> nanoStartsWith(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.startsWith(
property: r'nano',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<ThemeAssets, ThemeAssets, QAfterFilterCondition> nanoEndsWith(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.endsWith(
property: r'nano',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<ThemeAssets, ThemeAssets, QAfterFilterCondition> nanoContains(
String value,
{bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.contains(
property: r'nano',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<ThemeAssets, ThemeAssets, QAfterFilterCondition> nanoMatches(
String pattern,
{bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.matches(
property: r'nano',
wildcard: pattern,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<ThemeAssets, ThemeAssets, QAfterFilterCondition> nanoIsEmpty() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'nano',
value: '',
));
});
}
QueryBuilder<ThemeAssets, ThemeAssets, QAfterFilterCondition>
nanoIsNotEmpty() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.greaterThan(
property: r'nano',
value: '',
));
});
}
QueryBuilder<ThemeAssets, ThemeAssets, QAfterFilterCondition>
nanoImageEqualTo(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'nanoImage',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<ThemeAssets, ThemeAssets, QAfterFilterCondition>
nanoImageGreaterThan(
String value, {
bool include = false,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.greaterThan(
include: include,
property: r'nanoImage',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<ThemeAssets, ThemeAssets, QAfterFilterCondition>
nanoImageLessThan(
String value, {
bool include = false,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.lessThan(
include: include,
property: r'nanoImage',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<ThemeAssets, ThemeAssets, QAfterFilterCondition>
nanoImageBetween(
String lower,
String upper, {
bool includeLower = true,
bool includeUpper = true,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.between(
property: r'nanoImage',
lower: lower,
includeLower: includeLower,
upper: upper,
includeUpper: includeUpper,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<ThemeAssets, ThemeAssets, QAfterFilterCondition>
nanoImageStartsWith(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.startsWith(
property: r'nanoImage',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<ThemeAssets, ThemeAssets, QAfterFilterCondition>
nanoImageEndsWith(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.endsWith(
property: r'nanoImage',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<ThemeAssets, ThemeAssets, QAfterFilterCondition>
nanoImageContains(String value, {bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.contains(
property: r'nanoImage',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<ThemeAssets, ThemeAssets, QAfterFilterCondition>
nanoImageMatches(String pattern, {bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.matches(
property: r'nanoImage',
wildcard: pattern,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<ThemeAssets, ThemeAssets, QAfterFilterCondition>
nanoImageIsEmpty() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'nanoImage',
value: '',
));
});
}
QueryBuilder<ThemeAssets, ThemeAssets, QAfterFilterCondition>
nanoImageIsNotEmpty() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.greaterThan(
property: r'nanoImage',
value: '',
));
});
}
QueryBuilder<ThemeAssets, ThemeAssets, QAfterFilterCondition>
nanoImageSecondaryEqualTo(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'nanoImageSecondary',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<ThemeAssets, ThemeAssets, QAfterFilterCondition>
nanoImageSecondaryGreaterThan(
String value, {
bool include = false,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.greaterThan(
include: include,
property: r'nanoImageSecondary',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<ThemeAssets, ThemeAssets, QAfterFilterCondition>
nanoImageSecondaryLessThan(
String value, {
bool include = false,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.lessThan(
include: include,
property: r'nanoImageSecondary',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<ThemeAssets, ThemeAssets, QAfterFilterCondition>
nanoImageSecondaryBetween(
String lower,
String upper, {
bool includeLower = true,
bool includeUpper = true,
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.between(
property: r'nanoImageSecondary',
lower: lower,
includeLower: includeLower,
upper: upper,
includeUpper: includeUpper,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<ThemeAssets, ThemeAssets, QAfterFilterCondition>
nanoImageSecondaryStartsWith(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.startsWith(
property: r'nanoImageSecondary',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<ThemeAssets, ThemeAssets, QAfterFilterCondition>
nanoImageSecondaryEndsWith(
String value, {
bool caseSensitive = true,
}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.endsWith(
property: r'nanoImageSecondary',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<ThemeAssets, ThemeAssets, QAfterFilterCondition>
nanoImageSecondaryContains(String value, {bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.contains(
property: r'nanoImageSecondary',
value: value,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<ThemeAssets, ThemeAssets, QAfterFilterCondition>
nanoImageSecondaryMatches(String pattern, {bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.matches(
property: r'nanoImageSecondary',
wildcard: pattern,
caseSensitive: caseSensitive,
));
});
}
QueryBuilder<ThemeAssets, ThemeAssets, QAfterFilterCondition>
nanoImageSecondaryIsEmpty() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'nanoImageSecondary',
value: '',
));
});
}
QueryBuilder<ThemeAssets, ThemeAssets, QAfterFilterCondition>
nanoImageSecondaryIsNotEmpty() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.greaterThan(
property: r'nanoImageSecondary',
value: '',
));
});
}
QueryBuilder<ThemeAssets, ThemeAssets, QAfterFilterCondition> particlEqualTo(
String value, {
bool caseSensitive = true,

View file

@ -717,6 +717,7 @@ class _NodeFormState extends ConsumerState<NodeForm> {
case Coin.firoTestNet:
case Coin.dogecoinTestNet:
case Coin.epicCash:
case Coin.nano:
return false;
case Coin.ethereum:

View file

@ -13,6 +13,7 @@ import 'package:stackwallet/services/coins/firo/firo_wallet.dart';
import 'package:stackwallet/services/coins/litecoin/litecoin_wallet.dart';
import 'package:stackwallet/services/coins/monero/monero_wallet.dart';
import 'package:stackwallet/services/coins/namecoin/namecoin_wallet.dart';
import 'package:stackwallet/services/coins/nano/nano_wallet.dart';
import 'package:stackwallet/services/coins/particl/particl_wallet.dart';
import 'package:stackwallet/services/coins/wownero/wownero_wallet.dart';
import 'package:stackwallet/services/transaction_notification_tracker.dart';
@ -223,6 +224,15 @@ abstract class CoinServiceAPI {
client: client,
);
case Coin.nano:
return NanoWallet(
walletId: walletId,
walletName: walletName,
coin: coin,
tracker: tracker,
secureStore: secureStorageInterface
);
case Coin.dogecoinTestNet:
return DogecoinWallet(
walletId: walletId,

View file

@ -0,0 +1,416 @@
import 'dart:convert';
import 'package:isar/isar.dart';
import 'package:nanodart/nanodart.dart';
import 'package:http/http.dart' as http;
import 'package:stackwallet/models/balance.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/transaction.dart';
import 'package:stackwallet/models/isar/models/blockchain_data/utxo.dart';
import 'package:stackwallet/models/paymint/fee_object_model.dart';
import 'package:stackwallet/services/coins/coin_service.dart';
import 'package:stackwallet/services/mixins/coin_control_interface.dart';
import 'package:stackwallet/services/mixins/wallet_cache.dart';
import 'package:stackwallet/services/mixins/wallet_db.dart';
import 'package:stackwallet/utilities/amount/amount.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import '../../../db/isar/main_db.dart';
import '../../../models/isar/models/blockchain_data/address.dart';
import '../../../models/node_model.dart';
import '../../../utilities/default_nodes.dart';
import '../../../utilities/flutter_secure_storage_interface.dart';
import '../../../utilities/prefs.dart';
import '../../node_service.dart';
import '../../transaction_notification_tracker.dart';
import 'dart:async';
import 'package:stackwallet/models/isar/models/isar_models.dart';
const int MINIMUM_CONFIRMATIONS = 1;
class NanoWallet extends CoinServiceAPI with WalletCache, WalletDB, CoinControlInterface {
NanoWallet({
required String walletId,
required String walletName,
required Coin coin,
required TransactionNotificationTracker tracker,
required SecureStorageInterface secureStore,
MainDB? mockableOverride,
}) {
txTracker = tracker;
_walletId = walletId;
_walletName = walletName;
_coin = coin;
_secureStore = secureStore;
initCache(walletId, coin);
initWalletDB(mockableOverride: mockableOverride);
}
NodeModel? _xnoNode;
@override
Future<String?> get mnemonicPassphrase => _secureStore.read(
key: '${_walletId}_mnemonicPassphrase',
);
@override
Future<String?> get mnemonicString =>
_secureStore.read(key: '${_walletId}_mnemonic');
Future<String> getSeedFromMnemonic() async {
var mnemonic = await mnemonicString;
return NanoMnemomics.mnemonicListToSeed(mnemonic!.split(" "));
}
Future<String> getPrivateKeyFromMnemonic() async {
var mnemonic = await mnemonicString;
var seed = NanoMnemomics.mnemonicListToSeed(mnemonic!.split(" "));
return NanoKeys.seedToPrivate(seed, 0);
}
Future<String> getAddressFromMnemonic() async {
var mnemonic = await mnemonicString;
var seed = NanoMnemomics.mnemonicListToSeed(mnemonic!.split(' '));
var address = NanoAccounts.createAccount(NanoAccountType.NANO, NanoKeys.createPublicKey(NanoKeys.seedToPrivate(seed, 0)));
return address;
}
Future<String> getPublicKeyFromMnemonic() async {
var mnemonic = await mnemonicString;
if (mnemonic == null) {
return "";
} else {
var seed = NanoMnemomics.mnemonicListToSeed(mnemonic.split(" "));
return NanoKeys.createPublicKey(NanoKeys.seedToPrivate(seed, 0));
}
}
@override
String get walletId => _walletId;
late String _walletId;
@override
String get walletName => _walletName;
late String _walletName;
@override
set walletName(String name) => _walletName = name;
@override
set isFavorite(bool markFavorite) {
_isFavorite = markFavorite;
updateCachedIsFavorite(markFavorite);
}
@override
bool get isFavorite => _isFavorite ??= getCachedIsFavorite();
bool? _isFavorite;
@override
Coin get coin => _coin;
late Coin _coin;
late SecureStorageInterface _secureStore;
late final TransactionNotificationTracker txTracker;
final _prefs = Prefs.instance;
bool _shouldAutoSync = false;
@override
bool get shouldAutoSync => _shouldAutoSync;
@override
set shouldAutoSync(bool shouldAutoSync) => _shouldAutoSync = shouldAutoSync;
@override
Balance get balance => _balance ??= getCachedBalance();
Balance? _balance;
@override
Future<String> confirmSend({required Map<String, dynamic> txData}) {
// TODO: implement confirmSend
throw UnimplementedError();
}
@override
Future<String> get currentReceivingAddress => getAddressFromMnemonic();
@override
Future<Amount> estimateFeeFor(Amount amount, int feeRate) {
// TODO: implement estimateFeeFor
throw UnimplementedError();
}
@override
Future<void> exit() async {
_hasCalledExit = true;
}
@override
// TODO: implement fees
Future<FeeObject> get fees => throw UnimplementedError();
Future<void> updateBalance() async {
final body = jsonEncode({
"action": "account_balance",
"account": await getAddressFromMnemonic(),
});
final headers = {
"Content-Type": "application/json",
};
final response = await http.post(Uri.parse(getCurrentNode().host), headers: headers, body: body);
final data = jsonDecode(response.body);
_balance = Balance(
total: Amount(rawValue: (BigInt.parse(data["balance"].toString()) + BigInt.parse(data["receivable"].toString())) ~/ BigInt.from(10).pow(23), fractionDigits: 7),
spendable: Amount(rawValue: BigInt.parse(data["balance"].toString()) ~/ BigInt.from(10).pow(23), fractionDigits: 7),
blockedTotal: Amount(rawValue: BigInt.parse("0"), fractionDigits: 30),
pendingSpendable: Amount(rawValue: BigInt.parse(data["receivable"].toString()) ~/ BigInt.from(10).pow(23), fractionDigits: 7),
);
await updateCachedBalance(_balance!);
}
Future<void> confirmAllReceivable() async {
// TODO: Implement this function
}
Future<void> updateTransactions() async {
await confirmAllReceivable();
final response = await http.post(Uri.parse(getCurrentNode().host), headers: {"Content-Type": "application/json"}, body: jsonEncode({"action": "account_history", "account": await getAddressFromMnemonic(), "count": "-1"}));
final data = await jsonDecode(response.body);
final transactions = data["history"] as List<dynamic>;
if (transactions.isEmpty) {
return;
} else {
List<Transaction> transactionList = [];
for (var tx in transactions) {
var typeString = tx["type"].toString();
TransactionType type = TransactionType.unknown;
if (typeString == "send") {
type = TransactionType.outgoing;
} else if (typeString == "receive") {
type = TransactionType.incoming;
}
var intAmount = int.parse((BigInt.parse(tx["amount"].toString()) ~/ BigInt.from(10).pow(23)).toString());
var strAmount = jsonEncode({
"raw": intAmount.toString(),
"fractionDigits": 7,
});
var transaction = Transaction(
walletId: walletId,
txid: tx["hash"].toString(),
timestamp: int.parse(tx["local_timestamp"].toString()),
type: type,
subType: TransactionSubType.none,
amount: intAmount,
amountString: strAmount,
fee: 0, // TODO: Use real fee?
height: int.parse(tx["height"].toString()),
isCancelled: false,
isLelantus: false,
slateId: "",
otherData: "",
inputs: [],
outputs: [],
nonce: 0
);
transactionList.add(transaction);
}
await db.putTransactions(transactionList);
return;
}
}
@override
Future<void> fullRescan(int maxUnusedAddressGap, int maxNumberOfIndexesToCheck) async {
await _prefs.init();
await updateBalance();
await updateTransactions();
}
@override
Future<bool> generateNewAddress() {
// TODO: implement generateNewAddress
throw UnimplementedError();
}
@override
bool get hasCalledExit => _hasCalledExit;
bool _hasCalledExit = false;
@override
Future<void> initializeExisting() async {
await _prefs.init();
}
@override
Future<void> initializeNew() async {
if ((await mnemonicString) != null || (await mnemonicPassphrase) != null) {
throw Exception(
"Attempted to overwrite mnemonic on generate new wallet!");
}
await _prefs.init();
String seed = NanoSeeds.generateSeed();
final mnemonic = NanoMnemomics.seedToMnemonic(seed);
await _secureStore.write(
key: '${_walletId}_mnemonic',
value: mnemonic.join(' '),
);
await _secureStore.write(
key: '${_walletId}_mnemonicPassphrase',
value: "",
);
String privateKey = NanoKeys.seedToPrivate(seed, 0);
String publicKey = NanoKeys.createPublicKey(privateKey);
String publicAddress = NanoAccounts.createAccount(NanoAccountType.NANO, publicKey);
final address = Address(
walletId: walletId,
value: publicAddress,
publicKey: [], // TODO: add public key
derivationIndex: 0,
derivationPath: DerivationPath(),
type: AddressType.unknown,
subType: AddressSubType.receiving,
);
await db.putAddress(address);
await Future.wait([
updateCachedId(walletId),
updateCachedIsFavorite(false)
]);
}
@override
bool get isConnected => _isConnected;
bool _isConnected = false;
@override
bool get isRefreshing => refreshMutex;
bool refreshMutex = false;
@override
// TODO: implement maxFee
Future<int> get maxFee => throw UnimplementedError();
@override
Future<List<String>> get mnemonic => _getMnemonicList();
Future<List<String>> _getMnemonicList() async {
final _mnemonicString = await mnemonicString;
if (_mnemonicString == null) {
return [];
}
final List<String> data = _mnemonicString.split(' ');
return data;
}
@override
Future<Map<String, dynamic>> prepareSend({required String address, required Amount amount, Map<String, dynamic>? args}) {
// TODO: implement prepareSend
throw UnimplementedError();
}
@override
Future<void> recoverFromMnemonic({required String mnemonic, String? mnemonicPassphrase, required int maxUnusedAddressGap, required int maxNumberOfIndexesToCheck, required int height}) async {
try {
if ((await mnemonicString) != null ||
(await this.mnemonicPassphrase) != null) {
throw Exception("Attempted to overwrite mnemonic on restore!");
}
await _secureStore.write(
key: '${_walletId}_mnemonic', value: mnemonic.trim());
await _secureStore.write(
key: '${_walletId}_mnemonicPassphrase',
value: mnemonicPassphrase ?? "",
);
String seed = NanoMnemomics.mnemonicListToSeed(mnemonic.split(" "));
String privateKey = NanoKeys.seedToPrivate(seed, 0);
String publicKey = NanoKeys.createPublicKey(privateKey);
String publicAddress = NanoAccounts.createAccount(NanoAccountType.NANO, publicKey);
final address = Address(
walletId: walletId,
value: publicAddress,
publicKey: [], // TODO: add public key
derivationIndex: 0,
derivationPath: DerivationPath()..value = "0/0", // TODO: Check if this is true
type: AddressType.unknown,
subType: AddressSubType.receiving,
);
await db.putAddress(address);
await Future.wait([
updateCachedId(walletId),
updateCachedIsFavorite(false)
]);
} catch (e) {
rethrow;
}
}
@override
Future<void> refresh() async {
await _prefs.init();
await updateBalance();
await updateTransactions();
}
@override
int get storedChainHeight => getCachedChainHeight();
NodeModel getCurrentNode() {
return _xnoNode ??
NodeService(secureStorageInterface: _secureStore)
.getPrimaryNodeFor(coin: coin) ??
DefaultNodes.getNodeFor(coin);
}
@override
Future<bool> testNetworkConnection() {
http.get(Uri.parse("${getCurrentNode().host}?action=version")).then((response) {
if (response.statusCode == 200) {
return true;
}
});
return Future.value(false);
}
@override
Future<List<Transaction>> get transactions => db.getTransactions(walletId).findAll();
@override
Future<void> updateNode(bool shouldRefresh) async {
_xnoNode = NodeService(secureStorageInterface: _secureStore)
.getPrimaryNodeFor(coin: coin) ??
DefaultNodes.getNodeFor(coin);
if (shouldRefresh) {
unawaited(refresh());
}
}
@override
Future<void> updateSentCachedTxData(Map<String, dynamic> txData) {
// TODO: implement updateSentCachedTxData
throw UnimplementedError();
}
@override
// TODO: implement utxos
Future<List<UTXO>> get utxos => throw UnimplementedError();
@override
bool validateAddress(String address) {
return NanoAccounts.isValid(NanoAccountType.NANO, address);
}
}

View file

@ -32,5 +32,7 @@ final coinIconProvider = Provider.family<String, Coin>((ref, coin) {
return assets.particl;
case Coin.ethereum:
return assets.ethereum;
case Coin.nano:
return assets.nano;
}
});

View file

@ -36,6 +36,8 @@ final coinImageProvider = Provider.family<String, Coin>((ref, coin) {
return assets.dogecoinImage;
case Coin.ethereum:
return assets.ethereumImage;
case Coin.nano:
return assets.nano;
}
});
@ -43,17 +45,21 @@ final coinImageSecondaryProvider = Provider.family<String, Coin>((ref, coin) {
final assets = ref.watch(themeProvider).assets;
switch (coin) {
case Coin.bitcoin:
case Coin.bitcoinTestNet:
return assets.bitcoinImageSecondary;
case Coin.litecoin:
case Coin.litecoinTestNet:
return assets.litecoinImageSecondary;
case Coin.bitcoincash:
case Coin.bitcoincashTestnet:
return assets.bitcoincashImageSecondary;
case Coin.dogecoin:
case Coin.dogecoinTestNet:
return assets.dogecoinImageSecondary;
case Coin.epicCash:
return assets.epicCashImageSecondary;
case Coin.firo:
case Coin.firoTestNet:
return assets.firoImageSecondary;
case Coin.monero:
return assets.moneroImageSecondary;
@ -63,15 +69,9 @@ final coinImageSecondaryProvider = Provider.family<String, Coin>((ref, coin) {
return assets.namecoinImageSecondary;
case Coin.particl:
return assets.particlImageSecondary;
case Coin.bitcoinTestNet:
return assets.bitcoinImageSecondary;
case Coin.bitcoincashTestnet:
return assets.bitcoincashImageSecondary;
case Coin.firoTestNet:
return assets.firoImageSecondary;
case Coin.dogecoinTestNet:
return assets.dogecoinImageSecondary;
case Coin.ethereum:
return assets.ethereumImageSecondary;
case Coin.nano:
return assets.nano;
}
});

View file

@ -17,6 +17,7 @@ class CoinThemeColorDefault {
Color get namecoin => const Color(0xFF91B1E1);
Color get wownero => const Color(0xFFED80C1);
Color get particl => const Color(0xFF8175BD);
Color get nano => const Color(0xFF209CE9);
Color forCoin(Coin coin) {
switch (coin) {
@ -47,6 +48,8 @@ class CoinThemeColorDefault {
return wownero;
case Coin.particl:
return particl;
case Coin.nano:
return nano;
}
}
}

View file

@ -1684,6 +1684,8 @@ class StackColors extends ThemeExtension<StackColors> {
return _coin.wownero;
case Coin.particl:
return _coin.particl;
case Coin.nano:
return _coin.nano;
}
}

View file

@ -3,6 +3,7 @@ import 'dart:convert';
import 'package:bitcoindart/bitcoindart.dart';
import 'package:crypto/crypto.dart';
import 'package:flutter_libepiccash/epic_cash.dart';
import 'package:nanodart/nanodart.dart';
import 'package:stackwallet/services/coins/bitcoincash/bitcoincash_wallet.dart';
import 'package:stackwallet/services/coins/dogecoin/dogecoin_wallet.dart';
import 'package:stackwallet/services/coins/firo/firo_wallet.dart';
@ -71,6 +72,8 @@ class AddressUtils {
return Address.validateAddress(address, namecoin, namecoin.bech32!);
case Coin.particl:
return Address.validateAddress(address, particl);
case Coin.nano:
return NanoAccounts.isValid(NanoAccountType.NANO, address);
case Coin.bitcoinTestNet:
return Address.validateAddress(address, testnet);
case Coin.litecoinTestNet:

View file

@ -41,6 +41,8 @@ Uri getDefaultBlockExplorerUrlFor({
return Uri.parse("https://chainz.cryptoid.info/nmc/tx.dws?$txid.htm");
case Coin.particl:
return Uri.parse("https://chainz.cryptoid.info/part/tx.dws?$txid.htm");
case Coin.nano:
return Uri.parse("https://www.nanolooker.com/block/$txid");
}
}

View file

@ -61,6 +61,7 @@ abstract class Constants {
case Coin.epicCash:
case Coin.namecoin:
case Coin.particl:
case Coin.nano: // TODO: Check this: https://nano.org/en/faq#what-are-the-units-of-nano
return _satsPerCoin;
case Coin.wownero:
@ -89,6 +90,7 @@ abstract class Constants {
case Coin.epicCash:
case Coin.namecoin:
case Coin.particl:
case Coin.nano:
return _decimalPlaces;
case Coin.wownero:
@ -119,6 +121,7 @@ abstract class Constants {
case Coin.ethereum:
case Coin.namecoin:
case Coin.particl:
case Coin.nano:
values.addAll([24, 21, 18, 15, 12]);
break;
@ -172,6 +175,9 @@ abstract class Constants {
case Coin.particl:
return 600;
case Coin.nano: // TODO: Verify this
return 3;
}
}

View file

@ -171,6 +171,18 @@ abstract class DefaultNodes {
isFailover: true,
isDown: false);
static NodeModel get nano => NodeModel(
host: "https://rainstorm.city/api",
port: 443,
name: defaultName,
id: _nodeId(Coin.nano),
useSSL: true,
enabled: true,
coinName: Coin.nano.name,
isFailover: true,
isDown: false
);
static NodeModel get bitcoinTestnet => NodeModel(
host: "bitcoin-testnet.stackwallet.com",
port: 51002,
@ -254,6 +266,9 @@ abstract class DefaultNodes {
case Coin.particl:
return particl;
case Coin.nano:
return nano;
case Coin.bitcoinTestNet:
return bitcoinTestnet;

View file

@ -16,6 +16,7 @@ import 'package:stackwallet/services/coins/namecoin/namecoin_wallet.dart'
import 'package:stackwallet/services/coins/particl/particl_wallet.dart'
as particl;
import 'package:stackwallet/services/coins/wownero/wownero_wallet.dart' as wow;
import 'package:stackwallet/services/coins/nano/nano_wallet.dart' as nano;
import 'package:stackwallet/utilities/constants.dart';
enum Coin {
@ -30,6 +31,7 @@ enum Coin {
namecoin,
particl,
wownero,
nano,
///
@ -71,6 +73,8 @@ extension CoinExt on Coin {
return "Wownero";
case Coin.namecoin:
return "Namecoin";
case Coin.nano:
return "Nano";
case Coin.bitcoinTestNet:
return "tBitcoin";
case Coin.litecoinTestNet:
@ -108,6 +112,8 @@ extension CoinExt on Coin {
return "WOW";
case Coin.namecoin:
return "NMC";
case Coin.nano:
return "XNO";
case Coin.bitcoinTestNet:
return "tBTC";
case Coin.litecoinTestNet:
@ -146,6 +152,8 @@ extension CoinExt on Coin {
return "wownero";
case Coin.namecoin:
return "namecoin";
case Coin.nano:
return "nano";
case Coin.bitcoinTestNet:
return "bitcoin";
case Coin.litecoinTestNet:
@ -179,6 +187,7 @@ extension CoinExt on Coin {
case Coin.ethereum:
case Coin.monero:
case Coin.wownero:
case Coin.nano: // TODO: Check this
return false;
}
}
@ -203,6 +212,7 @@ extension CoinExt on Coin {
case Coin.litecoinTestNet:
case Coin.bitcoincashTestnet:
case Coin.firoTestNet:
case Coin.nano:
return false;
}
}
@ -220,6 +230,7 @@ extension CoinExt on Coin {
case Coin.ethereum:
case Coin.monero:
case Coin.wownero:
case Coin.nano:
return false;
case Coin.dogecoinTestNet:
@ -244,6 +255,7 @@ extension CoinExt on Coin {
case Coin.ethereum:
case Coin.monero:
case Coin.wownero:
case Coin.nano:
return this;
case Coin.dogecoinTestNet:
@ -302,6 +314,9 @@ extension CoinExt on Coin {
case Coin.namecoin:
return nmc.MINIMUM_CONFIRMATIONS;
case Coin.nano:
return nano.MINIMUM_CONFIRMATIONS;
}
}
@ -383,6 +398,10 @@ Coin coinFromPrettyName(String name) {
case "wownero":
return Coin.wownero;
case "Nano":
case "nano":
return Coin.nano;
default:
throw ArgumentError.value(
name,
@ -426,6 +445,8 @@ Coin coinFromTickerCaseInsensitive(String ticker) {
return Coin.dogecoinTestNet;
case "wow":
return Coin.wownero;
case "xno":
return Coin.nano;
default:
throw ArgumentError.value(
ticker, "name", "No Coin enum value with that ticker");

View file

@ -33,6 +33,7 @@ extension DerivePathTypeExt on DerivePathType {
case Coin.epicCash:
case Coin.monero:
case Coin.wownero:
case Coin.nano:
throw UnsupportedError(
"$coin does not use bitcoin style derivation paths");
}

View file

@ -578,6 +578,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.1"
fixnum_nanodart:
dependency: transitive
description:
name: fixnum_nanodart
sha256: "4b0132d11ecddc0d2ca64b6d7dee6726db432ed02cac1349d7532a08be5c54fc"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
flare_flutter:
dependency: "direct main"
description:
@ -1093,6 +1101,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.0.1"
nanodart:
dependency: "direct main"
description:
name: nanodart
sha256: "4b2f42d60307b54e8cf384d6193a567d07f8efd773858c0d5948246153c13282"
url: "https://pub.dev"
source: hosted
version: "2.0.0"
nm:
dependency: transitive
description:

View file

@ -153,6 +153,7 @@ dependencies:
rational: ^2.2.2
archive: ^3.3.2
desktop_drop: ^0.4.1
nanodart: ^2.0.0
dev_dependencies:
flutter_test: