WIP: Add namecoin

This commit is contained in:
Likho 2022-09-12 14:01:42 +02:00
parent 6a418c4215
commit 84694fa1dd
13 changed files with 183 additions and 104 deletions

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32"><g fill="none" fill-rule="evenodd"><circle cx="16" cy="16" r="16" fill="#186C9D"/><path fill="#FFF" fill-rule="nonzero" d="M19.261 23.5l.001-.002a1.8 1.8 0 0 0 .458-.05c.876-.205 1.617-.97 1.793-1.796L25 8.556l-2.772-.014-2.286 8.568-6.18-8.597-.004.004.003-.01L12.74 8.5v.001a1.9 1.9 0 0 0-.459.049c-.875.206-1.616.971-1.793 1.796L7 23.445l2.773.012 2.285-8.568 6.18 8.598h.003l1.02.013zm-6.593-10.894l.483-1.81 6.181 8.599-.483 1.81-6.18-8.6z"/></g></svg>

After

Width:  |  Height:  |  Size: 520 B

View file

@ -116,6 +116,7 @@ class _AddEditNodeViewState extends ConsumerState<AddEditNodeView> {
case Coin.bitcoincash:
case Coin.dogecoin:
case Coin.firo:
case Coin.namecoin:
case Coin.bitcoinTestNet:
case Coin.firoTestNet:
case Coin.dogecoinTestNet:
@ -528,6 +529,7 @@ class _NodeFormState extends ConsumerState<NodeForm> {
case Coin.bitcoin:
case Coin.dogecoin:
case Coin.firo:
case Coin.namecoin:
case Coin.bitcoincash:
case Coin.bitcoinTestNet:
case Coin.firoTestNet:

View file

@ -3040,6 +3040,36 @@ class BitcoinCashWallet extends CoinServiceAPI {
return available - estimatedFee;
}
@override
Future<bool> generateNewAddress() async {
try {
await _incrementAddressIndexForChain(
0, DerivePathType.bip44); // First increment the receiving index
final newReceivingIndex = DB.instance.get<dynamic>(
boxName: walletId,
key: 'receivingIndexP2PKH') as int; // Check the new receiving index
final newReceivingAddress = await _generateAddressForChain(
0,
newReceivingIndex,
DerivePathType
.bip44); // Use new index to derive a new receiving address
await _addToAddressesArrayForChain(
newReceivingAddress,
0,
DerivePathType
.bip44); // Add that new receiving address to the array of receiving addresses
_currentReceivingAddressP2PKH = Future(() =>
newReceivingAddress); // Set the new receiving address that the service
return true;
} catch (e, s) {
Logging.instance.log(
"Exception rethrown from generateNewAddress(): $e\n$s",
level: LogLevel.Error);
return false;
}
}
}
// Bitcoincash Network

View file

@ -9,6 +9,7 @@ import 'package:stackwallet/services/coins/epiccash/epiccash_wallet.dart';
import 'package:stackwallet/services/coins/firo/firo_wallet.dart';
import 'package:stackwallet/services/coins/monero/monero_wallet.dart';
import 'package:stackwallet/services/coins/bitcoincash/bitcoincash_wallet.dart';
import 'package:stackwallet/services/coins/namecoin/namecoin_wallet.dart';
import 'package:stackwallet/services/transaction_notification_tracker.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/prefs.dart';
@ -134,6 +135,16 @@ abstract class CoinServiceAPI {
// tracker: tracker,
);
case Coin.namecoin:
return NamecoinWallet(
walletId: walletId,
walletName: walletName,
coin: coin,
tracker: tracker,
cachedClient: cachedClient,
client: client,
);
case Coin.dogecoinTestNet:
return DogecoinWallet(
walletId: walletId,

View file

@ -46,9 +46,7 @@ const int MINIMUM_CONFIRMATIONS = 3;
const int DUST_LIMIT = 1000000;
const String GENESIS_HASH_MAINNET =
"000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f";
const String GENESIS_HASH_TESTNET =
"000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943";
"000000000062b72c5e2ceb45fbc8587e807c155b0da735e6483dfba2f0a9c770";
enum DerivePathType { bip44 }
@ -77,11 +75,11 @@ bip32.BIP32 getBip32NodeFromRoot(
int chain, int index, bip32.BIP32 root, DerivePathType derivePathType) {
String coinType;
switch (root.network.wif) {
case 0x80: // bch mainnet wif
coinType = "145"; // bch mainnet
case 0x80: // nmc mainnet wif
coinType = "7"; // nmc mainnet
break;
default:
throw Exception("Invalid Bitcoincash network type used!");
throw Exception("Invalid Namecoin network type used!");
}
switch (derivePathType) {
case DerivePathType.bip44:
@ -122,7 +120,7 @@ bip32.BIP32 getBip32RootWrapper(Tuple2<String, NetworkType> args) {
return getBip32Root(args.item1, args.item2);
}
class NamecoinCashWallet extends CoinServiceAPI {
class NamecoinWallet extends CoinServiceAPI {
static const integrationTestFlag =
bool.fromEnvironment("IS_INTEGRATION_TEST");
final _prefs = Prefs.instance;
@ -134,10 +132,10 @@ class NamecoinCashWallet extends CoinServiceAPI {
NetworkType get _network {
switch (coin) {
case Coin.bitcoincash:
return bitcoincash;
case Coin.namecoin:
return namecoin;
default:
throw Exception("Bitcoincash network type not set!");
throw Exception("Namecoin network type not set!");
}
}
@ -298,14 +296,14 @@ class NamecoinCashWallet extends CoinServiceAPI {
final features = await electrumXClient.getServerFeatures();
Logging.instance.log("features: $features", level: LogLevel.Info);
switch (coin) {
case Coin.bitcoincash:
case Coin.namecoin:
if (features['genesis_hash'] != GENESIS_HASH_MAINNET) {
throw Exception("genesis hash does not match main net!");
}
break;
default:
throw Exception(
"Attempted to generate a BitcoinCashWallet using a non bch coin type: ${coin.name}");
"Attempted to generate a NamecoinWallet using a non namecoin coin type: ${coin.name}");
}
}
// check to make sure we aren't overwriting a mnemonic
@ -730,9 +728,6 @@ class NamecoinCashWallet extends CoinServiceAPI {
/// Refreshes display data for the wallet
@override
Future<void> refresh() async {
final bchaddr = Bitbox.Address.toCashAddress(await currentReceivingAddress);
print("bchaddr: $bchaddr ${await currentReceivingAddress}");
if (refreshMutex) {
Logging.instance.log("$walletId $walletName refreshMutex denied",
level: LogLevel.Info);
@ -1048,14 +1043,7 @@ class NamecoinCashWallet extends CoinServiceAPI {
@override
bool validateAddress(String address) {
try {
// 0 for bitcoincash: address scheme, 1 for legacy address
final format = Bitbox.Address.detectFormat(address);
print("format $format");
return true;
} catch (e, s) {
return false;
}
return Address.validateAddress(address, _network);
}
@override
@ -1082,7 +1070,7 @@ class NamecoinCashWallet extends CoinServiceAPI {
late PriceAPI _priceAPI;
BitcoinCashWallet({
NamecoinWallet({
required String walletId,
required String walletName,
required Coin coin,
@ -1222,14 +1210,14 @@ class NamecoinCashWallet extends CoinServiceAPI {
final features = await electrumXClient.getServerFeatures();
Logging.instance.log("features: $features", level: LogLevel.Info);
switch (coin) {
case Coin.bitcoincash:
case Coin.namecoin:
if (features['genesis_hash'] != GENESIS_HASH_MAINNET) {
throw Exception("genesis hash does not match main net!");
}
break;
default:
throw Exception(
"Attempted to generate a BitcoinWallet using a non bitcoin coin type: ${coin.name}");
"Attempted to generate a NamecoinWallet using a non namecoin coin type: ${coin.name}");
}
}
@ -1840,10 +1828,10 @@ class NamecoinCashWallet extends CoinServiceAPI {
/// attempts to convert a string to a valid scripthash
///
/// Returns the scripthash or throws an exception on invalid bch address
String _convertToScriptHash(String bchAddress, NetworkType network) {
/// Returns the scripthash or throws an exception on invalid namecoin address
String _convertToScriptHash(String namecoinAddress, NetworkType network) {
try {
final output = Address.addressToOutputScript(bchAddress, network);
final output = Address.addressToOutputScript(namecoinAddress, network);
final hash = sha256.convert(output.toList(growable: false)).toString();
final chars = hash.split("");
@ -1937,7 +1925,6 @@ class NamecoinCashWallet extends CoinServiceAPI {
unconfirmedCachedTransactions
.removeWhere((key, value) => value.confirmedStatus);
print("CACHED_TRANSACTIONS_IS $cachedTransactions");
if (cachedTransactions != null) {
for (final tx in allTxHashes.toList(growable: false)) {
final txHeight = tx["height"] as int;
@ -1953,7 +1940,6 @@ class NamecoinCashWallet extends CoinServiceAPI {
List<Map<String, dynamic>> allTransactions = [];
for (final txHash in allTxHashes) {
Logging.instance.log("bch: $txHash", level: LogLevel.Info);
final tx = await cachedElectrumXClient.getTransaction(
txHash: txHash["tx_hash"] as String,
verbose: true,
@ -2325,8 +2311,8 @@ class NamecoinCashWallet extends CoinServiceAPI {
vSize: vSizeForOneOutput,
feeRatePerKB: selectedTxFeeRate,
);
if (feeForOneOutput < (vSizeForOneOutput + 1)) {
feeForOneOutput = (vSizeForOneOutput + 1);
if (feeForOneOutput < (vSizeForOneOutput + 1) * 1000) {
feeForOneOutput = (vSizeForOneOutput + 1) * 1000;
}
final int amount = satoshiAmountToSend - feeForOneOutput;
@ -2382,11 +2368,11 @@ class NamecoinCashWallet extends CoinServiceAPI {
.log("feeForTwoOutputs: $feeForTwoOutputs", level: LogLevel.Info);
Logging.instance
.log("feeForOneOutput: $feeForOneOutput", level: LogLevel.Info);
if (feeForOneOutput < (vSizeForOneOutput + 1)) {
feeForOneOutput = (vSizeForOneOutput + 1);
if (feeForOneOutput < (vSizeForOneOutput + 1) * 1000) {
feeForOneOutput = (vSizeForOneOutput + 1) * 1000;
}
if (feeForTwoOutputs < ((vSizeForTwoOutPuts + 1))) {
feeForTwoOutputs = ((vSizeForTwoOutPuts + 1));
if (feeForTwoOutputs < ((vSizeForTwoOutPuts + 1) * 1000)) {
feeForTwoOutputs = ((vSizeForTwoOutPuts + 1) * 1000);
}
Logging.instance
@ -2686,76 +2672,45 @@ class NamecoinCashWallet extends CoinServiceAPI {
required List<String> recipients,
required List<int> satoshiAmounts,
}) async {
final builder = Bitbox.Bitbox.transactionBuilder();
Logging.instance
.log("Starting buildTransaction ----------", level: LogLevel.Info);
// retrieve address' utxos from the rest api
List<Bitbox.Utxo> _utxos =
[]; // await Bitbox.Address.utxo(address) as List<Bitbox.Utxo>;
utxosToUse.forEach((element) {
_utxos.add(Bitbox.Utxo(
element.txid,
element.vout,
Bitbox.BitcoinCash.fromSatoshi(element.value),
element.value,
0,
MINIMUM_CONFIRMATIONS + 1));
});
Logger.print("bch utxos: ${_utxos}");
final txb = TransactionBuilder(network: _network);
txb.setVersion(1);
// placeholder for input signatures
final signatures = <Map>[];
// placeholder for total input balance
int totalBalance = 0;
// iterate through the list of address _utxos and use them as inputs for the
// withdrawal transaction
_utxos.forEach((Bitbox.Utxo utxo) {
// add the utxo as an input for the transaction
builder.addInput(utxo.txid, utxo.vout);
final ec = utxoSigningData[utxo.txid]["keyPair"] as ECPair;
final bitboxEC = Bitbox.ECPair.fromWIF(ec.toWIF());
// add a signature to the list to be used later
signatures.add({
"vin": signatures.length,
"key_pair": bitboxEC,
"original_amount": utxo.satoshis
});
totalBalance += utxo.satoshis;
});
// calculate the fee based on number of inputs and one expected output
final fee =
Bitbox.BitcoinCash.getByteCount(signatures.length, recipients.length);
// calculate how much balance will be left over to spend after the fee
final sendAmount = totalBalance - fee;
// add the output based on the address provided in the testing data
for (int i = 0; i < recipients.length; i++) {
String recipient = recipients[i];
int satoshiAmount = satoshiAmounts[i];
builder.addOutput(recipient, satoshiAmount);
// Add transaction inputs
for (var i = 0; i < utxosToUse.length; i++) {
final txid = utxosToUse[i].txid;
txb.addInput(txid, utxosToUse[i].vout, null,
utxoSigningData[txid]["output"] as Uint8List);
}
// sign all inputs
signatures.forEach((signature) {
builder.sign(
signature["vin"] as int,
signature["key_pair"] as Bitbox.ECPair,
signature["original_amount"] as int);
});
// Add transaction output
for (var i = 0; i < recipients.length; i++) {
txb.addOutput(recipients[i], satoshiAmounts[i]);
}
// build the transaction
final tx = builder.build();
final txHex = tx.toHex();
final vSize = tx.virtualSize();
Logger.print("bch raw hex: $txHex");
try {
// Sign the transaction accordingly
for (var i = 0; i < utxosToUse.length; i++) {
final txid = utxosToUse[i].txid;
txb.sign(
vin: i,
keyPair: utxoSigningData[txid]["keyPair"] as ECPair,
witnessValue: utxosToUse[i].value,
redeemScript: utxoSigningData[txid]["redeemScript"] as Uint8List?,
);
}
} catch (e, s) {
Logging.instance.log("Caught exception while signing transaction: $e\n$s",
level: LogLevel.Error);
rethrow;
}
return {"hex": txHex, "vSize": vSize};
final builtTx = txb.build();
final vSize = builtTx.virtualSize();
return {"hex": builtTx.toHex(), "vSize": vSize};
}
@override
@ -3018,7 +2973,7 @@ class NamecoinCashWallet extends CoinServiceAPI {
}
}
// TODO: correct formula for bch?
// TODO: correct formula for nmc?
int roughFeeEstimate(int inputCount, int outputCount, int feeRatePerKB) {
return ((181 * inputCount) + (34 * outputCount) + 10) *
(feeRatePerKB / 1000).ceil();
@ -3039,10 +2994,39 @@ class NamecoinCashWallet extends CoinServiceAPI {
return available - estimatedFee;
}
Future<bool> generateNewAddress() async {
try {
await _incrementAddressIndexForChain(
0, DerivePathType.bip44); // First increment the receiving index
final newReceivingIndex = DB.instance.get<dynamic>(
boxName: walletId,
key: 'receivingIndexP2PKH') as int; // Check the new receiving index
final newReceivingAddress = await _generateAddressForChain(
0,
newReceivingIndex,
DerivePathType
.bip44); // Use new index to derive a new receiving address
await _addToAddressesArrayForChain(
newReceivingAddress,
0,
DerivePathType
.bip44); // Add that new receiving address to the array of receiving addresses
_currentReceivingAddressP2PKH = Future(() =>
newReceivingAddress); // Set the new receiving address that the service
return true;
} catch (e, s) {
Logging.instance.log(
"Exception rethrown from generateNewAddress(): $e\n$s",
level: LogLevel.Error);
return false;
}
}
}
// Bitcoincash Network
final bitcoincash = NetworkType(
// Namecoin Network
final namecoin = NetworkType(
messagePrefix: '\x18Bitcoin Signed Message:\n',
bech32: 'bc',
bip32: Bip32Type(public: 0x0488b21e, private: 0x0488ade4),

View file

@ -6,6 +6,7 @@ import 'package:flutter_libepiccash/epic_cash.dart';
import 'package:stackwallet/services/coins/dogecoin/dogecoin_wallet.dart';
import 'package:stackwallet/services/coins/firo/firo_wallet.dart';
import 'package:stackwallet/services/coins/bitcoincash/bitcoincash_wallet.dart';
import 'package:stackwallet/services/coins/namecoin/namecoin_wallet.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
import 'package:stackwallet/utilities/logger.dart';
@ -52,6 +53,8 @@ class AddressUtils {
case Coin.monero:
return RegExp("[a-zA-Z0-9]{95}").hasMatch(address) ||
RegExp("[a-zA-Z0-9]{106}").hasMatch(address);
case Coin.namecoin:
return Address.validateAddress(address, namecoin);
case Coin.bitcoinTestNet:
return Address.validateAddress(address, testnet);
case Coin.firoTestNet:

View file

@ -1,3 +1,4 @@
import 'package:stackwallet/services/coins/namecoin/namecoin_wallet.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
abstract class Assets {
@ -110,6 +111,7 @@ class _SVG {
String get epicCash => "assets/svg/coin_icons/EpicCash.svg";
String get firo => "assets/svg/coin_icons/Firo.svg";
String get monero => "assets/svg/coin_icons/Monero.svg";
String get namecoin => "assets/svg/coin_icons/Namecoin.svg";
// TODO provide proper assets
String get bitcoinTestnet => "assets/svg/coin_icons/Bitcoin.svg";
@ -130,6 +132,8 @@ class _SVG {
return firo;
case Coin.monero:
return monero;
case Coin.namecoin:
return namecoin;
case Coin.bitcoinTestNet:
return bitcoinTestnet;
case Coin.firoTestNet:
@ -152,6 +156,7 @@ class _PNG {
String get bitcoin => "assets/images/bitcoin.png";
String get epicCash => "assets/images/epic-cash.png";
String get bitcoincash => "assets/images/bitcoincash.png";
String get namecoin => "assets/images/bitcoincash.png";
String imageFor({required Coin coin}) {
switch (coin) {
@ -171,6 +176,8 @@ class _PNG {
return firo;
case Coin.monero:
return monero;
case Coin.namecoin:
return namecoin;
}
}
}

View file

@ -24,5 +24,7 @@ Uri getBlockExplorerTransactionUrlFor({
return Uri.parse("https://testexplorer.firo.org/tx/$txid");
case Coin.bitcoincash:
return Uri.parse("https://blockchair.com/bitcoin-cash/transaction/$txid");
case Coin.namecoin:
return Uri.parse("uri");
}
}

View file

@ -11,6 +11,7 @@ class _CoinThemeColor {
Color get dogecoin => const Color(0xFFFFE079);
Color get epicCash => const Color(0xFFC5C7CB);
Color get monero => const Color(0xFFFF9E6B);
Color get namecoin => const Color(0xFFFCC17B);
Color forCoin(Coin coin) {
switch (coin) {
@ -29,6 +30,8 @@ class _CoinThemeColor {
return firo;
case Coin.monero:
return monero;
case Coin.namecoin:
return namecoin;
}
}
}

View file

@ -46,6 +46,7 @@ abstract class Constants {
case Coin.dogecoinTestNet:
case Coin.firoTestNet:
case Coin.epicCash:
case Coin.namecoin:
values.addAll([24, 21, 18, 15, 12]);
break;
@ -79,6 +80,9 @@ abstract class Constants {
case Coin.monero:
return 120;
case Coin.namecoin:
return 600;
}
}

View file

@ -1,6 +1,7 @@
import 'dart:convert';
import 'package:stackwallet/models/node_model.dart';
import 'package:stackwallet/services/coins/namecoin/namecoin_wallet.dart';
import 'package:stackwallet/utilities/enums/coin_enum.dart';
abstract class DefaultNodes {
@ -14,6 +15,7 @@ abstract class DefaultNodes {
monero,
epicCash,
bitcoincash,
namecoin,
bitcoinTestnet,
dogecoinTestnet,
firoTestnet,
@ -93,6 +95,18 @@ abstract class DefaultNodes {
isDown: false,
);
static NodeModel get namecoin => NodeModel(
host: "46.229.238.187",
port: 57002,
name: defaultName,
id: _nodeId(Coin.namecoin),
useSSL: true,
enabled: true,
coinName: Coin.namecoin.name,
isFailover: true,
isDown: false,
);
static NodeModel get bitcoinTestnet => NodeModel(
host: "electrumx-testnet.cypherstack.com",
port: 51002,
@ -149,6 +163,9 @@ abstract class DefaultNodes {
case Coin.monero:
return monero;
case Coin.namecoin:
return namecoin;
case Coin.bitcoinTestNet:
return bitcoinTestnet;

View file

@ -7,6 +7,8 @@ import 'package:stackwallet/services/coins/firo/firo_wallet.dart' as firo;
import 'package:stackwallet/services/coins/monero/monero_wallet.dart' as xmr;
import 'package:stackwallet/services/coins/bitcoincash/bitcoincash_wallet.dart'
as bch;
import 'package:stackwallet/services/coins/namecoin/namecoin_wallet.dart'
as nmc;
enum Coin {
bitcoin,
@ -69,6 +71,8 @@ extension CoinExt on Coin {
return "FIRO";
case Coin.monero:
return "XMR";
case Coin.namecoin:
return "NMC";
case Coin.bitcoinTestNet:
return "tBTC";
case Coin.firoTestNet:
@ -93,6 +97,8 @@ extension CoinExt on Coin {
return "firo";
case Coin.monero:
return "monero";
case Coin.namecoin:
return "namecoin";
case Coin.bitcoinTestNet:
return "bitcoin";
case Coin.firoTestNet:
@ -108,6 +114,7 @@ extension CoinExt on Coin {
case Coin.bitcoincash:
case Coin.dogecoin:
case Coin.firo:
case Coin.namecoin:
case Coin.bitcoinTestNet:
case Coin.firoTestNet:
case Coin.dogecoinTestNet:
@ -141,6 +148,8 @@ extension CoinExt on Coin {
case Coin.monero:
return xmr.MINIMUM_CONFIRMATIONS;
case Coin.namecoin:
return nmc.MINIMUM_CONFIRMATIONS;
}
}
}
@ -166,6 +175,9 @@ Coin coinFromPrettyName(String name) {
case "Monero":
case "monero":
return Coin.monero;
case "Namecoin":
case "namecoin":
return Coin.namecoin;
case "Bitcoin Testnet":
case "tBitcoin":
case "bitcoinTestNet":
@ -198,6 +210,8 @@ Coin coinFromTickerCaseInsensitive(String ticker) {
return Coin.firo;
case "xmr":
return Coin.monero;
case "nmc":
return Coin.namecoin;
case "tbtc":
return Coin.bitcoinTestNet;
case "tfiro":

View file

@ -271,6 +271,7 @@ flutter:
- assets/svg/coin_icons/EpicCash.svg
- assets/svg/coin_icons/Firo.svg
- assets/svg/coin_icons/Monero.svg
- assets/svg/coin_icons/Namecoin.svg
# lottie animations
- assets/lottie/test.json
- assets/lottie/test2.json