diff --git a/src/Neo.CLI/CLI/MainService.Wallet.cs b/src/Neo.CLI/CLI/MainService.Wallet.cs index e74273bff4..4f65390f85 100644 --- a/src/Neo.CLI/CLI/MainService.Wallet.cs +++ b/src/Neo.CLI/CLI/MainService.Wallet.cs @@ -511,6 +511,7 @@ private void OnSendCommand(UInt160 asset, UInt160 to, string amount, UInt160? fr ConsoleHelper.Error("Incorrect password"); return; } + var snapshot = NeoSystem.StoreView; Transaction tx; AssetDescriptor descriptor = new(snapshot, NeoSystem.Settings, asset); @@ -550,10 +551,10 @@ private void OnSendCommand(UInt160 asset, UInt160 to, string amount, UInt160? fr return; } - ConsoleHelper.Info("Network fee: ", - $"{new BigDecimal((BigInteger)tx.NetworkFee, NativeContract.GAS.Decimals)}\t", - "Total fee: ", - $"{new BigDecimal((BigInteger)(tx.SystemFee + tx.NetworkFee), NativeContract.GAS.Decimals)} GAS"); + ConsoleHelper.Info( + "Send To: ", $"{to.ToAddress(NeoSystem.Settings.AddressVersion)}\n", + "Network fee: ", $"{new BigDecimal((BigInteger)tx.NetworkFee, NativeContract.GAS.Decimals)}\t", + "Total fee: ", $"{new BigDecimal((BigInteger)(tx.SystemFee + tx.NetworkFee), NativeContract.GAS.Decimals)} GAS"); if (!ConsoleHelper.ReadUserInput("Relay tx? (no|yes)").IsYes()) { return; diff --git a/src/Neo.CLI/CLI/MainService.cs b/src/Neo.CLI/CLI/MainService.cs index 1340c68cdd..e924d6478b 100644 --- a/src/Neo.CLI/CLI/MainService.cs +++ b/src/Neo.CLI/CLI/MainService.cs @@ -96,7 +96,7 @@ public MainService() : base() Initialize_Logger(); } - internal static UInt160 StringToAddress(string input, byte version) + internal UInt160 StringToAddress(string input, byte version) { switch (input.ToLowerInvariant()) { @@ -104,6 +104,11 @@ internal static UInt160 StringToAddress(string input, byte version) case "gas": return NativeContract.GAS.Hash; } + if (input.IndexOf('.') > 0 && input.LastIndexOf('.') < input.Length) + { + return ResolveNeoNameServiceAddress(input); + } + // Try to parse as UInt160 if (UInt160.TryParse(input, out var addr)) @@ -636,5 +641,45 @@ static string GetExceptionMessage(Exception exception) return exception.Message; } + + public UInt160 ResolveNeoNameServiceAddress(string domain) + { + if (Settings.Default.Contracts.NeoNameService == UInt160.Zero) + throw new Exception("Neo Name Service (NNS): is disabled on this network."); + + using var sb = new ScriptBuilder(); + sb.EmitDynamicCall(Settings.Default.Contracts.NeoNameService, "resolve", CallFlags.ReadOnly, domain, 16); + + using var appEng = ApplicationEngine.Run(sb.ToArray(), NeoSystem.StoreView, settings: NeoSystem.Settings); + if (appEng.State == VMState.HALT) + { + var data = appEng.ResultStack.Pop(); + if (data is ByteString) + { + try + { + var addressData = data.GetString(); + if (UInt160.TryParse(addressData, out var address)) + return address; + else + return addressData.ToScriptHash(NeoSystem.Settings.AddressVersion); + } + catch { } + } + else if (data is Null) + { + throw new Exception($"Neo Name Service (NNS): \"{domain}\" domain not found."); + } + throw new Exception("Neo Name Service (NNS): Record invalid address format."); + } + else + { + if (appEng.FaultException is not null) + { + throw new Exception($"Neo Name Service (NNS): \"{appEng.FaultException.Message}\"."); + } + } + throw new Exception($"Neo Name Service (NNS): \"{domain}\" domain not found."); + } } } diff --git a/src/Neo.CLI/Settings.cs b/src/Neo.CLI/Settings.cs index 7625db58f5..54067da747 100644 --- a/src/Neo.CLI/Settings.cs +++ b/src/Neo.CLI/Settings.cs @@ -11,6 +11,7 @@ using Microsoft.Extensions.Configuration; using Neo.Network.P2P; +using System; using System.Threading; namespace Neo @@ -21,6 +22,7 @@ public class Settings public StorageSettings Storage { get; } public P2PSettings P2P { get; } public UnlockWalletSettings UnlockWallet { get; } + public ContractsSettings Contracts { get; } static Settings? s_default; @@ -51,6 +53,7 @@ public static Settings Default public Settings(IConfigurationSection section) { + Contracts = new(section.GetSection(nameof(Contracts))); Logger = new(section.GetSection(nameof(Logger))); Storage = new(section.GetSection(nameof(Storage))); P2P = new(section.GetSection(nameof(P2P))); @@ -116,4 +119,22 @@ public UnlockWalletSettings(IConfigurationSection section) } } } + + public class ContractsSettings + { + public UInt160 NeoNameService { get; } = UInt160.Zero; + + public ContractsSettings(IConfigurationSection section) + { + if (section.Exists()) + { + if (UInt160.TryParse(section.GetValue(nameof(NeoNameService), string.Empty), out var hash)) + { + NeoNameService = hash; + } + else + throw new ArgumentException("Neo Name Service (NNS): NeoNameService hash is invalid. Check your config.json.", nameof(NeoNameService)); + } + } + } } diff --git a/src/Neo.CLI/config.fs.mainnet.json b/src/Neo.CLI/config.fs.mainnet.json index 57bf751406..248a2a8956 100644 --- a/src/Neo.CLI/config.fs.mainnet.json +++ b/src/Neo.CLI/config.fs.mainnet.json @@ -19,6 +19,9 @@ "Path": "", "Password": "", "IsActive": false + }, + "Contracts": { + "NeoNameService": "0x7061fbd31562664b58f422c3dee4acfd70dba8af" } }, "ProtocolConfiguration": { diff --git a/src/Neo.CLI/config.fs.testnet.json b/src/Neo.CLI/config.fs.testnet.json index a7a930c28b..88a03f7413 100644 --- a/src/Neo.CLI/config.fs.testnet.json +++ b/src/Neo.CLI/config.fs.testnet.json @@ -19,6 +19,9 @@ "Path": "", "Password": "", "IsActive": false + }, + "Contracts": { + "NeoNameService": "0xfb08ccf30ab534a871b7b092a49fe70c154ed678" } }, "ProtocolConfiguration": { diff --git a/src/Neo.CLI/config.json b/src/Neo.CLI/config.json index 897e33afab..5ff5dbb2bb 100644 --- a/src/Neo.CLI/config.json +++ b/src/Neo.CLI/config.json @@ -19,6 +19,9 @@ "Path": "", "Password": "", "IsActive": false + }, + "Contracts": { + "NeoNameService": "0x50ac1c37690cc2cfc594472833cf57505d5f46de" } }, "ProtocolConfiguration": { diff --git a/src/Neo.CLI/config.mainnet.json b/src/Neo.CLI/config.mainnet.json index 897e33afab..5ff5dbb2bb 100644 --- a/src/Neo.CLI/config.mainnet.json +++ b/src/Neo.CLI/config.mainnet.json @@ -19,6 +19,9 @@ "Path": "", "Password": "", "IsActive": false + }, + "Contracts": { + "NeoNameService": "0x50ac1c37690cc2cfc594472833cf57505d5f46de" } }, "ProtocolConfiguration": { diff --git a/src/Neo.CLI/config.testnet.json b/src/Neo.CLI/config.testnet.json index e3bcb7ce95..c6d771ef3a 100644 --- a/src/Neo.CLI/config.testnet.json +++ b/src/Neo.CLI/config.testnet.json @@ -19,6 +19,9 @@ "Path": "", "Password": "", "IsActive": false + }, + "Contracts": { + "NeoNameService": "0x50ac1c37690cc2cfc594472833cf57505d5f46de" } }, "ProtocolConfiguration": { diff --git a/src/Neo.ConsoleService/ConsoleServiceBase.cs b/src/Neo.ConsoleService/ConsoleServiceBase.cs index daa9d9f061..8b169fb808 100644 --- a/src/Neo.ConsoleService/ConsoleServiceBase.cs +++ b/src/Neo.ConsoleService/ConsoleServiceBase.cs @@ -88,10 +88,11 @@ private bool OnCommand(string commandLine) availableCommands.Add((command, arguments.ToArray())); } - catch + catch (Exception ex) { // Skip parse errors possibleHelp = command.Key; + ConsoleHelper.Error($"{ex.InnerException?.Message ?? ex.Message}"); } } }