/* * This file is part of Stack Wallet. * * Copyright (c) 2023 Cypher Stack * All Rights Reserved. * The code is distributed under GPLv3 license, see LICENSE file for details. * Generated by Cypher Stack on 2023-05-26 * */ import 'dart:convert'; import 'package:bitbox/bitbox.dart' as bitbox; 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/dogecoin/dogecoin_wallet.dart'; import 'package:stackwallet/services/coins/ecash/ecash_wallet.dart'; import 'package:stackwallet/services/coins/firo/firo_wallet.dart'; import 'package:stackwallet/services/coins/litecoin/litecoin_wallet.dart'; import 'package:stackwallet/services/coins/namecoin/namecoin_wallet.dart'; import 'package:stackwallet/services/coins/particl/particl_wallet.dart'; import 'package:stackwallet/utilities/enums/coin_enum.dart'; import 'package:stackwallet/utilities/logger.dart'; class AddressUtils { static String condenseAddress(String address) { return '${address.substring(0, 5)}...${address.substring(address.length - 5)}'; } /// attempts to convert a string to a valid scripthash /// /// Returns the scripthash or throws an exception on invalid firo address static String convertToScriptHash( String address, NetworkType network, [ String overridePrefix = "", ]) { try { final output = Address.addressToOutputScript(address, network, overridePrefix); final hash = sha256.convert(output.toList(growable: false)).toString(); final chars = hash.split(""); final reversedPairs = []; // TODO find a better/faster way to do this? var i = chars.length - 1; while (i > 0) { reversedPairs.add(chars[i - 1]); reversedPairs.add(chars[i]); i -= 2; } return reversedPairs.join(""); } catch (e) { rethrow; } } static bool validateAddress(String address, Coin coin) { switch (coin) { case Coin.bitcoin: return Address.validateAddress(address, bitcoin); case Coin.litecoin: return Address.validateAddress(address, litecoin); case Coin.bitcoincash: try { // 0 for bitcoincash: address scheme, 1 for legacy address final format = bitbox.Address.detectFormat(address); if (coin == Coin.bitcoincashTestnet) { return true; } if (format == bitbox.Address.formatCashAddr) { String addr = address; if (addr.contains(":")) { addr = addr.split(":").last; } return addr.startsWith("q"); } else { return address.startsWith("1"); } } catch (e) { return false; } case Coin.dogecoin: return Address.validateAddress(address, dogecoin); case Coin.epicCash: return validateSendAddress(address) == "1"; case Coin.ethereum: return true; //TODO - validate ETH address case Coin.firo: return Address.validateAddress(address, firoNetwork); case Coin.eCash: return Address.validateAddress(address, eCashNetwork); case Coin.monero: return RegExp("[a-zA-Z0-9]{95}").hasMatch(address) || RegExp("[a-zA-Z0-9]{106}").hasMatch(address); case Coin.wownero: 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, namecoin.bech32!); case Coin.particl: return Address.validateAddress(address, particl); case Coin.stellar: return RegExp(r"^[G][A-Z0-9]{55}$").hasMatch(address); case Coin.nano: return NanoAccounts.isValid(NanoAccountType.NANO, address); case Coin.banano: return NanoAccounts.isValid(NanoAccountType.BANANO, address); case Coin.tezos: return RegExp(r"^tz[1-9A-HJ-NP-Za-km-z]{34}$").hasMatch(address); case Coin.bitcoinTestNet: return Address.validateAddress(address, testnet); case Coin.litecoinTestNet: return Address.validateAddress(address, litecointestnet); case Coin.bitcoincashTestnet: try { // 0 for bitcoincash: address scheme, 1 for legacy address final format = bitbox.Address.detectFormat(address); if (coin == Coin.bitcoincashTestnet) { return true; } if (format == bitbox.Address.formatCashAddr) { String addr = address; if (addr.contains(":")) { addr = addr.split(":").last; } return addr.startsWith("q"); } else { return address.startsWith("1"); } } catch (e) { return false; } case Coin.firoTestNet: return Address.validateAddress(address, firoTestNetwork); case Coin.dogecoinTestNet: return Address.validateAddress(address, dogecointestnet); case Coin.stellarTestnet: return RegExp(r"^[G][A-Z0-9]{55}$").hasMatch(address); } } /// parse an address uri /// returns an empty map if the input string does not begin with "firo:" static Map parseUri(String uri) { Map result = {}; try { final u = Uri.parse(uri); if (u.hasScheme) { result["scheme"] = u.scheme.toLowerCase(); result["address"] = u.path; result.addAll(u.queryParameters); } } catch (e) { Logging.instance .log("Exception caught in parseUri($uri): $e", level: LogLevel.Error); } return result; } /// builds a uri string with the given address and query parameters if any static String buildUriString( Coin coin, String address, Map params, ) { // TODO: other sanitation as well ? String sanitizedAddress = address; if (coin == Coin.bitcoincash || coin == Coin.bitcoincashTestnet || coin == Coin.eCash) { final prefix = "${coin.uriScheme}:"; if (address.startsWith(prefix)) { sanitizedAddress = address.replaceFirst(prefix, ""); } } String uriString = "${coin.uriScheme}:$sanitizedAddress"; if (params.isNotEmpty) { uriString += Uri(queryParameters: params).toString(); } return uriString; } /// returns empty if bad data static Map decodeQRSeedData(String data) { Map result = {}; try { result = Map.from(jsonDecode(data) as Map); } catch (e) { Logging.instance.log("Exception caught in parseQRSeedData($data): $e", level: LogLevel.Error); } return result; } /// encode mnemonic words to qrcode formatted string static String encodeQRSeedData(List words) { return jsonEncode({"mnemonic": words}); } }