diff --git a/cw_nano/lib/nano_client.dart b/cw_nano/lib/nano_client.dart index 5efb19edf..aeb3530e1 100644 --- a/cw_nano/lib/nano_client.dart +++ b/cw_nano/lib/nano_client.dart @@ -13,6 +13,9 @@ import 'package:web3dart/contracts/erc20.dart'; import 'package:cw_core/node.dart'; class NanoClient { + // bit of a hack since we need access to a node in a weird location: + static const String BACKUP_NODE_URI = "rpc.nano.to"; + final _httpClient = Client(); StreamSubscription? subscription; Node? _node; @@ -97,23 +100,4 @@ class NanoClient { return []; } } - -// Future _getDecimalPlacesForContract(DeployedContract contract) async { -// final String abi = await rootBundle.loadString("assets/abi_json/erc20_abi.json"); -// final contractAbi = ContractAbi.fromJson(abi, "ERC20"); -// -// final contract = DeployedContract( -// contractAbi, -// EthereumAddress.fromHex(_erc20Currencies[erc20Currency]!), -// ); -// final decimalsFunction = contract.function('decimals'); -// final decimals = await _client!.call( -// contract: contract, -// function: decimalsFunction, -// params: [], -// ); -// -// int exponent = int.parse(decimals.first.toString()); -// return exponent; -// } } diff --git a/cw_nano/lib/nano_wallet.dart b/cw_nano/lib/nano_wallet.dart index baa148fc4..943bdf035 100644 --- a/cw_nano/lib/nano_wallet.dart +++ b/cw_nano/lib/nano_wallet.dart @@ -15,6 +15,7 @@ import 'package:cw_nano/nano_client.dart'; import 'package:cw_nano/nano_transaction_history.dart'; import 'package:cw_nano/nano_transaction_info.dart'; import 'package:cw_nano/nano_util.dart'; +import 'package:cw_nano/nano_wallet_info.dart'; import 'package:mobx/mobx.dart'; import 'dart:async'; import 'package:cw_nano/nano_wallet_addresses.dart'; @@ -27,20 +28,17 @@ part 'nano_wallet.g.dart'; class NanoWallet = NanoWalletBase with _$NanoWallet; -enum DerivationType { bip39, nano } - abstract class NanoWalletBase extends WalletBase with Store { NanoWalletBase({ - required WalletInfo walletInfo, + required NanoWalletInfo walletInfo, required String mnemonic, required String password, - required DerivationType derivationType, NanoBalance? initialBalance, }) : syncStatus = NotConnectedSyncStatus(), _password = password, _mnemonic = mnemonic, - _derivationType = derivationType, + _derivationType = walletInfo.derivationType, _isTransactionUpdating = false, _client = NanoClient(), walletAddresses = NanoWalletAddresses(walletInfo), @@ -86,8 +84,6 @@ abstract class NanoWalletBase await walletAddresses.init(); await transactionHistory.init(); - - // walletAddresses.address = _privateKey.address.toString(); await save(); } @@ -127,7 +123,58 @@ abstract class NanoWalletBase @override Future createTransaction(Object credentials) async { print("g"); - throw UnimplementedError(); + print(credentials); + // throw UnimplementedError(); + + // final _credentials = credentials as EthereumTransactionCredentials; + // final outputs = _credentials.outputs; + // final hasMultiDestination = outputs.length > 1; + // final _erc20Balance = balance[_credentials.currency]!; + // BigInt totalAmount = BigInt.zero; + // int exponent = + // _credentials.currency is Erc20Token ? (_credentials.currency as Erc20Token).decimal : 18; + // BigInt amountToEthereumMultiplier = BigInt.from(pow(10, exponent)); + + // if (hasMultiDestination) { + // if (outputs.any((item) => item.sendAll || (item.formattedCryptoAmount ?? 0) <= 0)) { + // throw EthereumTransactionCreationException(_credentials.currency); + // } + + // final totalOriginalAmount = EthereumFormatter.parseEthereumAmountToDouble( + // outputs.fold(0, (acc, value) => acc + (value.formattedCryptoAmount ?? 0))); + // totalAmount = BigInt.from(totalOriginalAmount) * amountToEthereumMultiplier; + + // if (_erc20Balance.balance < totalAmount) { + // throw EthereumTransactionCreationException(_credentials.currency); + // } + // } else { + // final output = outputs.first; + // final BigInt allAmount = _erc20Balance.balance - BigInt.from(feeRate(_credentials.priority!)); + // final totalOriginalAmount = + // EthereumFormatter.parseEthereumAmountToDouble(output.formattedCryptoAmount ?? 0); + // totalAmount = output.sendAll + // ? allAmount + // : BigInt.from(totalOriginalAmount) * amountToEthereumMultiplier; + + // if (_erc20Balance.balance < totalAmount) { + // throw EthereumTransactionCreationException(_credentials.currency); + // } + // } + + // final pendingEthereumTransaction = await _client.signTransaction( + // privateKey: _privateKey, + // toAddress: _credentials.outputs.first.address, + // amount: totalAmount.toString(), + // gas: _priorityFees[_credentials.priority!.raw], + // priority: _credentials.priority!, + // currency: _credentials.currency, + // exponent: exponent, + // contractAddress: _credentials.currency is Erc20Token + // ? (_credentials.currency as Erc20Token).contractAddress + // : null, + // ); + + // return pendingEthereumTransaction; } Future updateTransactions() async { @@ -178,9 +225,10 @@ abstract class NanoWalletBase } @override - Future rescan({required int height}) { - print("k"); - throw UnimplementedError("rescan"); + Future rescan({required int height}) async { + fetchTransactions(); + _updateBalance(); + return; } @override @@ -235,17 +283,20 @@ abstract class NanoWalletBase derivationType = DerivationType.nano; } - return NanoWallet( + final nanoWalletInfo = NanoWalletInfo( walletInfo: walletInfo, + derivationType: derivationType, + ); + + return NanoWallet( + walletInfo: nanoWalletInfo, password: password, mnemonic: mnemonic, initialBalance: balance, - derivationType: derivationType, ); } Future _updateBalance() async { - // this.balance.update(CryptoCurrency.nano, (value) => (await _client.getBalance(_publicAddress))); balance[currency] = await _client.getBalance(_publicAddress); await save(); } diff --git a/cw_nano/lib/nano_wallet_info.dart b/cw_nano/lib/nano_wallet_info.dart new file mode 100644 index 000000000..2d437e848 --- /dev/null +++ b/cw_nano/lib/nano_wallet_info.dart @@ -0,0 +1,23 @@ +import 'package:cw_core/wallet_info.dart'; + +enum DerivationType { bip39, nano } + +class NanoWalletInfo extends WalletInfo { + DerivationType derivationType; + + NanoWalletInfo({required WalletInfo walletInfo, required this.derivationType}) + : super( + walletInfo.id, + walletInfo.name, + walletInfo.type, + walletInfo.isRecovery, + walletInfo.restoreHeight, + walletInfo.timestamp, + walletInfo.dirPath, + walletInfo.path, + walletInfo.address, + walletInfo.yatEid, + walletInfo.yatLastUsedAddressRaw, + walletInfo.showIntroCakePayCard, + ); +} diff --git a/cw_nano/lib/nano_wallet_service.dart b/cw_nano/lib/nano_wallet_service.dart index cd3077918..0794dec12 100644 --- a/cw_nano/lib/nano_wallet_service.dart +++ b/cw_nano/lib/nano_wallet_service.dart @@ -1,15 +1,18 @@ import 'dart:io'; +import 'package:cw_core/node.dart'; import 'package:cw_core/pathForWallet.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_credentials.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_service.dart'; import 'package:cw_core/wallet_type.dart'; +import 'package:cw_nano/nano_balance.dart'; +import 'package:cw_nano/nano_client.dart'; import 'package:cw_nano/nano_mnemonic.dart'; -// import 'package:cw_nano/nano_mnemonics.dart'; +import 'package:cw_nano/nano_util.dart'; import 'package:cw_nano/nano_wallet.dart'; -import 'package:cw_nano/nano_wallet_creation_credentials.dart'; +import 'package:cw_nano/nano_wallet_info.dart'; import 'package:hive/hive.dart'; import 'package:bip39/bip39.dart' as bip39; @@ -62,11 +65,16 @@ class NanoWalletService extends WalletService create(NanoNewWalletCredentials credentials) async { print("nano_wallet_service create"); final mnemonic = bip39.generateMnemonic(); - final wallet = NanoWallet( + + final nanoWalletInfo = NanoWalletInfo( walletInfo: credentials.walletInfo!, + derivationType: DerivationType.nano, + ); + + final wallet = NanoWallet( + walletInfo: nanoWalletInfo, mnemonic: mnemonic, password: credentials.password!, - derivationType: DerivationType.bip39, ); return wallet; } @@ -102,35 +110,69 @@ class NanoWalletService extends WalletService compareDerivationMethods({String? mnemonic, String? seedKey}) async { - // TODO: - return DerivationType.nano; + if (seedKey?.length == 128) { + return DerivationType.bip39; + } + if (mnemonic?.split(' ').length == 12) { + return DerivationType.bip39; + } + + try { + NanoClient nanoClient = NanoClient(); + // TODO: figure out how to load the current node uri in this context: + nanoClient.connect(Node( + uri: NanoClient.BACKUP_NODE_URI, + type: WalletType.nano, + )); + + late String publicAddressStandard; + late String publicAddressBip39; + + if (seedKey == null) { + seedKey = bip39.mnemonicToEntropy(mnemonic).toUpperCase(); + } + + publicAddressBip39 = await NanoUtil.hdSeedToAddress(seedKey, 0); + publicAddressStandard = await NanoUtil.seedToAddress(seedKey, 0); + + // check if either has a balance: + + NanoBalance bip39Balance = await nanoClient.getBalance(publicAddressBip39); + NanoBalance standardBalance = await nanoClient.getBalance(publicAddressStandard); + + // TODO: this is super basic implementation, and if both addresses have balances + // it might not be the one that the user wants, though it is unlikely + if (bip39Balance.currentBalance > standardBalance.currentBalance) { + return DerivationType.bip39; + } else { + return DerivationType.nano; + } + } catch (e) { + return DerivationType.nano; + } } @override Future restoreFromKeys(NanoRestoreWalletFromKeysCredentials credentials) async { throw UnimplementedError("restoreFromKeys"); - DerivationType derivationType = DerivationType.bip39; + // TODO: mnemonic can't be derived from the seedKey in the nanostandard derivation + // which complicates things - if (credentials.seedKey.length == 128) { - derivationType = DerivationType.bip39; - } else { - // we don't know for sure, but probably the nano standard: - derivationType = await compareDerivationMethods(seedKey: credentials.seedKey); - } - - String? mnemonic; - - final wallet = await NanoWallet( - password: credentials.password!, - mnemonic: mnemonic ?? "", // we can't derive the mnemonic from the key in all cases - walletInfo: credentials.walletInfo!, - derivationType: derivationType, - ); - - await wallet.init(); - await wallet.save(); - return wallet; + // DerivationType derivationType = await compareDerivationMethods(seedKey: credentials.seedKey); + // String? mnemonic; + // final nanoWalletInfo = NanoWalletInfo( + // walletInfo: credentials.walletInfo!, + // derivationType: derivationType, + // ); + // final wallet = await NanoWallet( + // password: credentials.password!, + // mnemonic: mnemonic ?? "", // we can't derive the mnemonic from the key in all cases + // walletInfo: nanoWalletInfo, + // ); + // await wallet.init(); + // await wallet.save(); + // return wallet; } @override @@ -143,20 +185,17 @@ class NanoWalletService extends WalletService generateNano(bool hasImplementation) async { const nanoCWHeaders = """ import 'package:mobx/mobx.dart'; import 'package:cw_core/wallet_credentials.dart'; +import 'package:cw_nano/nano_wallet_info.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/transaction_history.dart'; import 'package:cw_core/wallet_service.dart';