diff --git a/assets/svg/coin_icons/Namecoin.svg b/assets/svg/coin_icons/Namecoin.svg new file mode 100644 index 000000000..2cda6aaf0 --- /dev/null +++ b/assets/svg/coin_icons/Namecoin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart b/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart index 9bc785547..0cb47b7ff 100644 --- a/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart +++ b/lib/pages/settings_views/global_settings_view/manage_nodes_views/add_edit_node_view.dart @@ -116,6 +116,7 @@ class _AddEditNodeViewState extends ConsumerState { 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 { case Coin.bitcoin: case Coin.dogecoin: case Coin.firo: + case Coin.namecoin: case Coin.bitcoincash: case Coin.bitcoinTestNet: case Coin.firoTestNet: diff --git a/lib/services/coins/bitcoincash/bitcoincash_wallet.dart b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart index eb209490f..bb241b494 100644 --- a/lib/services/coins/bitcoincash/bitcoincash_wallet.dart +++ b/lib/services/coins/bitcoincash/bitcoincash_wallet.dart @@ -3040,6 +3040,36 @@ class BitcoinCashWallet extends CoinServiceAPI { return available - estimatedFee; } + + @override + Future generateNewAddress() async { + try { + await _incrementAddressIndexForChain( + 0, DerivePathType.bip44); // First increment the receiving index + final newReceivingIndex = DB.instance.get( + 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 diff --git a/lib/services/coins/coin_service.dart b/lib/services/coins/coin_service.dart index 629912f00..5db6bd8bc 100644 --- a/lib/services/coins/coin_service.dart +++ b/lib/services/coins/coin_service.dart @@ -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, diff --git a/lib/services/coins/namecoin/namecoin_wallet.dart b/lib/services/coins/namecoin/namecoin_wallet.dart index 30394cd62..067b26c0f 100644 --- a/lib/services/coins/namecoin/namecoin_wallet.dart +++ b/lib/services/coins/namecoin/namecoin_wallet.dart @@ -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 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 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> 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 recipients, required List satoshiAmounts, }) async { - final builder = Bitbox.Bitbox.transactionBuilder(); + Logging.instance + .log("Starting buildTransaction ----------", level: LogLevel.Info); - // retrieve address' utxos from the rest api - List _utxos = - []; // await Bitbox.Address.utxo(address) as List; - 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 = []; - - // 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 generateNewAddress() async { + try { + await _incrementAddressIndexForChain( + 0, DerivePathType.bip44); // First increment the receiving index + final newReceivingIndex = DB.instance.get( + 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), diff --git a/lib/utilities/address_utils.dart b/lib/utilities/address_utils.dart index 68029158b..75a1f51f5 100644 --- a/lib/utilities/address_utils.dart +++ b/lib/utilities/address_utils.dart @@ -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: diff --git a/lib/utilities/assets.dart b/lib/utilities/assets.dart index 304a2ff98..850de104f 100644 --- a/lib/utilities/assets.dart +++ b/lib/utilities/assets.dart @@ -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; } } } diff --git a/lib/utilities/block_explorers.dart b/lib/utilities/block_explorers.dart index e1073e018..c89f79432 100644 --- a/lib/utilities/block_explorers.dart +++ b/lib/utilities/block_explorers.dart @@ -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"); } } diff --git a/lib/utilities/cfcolors.dart b/lib/utilities/cfcolors.dart index b72ddb4bf..93d1f2f03 100644 --- a/lib/utilities/cfcolors.dart +++ b/lib/utilities/cfcolors.dart @@ -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; } } } diff --git a/lib/utilities/constants.dart b/lib/utilities/constants.dart index 16a6c83c8..843194ab9 100644 --- a/lib/utilities/constants.dart +++ b/lib/utilities/constants.dart @@ -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; } } diff --git a/lib/utilities/default_nodes.dart b/lib/utilities/default_nodes.dart index 2cd55bea2..8f0a0f905 100644 --- a/lib/utilities/default_nodes.dart +++ b/lib/utilities/default_nodes.dart @@ -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; diff --git a/lib/utilities/enums/coin_enum.dart b/lib/utilities/enums/coin_enum.dart index f8a12c0a3..1b642603e 100644 --- a/lib/utilities/enums/coin_enum.dart +++ b/lib/utilities/enums/coin_enum.dart @@ -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": diff --git a/pubspec.yaml b/pubspec.yaml index f4e6ecee8..21bea3968 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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