diff --git a/All.slnx b/All.slnx
index 12eb905..7379cfb 100644
--- a/All.slnx
+++ b/All.slnx
@@ -4,6 +4,8 @@
+
+
diff --git a/src/Neo.Configuration/Json/Converters/JsonIPEndPointConverter.cs b/src/Neo.Configuration/Json/Converters/JsonIPEndPointConverter.cs
new file mode 100644
index 0000000..5a8658e
--- /dev/null
+++ b/src/Neo.Configuration/Json/Converters/JsonIPEndPointConverter.cs
@@ -0,0 +1,82 @@
+// BSD 2-Clause License
+//
+// Copyright (c) 2026, Rapid Loop
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES
+
+using System;
+using System.Net;
+using System.Net.Sockets;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Neo.Configuration.Json.Converters
+{
+ public class JsonIPEndPointConverter : JsonConverter
+ {
+ public override IPEndPoint? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ if (reader.TokenType != JsonTokenType.String)
+ throw new FormatException();
+
+ var valueString = reader.GetString();
+
+ if (string.IsNullOrEmpty(valueString))
+ return default;
+
+ if (IPEndPoint.TryParse(valueString, out var value))
+ return value;
+ else
+ {
+ // Try to see if its a hostname
+ var spString = valueString.Split(':', StringSplitOptions.TrimEntries);
+ if (spString.Length == 2)
+ {
+ try
+ {
+ var host = Dns.GetHostEntry(spString[0]);
+ if (host.AddressList.Length > 0)
+ {
+ var length = host.AddressList.Length - 1;
+ var ipAddress = host.AddressList
+ [
+ length > 0 ? Random.Shared.Next(length) : 0
+ ];
+
+ return new IPEndPoint(ipAddress, int.Parse(spString[1]));
+ }
+ }
+ catch (SocketException) // Host doesn't exist or network is down
+ {
+ }
+ }
+ }
+
+ return default;
+ }
+
+ public override void Write(Utf8JsonWriter writer, IPEndPoint? value, JsonSerializerOptions options)
+ {
+ if (value is null)
+ writer.WriteNullValue();
+ else
+ writer.WriteStringValue($"{value}");
+ }
+ }
+}
diff --git a/src/Neo.Configuration/Json/JsonDefaults.cs b/src/Neo.Configuration/Json/JsonDefaults.cs
index d86631c..6a34d92 100644
--- a/src/Neo.Configuration/Json/JsonDefaults.cs
+++ b/src/Neo.Configuration/Json/JsonDefaults.cs
@@ -48,6 +48,7 @@ public static class JsonDefaults
new JsonStringEnumConverter(),
new JsonStringECPointConverter(),
new JsonStringUInt160Converter(),
+ new JsonIPEndPointConverter(),
},
};
}
diff --git a/src/Neo.Configuration/Json/ProtocolSettingsModel.cs b/src/Neo.Configuration/ProtocolSettingsOptions.cs
similarity index 92%
rename from src/Neo.Configuration/Json/ProtocolSettingsModel.cs
rename to src/Neo.Configuration/ProtocolSettingsOptions.cs
index d6b19f8..5d1103f 100644
--- a/src/Neo.Configuration/Json/ProtocolSettingsModel.cs
+++ b/src/Neo.Configuration/ProtocolSettingsOptions.cs
@@ -20,6 +20,7 @@
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES
+using Neo.Configuration.Json;
using Neo.Core;
using Neo.Core.Cryptography.ECC;
using Neo.Core.Interfaces;
@@ -27,10 +28,11 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
+using System.Net;
-namespace Neo.Configuration.Json
+namespace Neo.Configuration
{
- public class ProtocolSettingsModel : JsonModel, IMap
+ public class ProtocolSettingsOptions : JsonModel, IMap
{
public uint Network { get; set; }
@@ -48,7 +50,7 @@ public class ProtocolSettingsModel : JsonModel, IMap
public int ValidatorsCount { get; set; }
- public string[]? SeedList { get; set; }
+ public IPEndPoint[]? SeedList { get; set; }
public IDictionary? HardForks { get; set; }
diff --git a/src/Neo.Core/Cryptography/ECC/ECPoint.cs b/src/Neo.Core/Cryptography/ECC/ECPoint.cs
index 4fbb837..7a611ca 100644
--- a/src/Neo.Core/Cryptography/ECC/ECPoint.cs
+++ b/src/Neo.Core/Cryptography/ECC/ECPoint.cs
@@ -22,7 +22,9 @@
using Neo.Core.Extensions;
using Neo.Core.Serialization;
+using Neo.Core.Types.Converter;
using System;
+using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Numerics;
@@ -30,6 +32,7 @@
namespace Neo.Core.Cryptography.ECC
{
+ [TypeConverter(typeof(ECPointTypeConverter))]
public class ECPoint : IEquatable, IComparable, IComparable, INeoSerializable
{
public const int UncompressedLength = 65;
diff --git a/src/Neo.Core/ProtocolSettings.cs b/src/Neo.Core/ProtocolSettings.cs
index c4d11fd..8121922 100644
--- a/src/Neo.Core/ProtocolSettings.cs
+++ b/src/Neo.Core/ProtocolSettings.cs
@@ -26,6 +26,7 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
+using System.Net;
namespace Neo.Core
{
@@ -59,7 +60,7 @@ public class ProtocolSettings
///
/// The default seed nodes list.
///
- public IReadOnlyList SeedList { get; init; } = [];
+ public IPEndPoint[] SeedList { get; init; } = [];
///
/// Indicates the time in milliseconds between two blocks. Note that starting from
diff --git a/src/Neo.Core/Types/Converter/ECPointTypeConverter.cs b/src/Neo.Core/Types/Converter/ECPointTypeConverter.cs
new file mode 100644
index 0000000..e98c0f2
--- /dev/null
+++ b/src/Neo.Core/Types/Converter/ECPointTypeConverter.cs
@@ -0,0 +1,54 @@
+// BSD 2-Clause License
+//
+// Copyright (c) 2026, Rapid Loop
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES
+
+using Neo.Core.Cryptography.ECC;
+using System;
+using System.ComponentModel;
+using System.Globalization;
+
+namespace Neo.Core.Types.Converter
+{
+ internal class ECPointTypeConverter : TypeConverter
+ {
+ public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) =>
+ sourceType == typeof(string) && base.CanConvertFrom(context, sourceType);
+
+ public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
+ {
+ if (value is string pointString)
+ {
+ if (ECPoint.TryParse(pointString, ECCurve.SecP256r1, out var point))
+ return point;
+ }
+
+ return base.ConvertFrom(context, culture, value);
+ }
+
+ public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType) =>
+ CanConvertTo(context, destinationType) ?
+ value switch
+ {
+ ECPoint e => e.Encode(shouldCompress: true),
+ _ => base.ConvertTo(context, culture, value, destinationType),
+ } : base.ConvertTo(context, culture, value, destinationType);
+ }
+}
diff --git a/src/Neo.Core/Types/Converter/IPAddressTypeConverter.cs b/src/Neo.Core/Types/Converter/IPAddressTypeConverter.cs
new file mode 100644
index 0000000..295601b
--- /dev/null
+++ b/src/Neo.Core/Types/Converter/IPAddressTypeConverter.cs
@@ -0,0 +1,46 @@
+// BSD 2-Clause License
+//
+// Copyright (c) 2026, Rapid Loop
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES
+
+using System;
+using System.ComponentModel;
+using System.Globalization;
+using System.Net;
+
+namespace Neo.Core.Types.Converter
+{
+ public class IPAddressTypeConverter : TypeConverter
+ {
+ public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) =>
+ sourceType == typeof(string) && base.CanConvertFrom(context, sourceType);
+
+ public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
+ {
+ if (value is string ipString)
+ {
+ if (IPAddress.TryParse(ipString, out var ipAddress))
+ return ipAddress;
+ }
+
+ return base.ConvertFrom(context, culture, value);
+ }
+ }
+}
diff --git a/src/Neo.Core/Types/Converter/IPEndPointTypeConverter.cs b/src/Neo.Core/Types/Converter/IPEndPointTypeConverter.cs
new file mode 100644
index 0000000..9002655
--- /dev/null
+++ b/src/Neo.Core/Types/Converter/IPEndPointTypeConverter.cs
@@ -0,0 +1,72 @@
+// BSD 2-Clause License
+//
+// Copyright (c) 2026, Rapid Loop
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES
+
+using System;
+using System.ComponentModel;
+using System.Globalization;
+using System.Net;
+using System.Net.Sockets;
+
+namespace Neo.Core.Types.Converter
+{
+ public class IPEndPointTypeConverter : TypeConverter
+ {
+ public override bool CanConvertFrom(ITypeDescriptorContext? context, Type sourceType) =>
+ sourceType == typeof(string) && base.CanConvertFrom(context, sourceType);
+
+ public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
+ {
+ if (value is string endPointString)
+ {
+ if (IPEndPoint.TryParse(endPointString, out var ipEndPoint))
+ return ipEndPoint;
+ else
+ {
+ // Try to see if its a hostname
+ var spString = endPointString.Split(':', StringSplitOptions.TrimEntries);
+ if (spString.Length == 2)
+ {
+ try
+ {
+ var host = Dns.GetHostEntry(spString[0]);
+ if (host.AddressList.Length > 0)
+ {
+ var length = host.AddressList.Length - 1;
+ var ipAddress = host.AddressList
+ [
+ length > 0 ? Random.Shared.Next(length) : 0
+ ];
+
+ return new IPEndPoint(ipAddress, int.Parse(spString[1]));
+ }
+ }
+ catch (SocketException) // Host doesn't exist or network is down
+ {
+ }
+ }
+ }
+ }
+
+ return base.ConvertFrom(context, culture, value);
+ }
+ }
+}
diff --git a/src/Neo.Platform.CLI/Commands/EmptyHandler.cs b/src/Neo.Platform.CLI/Commands/EmptyHandler.cs
new file mode 100644
index 0000000..ccb8028
--- /dev/null
+++ b/src/Neo.Platform.CLI/Commands/EmptyHandler.cs
@@ -0,0 +1,37 @@
+// BSD 2-Clause License
+//
+// Copyright (c) 2026, Rapid Loop
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES
+
+using System.CommandLine;
+using System.CommandLine.Invocation;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Neo.Platform.CLI.Commands
+{
+ public class EmptyHandler : AsynchronousCommandLineAction
+ {
+ public override Task InvokeAsync(ParseResult parseResult, CancellationToken cancellationToken = default)
+ {
+ return Task.FromResult(0);
+ }
+ }
+}
diff --git a/src/Neo.Platform.CLI/Commands/ProgramRootCommand.cs b/src/Neo.Platform.CLI/Commands/ProgramRootCommand.cs
new file mode 100644
index 0000000..b4e2d1a
--- /dev/null
+++ b/src/Neo.Platform.CLI/Commands/ProgramRootCommand.cs
@@ -0,0 +1,36 @@
+// BSD 2-Clause License
+//
+// Copyright (c) 2026, Rapid Loop
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES
+
+using System.CommandLine;
+
+namespace Neo.Platform.CLI.Commands
+{
+ internal class ProgramRootCommand : RootCommand
+ {
+ public ProgramRootCommand() : base("Neo Platform Command-line Tool")
+ {
+ var showCommand = new ShowCommand();
+
+ Add(showCommand);
+ }
+ }
+}
diff --git a/src/Neo.Platform.CLI/Commands/ShowCommand.cs b/src/Neo.Platform.CLI/Commands/ShowCommand.cs
new file mode 100644
index 0000000..c1515fa
--- /dev/null
+++ b/src/Neo.Platform.CLI/Commands/ShowCommand.cs
@@ -0,0 +1,50 @@
+// BSD 2-Clause License
+//
+// Copyright (c) 2026, Rapid Loop
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES
+
+using Microsoft.Extensions.Logging;
+using System.CommandLine;
+using System.CommandLine.Invocation;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Neo.Platform.CLI.Commands
+{
+ internal class ShowCommand : Command
+ {
+ public ShowCommand() : base("show", "Shows many configurations")
+ {
+
+ }
+
+ public class Handler(ILoggerFactory loggerFactory) : AsynchronousCommandLineAction
+ {
+ private readonly ILogger _logger = loggerFactory.CreateLogger();
+
+ public override Task InvokeAsync(ParseResult parseResult, CancellationToken cancellationToken = default)
+ {
+ _logger.LogInformation("Hello World!!!!");
+
+ return Task.FromResult(0);
+ }
+ }
+ }
+}
diff --git a/src/Neo.Platform.CLI/Neo.Platform.CLI.csproj b/src/Neo.Platform.CLI/Neo.Platform.CLI.csproj
new file mode 100644
index 0000000..8c6ee08
--- /dev/null
+++ b/src/Neo.Platform.CLI/Neo.Platform.CLI.csproj
@@ -0,0 +1,28 @@
+
+
+
+ Exe
+ net10.0
+ enable
+ true
+ neo
+ neo;blockchain;cli;vm;test;rapid-loop
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Always
+
+
+
+
diff --git a/src/Neo.Platform.CLI/Program.cs b/src/Neo.Platform.CLI/Program.cs
new file mode 100644
index 0000000..6f5a1b6
--- /dev/null
+++ b/src/Neo.Platform.CLI/Program.cs
@@ -0,0 +1,53 @@
+// BSD 2-Clause License
+//
+// Copyright (c) 2026, Rapid Loop
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES
+
+using Microsoft.Extensions.Hosting;
+using Neo.Platform.CLI.Commands;
+using Neo.Platform.Hosting;
+using Neo.Platform.Hosting.Builder;
+using System.Threading.Tasks;
+
+namespace Neo.Platform.CLI
+{
+ internal class Program
+ {
+ private static Task Main(string[] args)
+ {
+ var rootCommand = new ProgramRootCommand();
+ var cmd = new PlatformCommandLineBuilder(rootCommand, args)
+ .UseHost(DefaultNeoBuildHostFactory, builder =>
+ {
+ //builder.UseCommandAction();
+ builder.UseCommandAction();
+ })
+ .EnablePosixBundling()
+ .EnableDefaultExceptionHandler(false)
+ .Build();
+
+ return cmd.InvokeAsync();
+ }
+
+ private static IHostBuilder DefaultNeoBuildHostFactory(string[] args) =>
+ new HostBuilder()
+ .UseNeoPlatformConfiguration();
+ }
+}
diff --git a/src/Neo.Platform.CLI/Properties/launchSettings.json b/src/Neo.Platform.CLI/Properties/launchSettings.json
new file mode 100644
index 0000000..54a8d6a
--- /dev/null
+++ b/src/Neo.Platform.CLI/Properties/launchSettings.json
@@ -0,0 +1,8 @@
+{
+ "profiles": {
+ "Neo.Platform.CLI": {
+ "commandName": "Project",
+ "commandLineArgs": "show"
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Neo.Platform.CLI/config.json b/src/Neo.Platform.CLI/config.json
new file mode 100644
index 0000000..0db3279
--- /dev/null
+++ b/src/Neo.Platform.CLI/config.json
@@ -0,0 +1,3 @@
+{
+
+}
diff --git a/src/Neo.Platform.CLI/protocol.json b/src/Neo.Platform.CLI/protocol.json
new file mode 100644
index 0000000..0db3279
--- /dev/null
+++ b/src/Neo.Platform.CLI/protocol.json
@@ -0,0 +1,3 @@
+{
+
+}
diff --git a/src/Neo.Platform.CLI/vm.json b/src/Neo.Platform.CLI/vm.json
new file mode 100644
index 0000000..0db3279
--- /dev/null
+++ b/src/Neo.Platform.CLI/vm.json
@@ -0,0 +1,3 @@
+{
+
+}
diff --git a/src/Neo.Platform.Hosting/Builder/PlatformCommandLineBuilder.cs b/src/Neo.Platform.Hosting/Builder/PlatformCommandLineBuilder.cs
new file mode 100644
index 0000000..43876ef
--- /dev/null
+++ b/src/Neo.Platform.Hosting/Builder/PlatformCommandLineBuilder.cs
@@ -0,0 +1,116 @@
+// BSD 2-Clause License
+//
+// Copyright (c) 2026, Rapid Loop
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES
+
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Neo.Platform.Hosting.Middleware;
+using System;
+using System.Collections.Generic;
+using System.CommandLine;
+using System.Linq;
+
+namespace Neo.Platform.Hosting.Builder
+{
+ public class PlatformCommandLineBuilder(Command? rootCommand = default, string[]? args = default)
+ {
+ internal Command Command { get; } = rootCommand ?? new RootCommand();
+ internal ParserConfiguration ParserConfiguration { get; } = new();
+ internal InvocationConfiguration InvocationConfiguration { get; } = new();
+
+ private readonly List> _middleware = [];
+ private IHostBuilder? _hostBuilder;
+
+ internal void AddMiddleware(Action middleware) =>
+ _middleware.Add(middleware);
+
+ public PlatformCommandLineBuilder UseHost(
+ Func? hostBuilderFactory,
+ Action? configure = default)
+ {
+ AddMiddleware(async (parseResult, next) =>
+ {
+ var argsRemaining = parseResult.UnmatchedTokens.ToArray();
+ _hostBuilder = hostBuilderFactory?.Invoke(argsRemaining) ??
+ new HostBuilder();
+
+ _hostBuilder.Properties[typeof(ParseResult)] = parseResult;
+
+ _hostBuilder.ConfigureServices(services =>
+ {
+ services.AddTransient(_ => parseResult);
+ });
+
+ _hostBuilder.UseNeoPlatformLifetime();
+ configure?.Invoke(_hostBuilder);
+
+ next(parseResult);
+ });
+
+ return this;
+ }
+
+ public PlatformCommandLineBuilder EnableDefaultExceptionHandler(bool value = true)
+ {
+ InvocationConfiguration.EnableDefaultExceptionHandler = value;
+ return this;
+ }
+
+ public PlatformCommandLineBuilder SetProcessTerminationTimeout(TimeSpan timeout)
+ {
+ InvocationConfiguration.ProcessTerminationTimeout = timeout;
+ return this;
+ }
+
+ public PlatformCommandLineBuilder EnablePosixBundling(bool value = true)
+ {
+ ParserConfiguration.EnablePosixBundling = value;
+ return this;
+ }
+
+ public PlatformCommandLine Build()
+ {
+ var parseResults = Command.Parse(args ?? [], ParserConfiguration);
+
+ BuildChain().Invoke(parseResults);
+
+ var host = (_hostBuilder ?? new HostBuilder()).Build();
+ return new(parseResults, host, InvocationConfiguration);
+ }
+
+ private PlatformCommandLineNextDelegate BuildChain()
+ {
+ PlatformCommandLineNextDelegate app = _ => { }; // terminal handler (no-op)
+
+ // Build chain in reverse so first registered middleware runs first
+ for (var i = _middleware.Count - 1; i >= 0; i--)
+ {
+ var middleware = _middleware[i];
+ var current = middleware;
+ var next = app;
+
+ app = context => current(context, next);
+ }
+
+ return app;
+ }
+ }
+}
diff --git a/src/Neo.Platform.Hosting/Configuration/ConfigurationBuilderExtensions.cs b/src/Neo.Platform.Hosting/Configuration/ConfigurationBuilderExtensions.cs
new file mode 100644
index 0000000..ebffc0c
--- /dev/null
+++ b/src/Neo.Platform.Hosting/Configuration/ConfigurationBuilderExtensions.cs
@@ -0,0 +1,36 @@
+// BSD 2-Clause License
+//
+// Copyright (c) 2026, Rapid Loop
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES
+
+using Microsoft.Extensions.Configuration;
+using System.Collections.Generic;
+
+namespace Neo.Platform.Hosting.Configuration
+{
+ public static class ConfigurationBuilderExtensions
+ {
+ public static IConfigurationBuilder AddNeoPlatformConfiguration(this IConfigurationBuilder builder, IEnumerable>? initialData = default)
+ {
+ builder.Add(new NeoEnvironmentConfigurationSource() { InitialData = initialData, });
+ return builder;
+ }
+ }
+}
diff --git a/src/Neo.Platform.Hosting/Configuration/NeoEnvironmentConfigurationSource.cs b/src/Neo.Platform.Hosting/Configuration/NeoEnvironmentConfigurationSource.cs
new file mode 100644
index 0000000..9f7a9be
--- /dev/null
+++ b/src/Neo.Platform.Hosting/Configuration/NeoEnvironmentConfigurationSource.cs
@@ -0,0 +1,36 @@
+// BSD 2-Clause License
+//
+// Copyright (c) 2026, Rapid Loop
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES
+
+using Microsoft.Extensions.Configuration;
+using Neo.Platform.Hosting.Provider;
+using System.Collections.Generic;
+
+namespace Neo.Platform.Hosting.Configuration
+{
+ internal class NeoEnvironmentConfigurationSource : IConfigurationSource
+ {
+ public IEnumerable>? InitialData { get; set; }
+
+ public IConfigurationProvider Build(IConfigurationBuilder builder) =>
+ new NeoEnvironmentConfigurationProvider(this);
+ }
+}
diff --git a/src/Neo.Platform.Hosting/Factory/NeoHostingFactory.cs b/src/Neo.Platform.Hosting/Factory/NeoHostingFactory.cs
new file mode 100644
index 0000000..f4b0ace
--- /dev/null
+++ b/src/Neo.Platform.Hosting/Factory/NeoHostingFactory.cs
@@ -0,0 +1,52 @@
+// BSD 2-Clause License
+//
+// Copyright (c) 2026, Rapid Loop
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES
+
+using System.IO;
+using System.Threading;
+
+namespace Neo.Platform.Hosting.Factory
+{
+ internal static class NeoHostingFactory
+ {
+ private static readonly uint s_networkSeed = 810960196u; // DEV0 Magic Code
+ private static readonly string s_globalString = "Global\\";
+
+ public static uint GetDevNetwork(uint index) =>
+ s_networkSeed & ~(0xfu << 24) | (index << 24);
+
+ public static FileInfo ResolveFileName(string filename, string rootPath)
+ {
+ if (Path.IsPathRooted(filename))
+ return new(filename);
+ if (Path.IsPathRooted(rootPath) == false)
+ rootPath = Path.GetFullPath(rootPath);
+ return new(Path.Combine(rootPath, filename));
+ }
+
+ public static Mutex CreateMutex(string? name = null)
+ {
+ if (string.IsNullOrEmpty(name))
+ name = Path.Combine(s_globalString, Path.GetRandomFileName());
+ return new(initiallyOwned: true, name);
+ }
+ }
+}
diff --git a/src/Neo.Platform.Hosting/HostEnvironmentExtensions.cs b/src/Neo.Platform.Hosting/HostEnvironmentExtensions.cs
new file mode 100644
index 0000000..2adefa1
--- /dev/null
+++ b/src/Neo.Platform.Hosting/HostEnvironmentExtensions.cs
@@ -0,0 +1,54 @@
+// BSD 2-Clause License
+//
+// Copyright (c) 2026, Rapid Loop
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES
+
+using Microsoft.Extensions.Hosting;
+using System;
+
+namespace Neo.Platform.Hosting
+{
+ public static class HostEnvironmentExtensions
+ {
+ public static bool IsLocalNet(this IHostEnvironment hostEnvironment)
+ {
+ ArgumentNullException.ThrowIfNull(hostEnvironment, nameof(hostEnvironment));
+ return hostEnvironment.IsEnvironment(NeoHostingEnvironmentNames.LocalNet);
+ }
+
+ public static bool IsTestNet(this IHostEnvironment hostEnvironment)
+ {
+ ArgumentNullException.ThrowIfNull(hostEnvironment, nameof(hostEnvironment));
+ return hostEnvironment.IsEnvironment(NeoHostingEnvironmentNames.TestNet);
+ }
+
+ public static bool IsMainNet(this IHostEnvironment hostEnvironment)
+ {
+ ArgumentNullException.ThrowIfNull(hostEnvironment, nameof(hostEnvironment));
+ return hostEnvironment.IsEnvironment(NeoHostingEnvironmentNames.MainNet);
+ }
+
+ public static bool IsPrivateNet(this IHostEnvironment hostEnvironment)
+ {
+ ArgumentNullException.ThrowIfNull(hostEnvironment, nameof(hostEnvironment));
+ return hostEnvironment.IsEnvironment(NeoHostingEnvironmentNames.PrivateNet);
+ }
+ }
+}
diff --git a/src/Neo.Platform.Hosting/HostingExtensions.cs b/src/Neo.Platform.Hosting/HostingExtensions.cs
new file mode 100644
index 0000000..de711ab
--- /dev/null
+++ b/src/Neo.Platform.Hosting/HostingExtensions.cs
@@ -0,0 +1,186 @@
+// BSD 2-Clause License
+//
+// Copyright (c) 2026, Rapid Loop
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES
+
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.EventLog;
+using Neo.Configuration;
+using Neo.Core;
+using Neo.Core.Types.Converter;
+using Neo.Platform.Hosting.Configuration;
+using Neo.Platform.Hosting.Logging;
+using System;
+using System.CommandLine;
+using System.CommandLine.Invocation;
+using System.ComponentModel;
+using System.Net;
+
+namespace Neo.Platform.Hosting
+{
+ public static class HostingExtensions
+ {
+
+ public static IHostBuilder UseCommandAction(this IHostBuilder builder)
+ where TCommand : Command
+ where TCommandAction : CommandLineAction
+ {
+ if (builder.Properties[typeof(ParseResult)] is ParseResult parseResult &&
+ parseResult.CommandResult.Command is Command command &&
+ command.GetType() == typeof(TCommand))
+ {
+ builder.ConfigureServices(services =>
+ {
+ services.AddTransient();
+
+ var sp = services.BuildServiceProvider();
+ command.Action = sp.GetRequiredService();
+ });
+ }
+
+ return builder;
+ }
+
+ public static IHostBuilder UseNeoPlatformConfiguration(this IHostBuilder builder)
+ {
+ // Host Configuration
+ builder.ConfigureHostConfiguration
+ (
+ static config =>
+ {
+ var manger = new ConfigurationManager();
+
+ config.AddConfiguration(manger);
+ config.AddNeoPlatformConfiguration();
+ }
+ );
+
+ // Application Configuration
+ builder.ConfigureAppConfiguration
+ (
+ static (context, config) =>
+ {
+ var environmentName = context.HostingEnvironment.EnvironmentName;
+
+ // Executable Directory (Config)
+ config.SetBasePath(AppContext.BaseDirectory);
+ config.AddJsonFile("config.json", optional: false); // application global settings (gets overwritten by environment)
+
+ // Working Directory (Config)
+ var workingDir = context.Configuration[HostDefaults.ContentRootKey];
+
+ // NOTE: JSON Files Overwrite Environment Variables
+ config.SetBasePath($"{workingDir}");
+ config.AddJsonFile($"config.{environmentName}.json", optional: true); // application settings by environment
+ config.AddJsonFile($"protocol.json", optional: false); // protocol global settings (gets overwritten by environment)
+ config.AddJsonFile($"protocol.{environmentName}.json", optional: true); // protocol settings by environment
+ config.AddJsonFile($"vm.json", optional: false); // vm global settings (gets overwritten by environment)
+ config.AddJsonFile($"vm.{environmentName}.json", optional: true); // vm settings by environment
+ }
+ );
+
+ // Logging Configuration
+ builder.ConfigureLogging
+ (
+ static (context, logging) =>
+ {
+ var isWindows = OperatingSystem.IsWindows();
+
+ if (isWindows)
+ logging.AddFilter
+ (
+ level => level >= LogLevel.Warning
+ );
+
+ logging.AddConfiguration(context.Configuration.GetSection("Logging"));
+ logging.AddNeoPlatform();
+#if DEBUG
+ logging.AddDebug();
+#endif
+ logging.AddEventSourceLogger();
+
+ if (isWindows)
+ logging.AddEventLog();
+
+ logging.Configure
+ (
+ static options =>
+ {
+ options.ActivityTrackingOptions = ActivityTrackingOptions.SpanId
+ | ActivityTrackingOptions.TraceId
+ | ActivityTrackingOptions.ParentId;
+ }
+ );
+ }
+ );
+
+ builder.UseDefaultServiceProvider
+ (
+ static (context, options) =>
+ {
+ var isDevelopment = context.HostingEnvironment.IsDevelopment();
+
+ options.ValidateScopes = isDevelopment;
+ options.ValidateOnBuild = isDevelopment;
+ }
+ );
+
+ builder.UseNeoPlatformLifetime();
+ builder.UseNeoPlatformOptions();
+
+ builder.ConfigureServices
+ (
+ static (context, services) =>
+ {
+ // Add default services
+ }
+ );
+
+ // IConfiguration.Get() Converting
+ TypeDescriptor.AddAttributes(typeof(IPAddress), new TypeConverterAttribute(typeof(IPAddressTypeConverter)));
+ TypeDescriptor.AddAttributes(typeof(IPEndPoint), new TypeConverterAttribute(typeof(IPEndPointTypeConverter)));
+
+ return builder;
+ }
+
+ public static IHostBuilder UseNeoPlatformOptions(this IHostBuilder builder) =>
+ builder.ConfigureServices
+ (
+ static (context, services) =>
+ {
+ var protocolSettingsSection = context.Configuration.GetSection(ProtocolSettingNames.SectionKey);
+ var protocolSettingsOptions = protocolSettingsSection.Get();
+ services.AddSingleton(protocolSettingsOptions?.ToObject() ?? ProtocolSettings.Default);
+ }
+ );
+
+ public static IHostBuilder UseNeoPlatformLifetime(this IHostBuilder builder, Action? configure = default) =>
+ builder.ConfigureServices(
+ services =>
+ {
+ services.AddSingleton();
+ if (configure is not null)
+ services.Configure(configure);
+ });
+ }
+}
diff --git a/src/Neo.Core/Logging/LoggingBuilderExtensions.cs b/src/Neo.Platform.Hosting/Logging/LoggingBuilderExtensions.cs
similarity index 96%
rename from src/Neo.Core/Logging/LoggingBuilderExtensions.cs
rename to src/Neo.Platform.Hosting/Logging/LoggingBuilderExtensions.cs
index 1f422ae..201c77d 100644
--- a/src/Neo.Core/Logging/LoggingBuilderExtensions.cs
+++ b/src/Neo.Platform.Hosting/Logging/LoggingBuilderExtensions.cs
@@ -26,14 +26,12 @@
using Microsoft.Extensions.Logging.Configuration;
using System;
-namespace Neo.Core.Logging
+namespace Neo.Platform.Hosting.Logging
{
public static class LoggingBuilderExtensions
{
public static ILoggingBuilder AddNeoPlatform(this ILoggingBuilder builder)
{
- builder.AddConfiguration();
-
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton());
LoggerProviderOptions.RegisterProviderOptions(builder.Services);
diff --git a/src/Neo.Core/Logging/NeoPlatformLogger.cs b/src/Neo.Platform.Hosting/Logging/NeoPlatformLogger.cs
similarity index 81%
rename from src/Neo.Core/Logging/NeoPlatformLogger.cs
rename to src/Neo.Platform.Hosting/Logging/NeoPlatformLogger.cs
index 236f2c2..e04d3f8 100644
--- a/src/Neo.Core/Logging/NeoPlatformLogger.cs
+++ b/src/Neo.Platform.Hosting/Logging/NeoPlatformLogger.cs
@@ -22,10 +22,9 @@
using Microsoft.Extensions.Logging;
using System;
-using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
-namespace Neo.Core.Logging
+namespace Neo.Platform.Hosting.Logging
{
internal sealed class NeoPlatformLogger : ILogger
{
@@ -61,7 +60,7 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except
if (string.IsNullOrEmpty(message) && exception == null)
return;
- message = $"{_name}[{eventId.Id}] {message}";
+ message = $"{_name}[{eventId.Name ?? $"{eventId.Id:d}"}] {message}";
switch (logLevel)
{
@@ -80,7 +79,7 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except
case LogLevel.Error:
case LogLevel.Critical:
ErrorMessage("{0}", message);
- if (exception != null)
+ if (exception is not null)
ErrorMessage(exception, ShowExceptionStackTrace);
break;
default:
@@ -88,15 +87,26 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except
}
}
- private void WriteDateTime() =>
- Write($"[{BuildFormatString(_getConfig().TimestampFormat)}] ", DateTimeNow);
+ private void WriteDateTime(bool isError = false)
+ {
+ var format = $"[{BuildFormatString(_getConfig().TimestampFormat)}] ";
+ if (isError)
+ ErrorWrite(format, DateTimeNow);
+ else
+ Write(format, DateTimeNow);
+ }
public static void Write([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, params object?[] args)
{
var message = string.Format(format, args);
Console.Out.Write(message);
- DebugWrite(message);
+ }
+ public static void ErrorWrite([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, params object?[] args)
+ {
+ var message = string.Format(format, args);
+
+ Console.Error.Write(message);
}
public static void WriteLine() =>
@@ -105,6 +115,12 @@ public static void WriteLine() =>
public static void WriteLine([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, params object?[] args) =>
Write(string.Format(format, args) + Environment.NewLine);
+ public static void ErrorWriteLine() =>
+ ErrorWrite(Environment.NewLine);
+
+ public static void ErrorWriteLine([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, params object?[] args) =>
+ ErrorWrite(string.Format(format, args) + Environment.NewLine);
+
public void InfoMessage([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, params object?[] args)
{
WriteDateTime();
@@ -148,17 +164,17 @@ public void ErrorMessage(Exception exception, bool showStackTrace = true)
var stackTrace = exception.InnerException?.StackTrace ?? exception.StackTrace;
SetTerminalForegroundColor(ConsoleColor.Red);
- WriteDateTime();
+ WriteDateTime(isError: true);
SetTerminalForegroundColor(ConsoleColor.DarkRed);
- WriteLine("{0}: ", exception.InnerException?.GetType().Name ?? exception.GetType().Name);
+ ErrorWriteLine("{0}: ", exception.InnerException?.GetType().Name ?? exception.GetType().Name);
SetTerminalForegroundColor(ConsoleColor.Red);
- WriteLine(" {0}", exception.InnerException?.Message ?? exception.Message);
+ ErrorWriteLine(" {0}", exception.InnerException?.Message ?? exception.Message);
if (showStackTrace)
{
- WriteLine("Stack Trace: ");
+ ErrorWriteLine("Stack Trace: ");
SetTerminalForegroundColor(ConsoleColor.DarkRed);
- WriteLine(" {0}", stackTrace?.Trim() ?? string.Empty);
+ ErrorWriteLine(" {0}", stackTrace?.Trim() ?? string.Empty);
}
ResetColor();
@@ -167,19 +183,14 @@ public void ErrorMessage(Exception exception, bool showStackTrace = true)
public void ErrorMessage([StringSyntax(StringSyntaxAttribute.CompositeFormat)] string format, params object?[] args)
{
SetTerminalForegroundColor(ConsoleColor.Red);
- WriteDateTime();
+ WriteDateTime(isError: true);
SetTerminalForegroundColor(ConsoleColor.DarkRed);
- Write("Error: ");
+ ErrorWrite("Error: ");
SetTerminalForegroundColor(ConsoleColor.Red);
- WriteLine(format, args);
+ ErrorWriteLine(format, args);
ResetColor();
}
- private static void DebugWrite(string? message)
- {
- if (Debugger.IsAttached) Debug.Write(message);
- }
-
public static void SetTerminalForegroundColor(ConsoleColor consoleColor)
{
Console.ForegroundColor = consoleColor;
diff --git a/src/Neo.Core/Logging/NeoPlatformLoggerOptions.cs b/src/Neo.Platform.Hosting/Logging/NeoPlatformLoggerOptions.cs
similarity index 97%
rename from src/Neo.Core/Logging/NeoPlatformLoggerOptions.cs
rename to src/Neo.Platform.Hosting/Logging/NeoPlatformLoggerOptions.cs
index b4930bc..90672d6 100644
--- a/src/Neo.Core/Logging/NeoPlatformLoggerOptions.cs
+++ b/src/Neo.Platform.Hosting/Logging/NeoPlatformLoggerOptions.cs
@@ -22,7 +22,7 @@
using System.Diagnostics.CodeAnalysis;
-namespace Neo.Core.Logging
+namespace Neo.Platform.Hosting.Logging
{
public sealed class NeoPlatformLoggerOptions
{
diff --git a/src/Neo.Core/Logging/NeoPlatformLoggerProvider.cs b/src/Neo.Platform.Hosting/Logging/NeoPlatformLoggerProvider.cs
similarity index 95%
rename from src/Neo.Core/Logging/NeoPlatformLoggerProvider.cs
rename to src/Neo.Platform.Hosting/Logging/NeoPlatformLoggerProvider.cs
index 908945a..6882d01 100644
--- a/src/Neo.Core/Logging/NeoPlatformLoggerProvider.cs
+++ b/src/Neo.Platform.Hosting/Logging/NeoPlatformLoggerProvider.cs
@@ -26,11 +26,11 @@
using System.Collections.Concurrent;
using System.Runtime.Versioning;
-namespace Neo.Core.Logging
+namespace Neo.Platform.Hosting.Logging
{
[UnsupportedOSPlatform("browser")]
[ProviderAlias("NeoPlatform")]
- public sealed class NeoPlatformLoggerProvider : ILoggerProvider
+ internal sealed class NeoPlatformLoggerProvider : ILoggerProvider
{
private readonly IDisposable? _onChangeToken;
private readonly ConcurrentDictionary _loggers = new(StringComparer.OrdinalIgnoreCase);
diff --git a/src/Neo.Platform.Hosting/Middleware/CommandLineMiddleware.cs b/src/Neo.Platform.Hosting/Middleware/CommandLineMiddleware.cs
new file mode 100644
index 0000000..53204f4
--- /dev/null
+++ b/src/Neo.Platform.Hosting/Middleware/CommandLineMiddleware.cs
@@ -0,0 +1,28 @@
+// BSD 2-Clause License
+//
+// Copyright (c) 2026, Rapid Loop
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES
+
+using System.CommandLine;
+
+namespace Neo.Platform.Hosting.Middleware
+{
+ public delegate void PlatformCommandLineNextDelegate(ParseResult parseResult);
+}
diff --git a/src/Neo.Platform.Hosting/Neo.Platform.Hosting.csproj b/src/Neo.Platform.Hosting/Neo.Platform.Hosting.csproj
new file mode 100644
index 0000000..6225137
--- /dev/null
+++ b/src/Neo.Platform.Hosting/Neo.Platform.Hosting.csproj
@@ -0,0 +1,18 @@
+
+
+
+ net10.0
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Neo.Platform.Hosting/NeoHostingEnvironments.cs b/src/Neo.Platform.Hosting/NeoHostingEnvironments.cs
new file mode 100644
index 0000000..f234eb9
--- /dev/null
+++ b/src/Neo.Platform.Hosting/NeoHostingEnvironments.cs
@@ -0,0 +1,36 @@
+// BSD 2-Clause License
+//
+// Copyright (c) 2026, Rapid Loop
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES
+
+namespace Neo.Platform.Hosting
+{
+ internal static class NeoHostingEnvironmentNames
+ {
+ public static readonly string LocalNet = "localnet";
+
+ public static readonly string TestNet = "testnet";
+
+ public static readonly string MainNet = "mainnet";
+
+ public static readonly string PrivateNet = "privatenet";
+
+ }
+}
diff --git a/src/Neo.Platform.Hosting/NeoPlatformLifetime.cs b/src/Neo.Platform.Hosting/NeoPlatformLifetime.cs
new file mode 100644
index 0000000..45c2d80
--- /dev/null
+++ b/src/Neo.Platform.Hosting/NeoPlatformLifetime.cs
@@ -0,0 +1,119 @@
+// BSD 2-Clause License
+//
+// Copyright (c) 2026, Rapid Loop
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES
+
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Logging.Abstractions;
+using Microsoft.Extensions.Options;
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Neo.Platform.Hosting
+{
+ public class NeoPlatformLifetime : IHostLifetime, IDisposable
+ {
+ public NeoPlatformLifetimeOptions Options { get; }
+ public IHostEnvironment Environment { get; }
+ public IHostApplicationLifetime ApplicationLifetime { get; }
+
+ private CancellationTokenRegistration _appStartedReg;
+ private CancellationTokenRegistration _appStoppingReg;
+
+ private readonly ILogger _logger;
+
+ public NeoPlatformLifetime(
+ IOptions options,
+ IHostEnvironment environment,
+ IHostApplicationLifetime applicationLifetime,
+ ILoggerFactory? loggerFactory = default)
+ {
+ Options = options?.Value ??
+ throw new ArgumentNullException(nameof(options));
+ Environment = environment ??
+ throw new ArgumentNullException(nameof(environment));
+ ApplicationLifetime = applicationLifetime ??
+ throw new ArgumentNullException(nameof(applicationLifetime));
+
+ _logger = (loggerFactory ?? NullLoggerFactory.Instance)
+ .CreateLogger("Neo.Hosting.Lifetime");
+ }
+
+ public void Dispose()
+ {
+ _appStartedReg.Dispose();
+ _appStoppingReg.Dispose();
+
+ GC.SuppressFinalize(this);
+ }
+
+ public Task StopAsync(CancellationToken cancellationToken)
+ {
+ // Nothing to do
+ return Task.CompletedTask;
+ }
+
+ public Task WaitForStartAsync(CancellationToken cancellationToken)
+ {
+ if (Options.SuppressStatusMessages == false)
+ {
+ _appStartedReg = ApplicationLifetime.ApplicationStarted.Register
+ (
+ static state =>
+ {
+ if (state is NeoPlatformLifetime pl)
+ pl.OnApplicationStarted();
+ },
+ this
+ );
+
+ _appStoppingReg = ApplicationLifetime.ApplicationStopping.Register
+ (
+ static state =>
+ {
+ if (state is NeoPlatformLifetime pl)
+ pl.OnApplicationStopping();
+ },
+ this
+ );
+ }
+
+ return Task.CompletedTask;
+ }
+
+ private void OnApplicationStarted()
+ {
+ if (_logger.IsEnabled(LogLevel.Information))
+ {
+ _logger.LogInformation("Neo platform started. Press Ctrl+C to shut down.");
+ _logger.LogInformation("Hosting environment: {envName}", Environment.EnvironmentName);
+ _logger.LogInformation("Content root path: {contentRoot}", Environment.ContentRootPath);
+ }
+ }
+
+ private void OnApplicationStopping()
+ {
+ if (_logger.IsEnabled(LogLevel.Information))
+ _logger.LogInformation("Neo platform is shutting down...");
+ }
+ }
+}
diff --git a/src/Neo.Platform.Hosting/NeoPlatformLifetimeOptions.cs b/src/Neo.Platform.Hosting/NeoPlatformLifetimeOptions.cs
new file mode 100644
index 0000000..9a3e252
--- /dev/null
+++ b/src/Neo.Platform.Hosting/NeoPlatformLifetimeOptions.cs
@@ -0,0 +1,30 @@
+// BSD 2-Clause License
+//
+// Copyright (c) 2026, Rapid Loop
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES
+
+using Microsoft.Extensions.Hosting;
+
+namespace Neo.Platform.Hosting
+{
+ public class NeoPlatformLifetimeOptions : ConsoleLifetimeOptions
+ {
+ }
+}
diff --git a/src/Neo.Platform.Hosting/PlatformCommandLine.cs b/src/Neo.Platform.Hosting/PlatformCommandLine.cs
new file mode 100644
index 0000000..953c3b9
--- /dev/null
+++ b/src/Neo.Platform.Hosting/PlatformCommandLine.cs
@@ -0,0 +1,56 @@
+// BSD 2-Clause License
+//
+// Copyright (c) 2026, Rapid Loop
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES
+
+using Microsoft.Extensions.Hosting;
+using System;
+using System.CommandLine;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Neo.Platform.Hosting
+{
+ public sealed class PlatformCommandLine(ParseResult parseResult, IHost host, InvocationConfiguration invocationConfiguration)
+ {
+ public async Task InvokeAsync(CancellationToken cancellationToken = default)
+ {
+ await host.StartAsync(cancellationToken);
+
+ var exitCode = 0;
+
+ try
+ {
+ exitCode = await parseResult.InvokeAsync(invocationConfiguration, cancellationToken);
+ }
+ catch (Exception ex)
+ {
+ exitCode = ex.HResult;
+ }
+ finally
+ {
+ await host.StopAsync(cancellationToken);
+ }
+
+
+ return exitCode;
+ }
+ }
+}
diff --git a/src/Neo.Platform.Hosting/ProtocolSettingNames.cs b/src/Neo.Platform.Hosting/ProtocolSettingNames.cs
new file mode 100644
index 0000000..a34e65b
--- /dev/null
+++ b/src/Neo.Platform.Hosting/ProtocolSettingNames.cs
@@ -0,0 +1,43 @@
+// BSD 2-Clause License
+//
+// Copyright (c) 2026, Rapid Loop
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES
+
+using Neo.Core;
+
+namespace Neo.Platform.Hosting
+{
+ public static class ProtocolSettingNames
+ {
+ public static readonly string SectionKey = "Protocol";
+
+ public static readonly string NetworkKey = $"{SectionKey}:{nameof(ProtocolSettings.Network)}";
+ public static readonly string AddressVersionKey = $"{SectionKey}:{nameof(ProtocolSettings.AddressVersion)}";
+ public static readonly string StandbyCommitteeKey = $"{SectionKey}:{nameof(ProtocolSettings.StandbyCommittee)}";
+ public static readonly string ValidatorsCountKey = $"{SectionKey}:{nameof(ProtocolSettings.ValidatorsCount)}";
+ public static readonly string HardForksKey = $"{SectionKey}:{nameof(ProtocolSettings.HardForks)}";
+ public static readonly string SeedListKey = $"{SectionKey}:{nameof(ProtocolSettings.SeedList)}";
+ public static readonly string MillisecondsPerBlockKey = $"{SectionKey}:{nameof(ProtocolSettings.MillisecondsPerBlock)}";
+ public static readonly string MaxTransactionsPerBlockKey = $"{SectionKey}:{nameof(ProtocolSettings.MaxTransactionsPerBlock)}";
+ public static readonly string MemoryPoolMaxTransactionsKey = $"{SectionKey}:{nameof(ProtocolSettings.MemoryPoolMaxTransactions)}";
+ public static readonly string MaxTraceableBlocksKey = $"{SectionKey}:{nameof(ProtocolSettings.MaxTraceableBlocks)}";
+ public static readonly string InitialGasDistributionKey = $"{SectionKey}:{nameof(ProtocolSettings.InitialGasDistribution)}";
+ }
+}
diff --git a/src/Neo.Platform.Hosting/Provider/NeoEnvironmentConfigurationProvider.cs b/src/Neo.Platform.Hosting/Provider/NeoEnvironmentConfigurationProvider.cs
new file mode 100644
index 0000000..ac40e2e
--- /dev/null
+++ b/src/Neo.Platform.Hosting/Provider/NeoEnvironmentConfigurationProvider.cs
@@ -0,0 +1,111 @@
+// BSD 2-Clause License
+//
+// Copyright (c) 2026, Rapid Loop
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+// list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+// this list of conditions and the following disclaimer in the documentation
+// and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES
+
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Hosting;
+using Neo.Platform.Hosting.Configuration;
+using Neo.Platform.Hosting.Factory;
+using System;
+using System.Collections;
+using System.Collections.Generic;
+
+namespace Neo.Platform.Hosting.Provider
+{
+ internal sealed class NeoEnvironmentConfigurationProvider : ConfigurationProvider
+ {
+ private const string PREFIX = "NEO";
+
+ private readonly NeoEnvironmentConfigurationSource _source;
+
+ public NeoEnvironmentConfigurationProvider(
+ NeoEnvironmentConfigurationSource source)
+ {
+ _source = source;
+
+ // ==================================
+ // * NEO Platform Initial Defaults *
+ // ==================================
+
+ // Hosting Environment
+ Data.Add(HostDefaults.EnvironmentKey, NeoHostingEnvironmentNames.LocalNet);
+ Data.Add(HostDefaults.ContentRootKey, Environment.CurrentDirectory);
+
+ // Protocol Configuration
+ var protocolNetwork = NeoHostingFactory.GetDevNetwork(0);
+
+ Data.Add(ProtocolSettingNames.NetworkKey, $"{protocolNetwork:d}");
+ Data.Add(ProtocolSettingNames.AddressVersionKey, "53");
+ Data.Add(ProtocolSettingNames.MillisecondsPerBlockKey, "1000");
+ Data.Add(ProtocolSettingNames.MaxTransactionsPerBlockKey, "512");
+ Data.Add(ProtocolSettingNames.MemoryPoolMaxTransactionsKey, "50000");
+ Data.Add(ProtocolSettingNames.MaxTraceableBlocksKey, "2102400");
+ Data.Add(ProtocolSettingNames.InitialGasDistributionKey, "5200000000000000");
+
+ // ==================================
+ // * End of Initial Defaults *
+ // ==================================
+
+ // Application Overrides Above Defaults
+
+ if (_source.InitialData is not null)
+ {
+ foreach (var pair in _source.InitialData)
+ Data.Add(pair.Key, pair.Value);
+ }
+
+ // Environment Overrides Above Defaults
+ Load(Environment.GetEnvironmentVariables(EnvironmentVariableTarget.User));
+ Load(Environment.GetEnvironmentVariables(EnvironmentVariableTarget.Process));
+ }
+
+ private void Load(IDictionary envVariables)
+ {
+ var iter = envVariables.GetEnumerator();
+
+ try
+ {
+ while (iter.MoveNext())
+ {
+ var key = (string)iter.Entry.Key;
+ var value = (string?)iter.Entry.Value;
+
+ AddIfNormalizedKeyMatchesPrefix(Data, Normalize(key), value);
+ }
+ }
+ finally
+ {
+ (iter as IDisposable)?.Dispose();
+ }
+ }
+
+ private static void AddIfNormalizedKeyMatchesPrefix(IDictionary data, string normalizedKey, string? value)
+ {
+ var normalizedPrefix = PREFIX + ':';
+
+ if (normalizedKey.StartsWith(normalizedPrefix, StringComparison.OrdinalIgnoreCase))
+ data[normalizedKey[normalizedPrefix.Length..]] = value;
+ }
+
+ private static string Normalize(string key) =>
+ key.Replace("_", ConfigurationPath.KeyDelimiter);
+ }
+}
diff --git a/src/Neo.VM/Builders/VirtualMachineBuilder.cs b/src/Neo.VM/Builder/VirtualMachineBuilder.cs
similarity index 99%
rename from src/Neo.VM/Builders/VirtualMachineBuilder.cs
rename to src/Neo.VM/Builder/VirtualMachineBuilder.cs
index 51d8c84..1082dab 100644
--- a/src/Neo.VM/Builders/VirtualMachineBuilder.cs
+++ b/src/Neo.VM/Builder/VirtualMachineBuilder.cs
@@ -26,7 +26,7 @@
using Neo.VM.Pipeline;
using System;
-namespace Neo.VM.Builders
+namespace Neo.VM.Builder
{
public sealed class VirtualMachineBuilder
{
diff --git a/src/Neo.VM/Builders/VirtualMachinePipelineBuilder.cs b/src/Neo.VM/Builder/VirtualMachinePipelineBuilder.cs
similarity index 99%
rename from src/Neo.VM/Builders/VirtualMachinePipelineBuilder.cs
rename to src/Neo.VM/Builder/VirtualMachinePipelineBuilder.cs
index 78b1898..d4cabec 100644
--- a/src/Neo.VM/Builders/VirtualMachinePipelineBuilder.cs
+++ b/src/Neo.VM/Builder/VirtualMachinePipelineBuilder.cs
@@ -26,7 +26,7 @@
using System;
using System.Collections.Generic;
-namespace Neo.VM.Builders
+namespace Neo.VM.Builder
{
///
/// Fluent builder for constructing a .
diff --git a/src/Neo.VM/Extensions/ServiceCollectionExtensions.cs b/src/Neo.VM/Extensions/ServiceCollectionExtensions.cs
index 42edcd6..68616b3 100644
--- a/src/Neo.VM/Extensions/ServiceCollectionExtensions.cs
+++ b/src/Neo.VM/Extensions/ServiceCollectionExtensions.cs
@@ -21,7 +21,7 @@
// SERVICES
using Microsoft.Extensions.DependencyInjection;
-using Neo.VM.Builders;
+using Neo.VM.Builder;
using Neo.VM.Middleware;
using System;
@@ -34,10 +34,10 @@ public static IServiceCollection AddExecutionEngine(this IServiceCollection serv
// Register VirtualMachineEngine using the built pipeline
services.AddScoped(sp =>
{
- var middleware = sp.GetServices(); // NOTE: Get all custom middleware
+ var middleware = sp.GetServices(); // NOTE: Get all custom middleware
var debugger = sp.GetRequiredService(); // NOTE: get the debugger middleware
var pipeline = VirtualMachinePipelineBuilder.Create()
- .Use([debugger, .. middleware]) // NOTE: ORDER MATTERS HERE
+ .Use([debugger, .. middleware]) // NOTE: ORDER MATTERS HERE
.Build();
return VirtualMachineBuilder.Create()
diff --git a/src/Neo.Wallet/Json/DevWalletAccountModel.cs b/src/Neo.Wallet/Json/DevWalletAccountModel.cs
index 0cb0e19..786b2b8 100644
--- a/src/Neo.Wallet/Json/DevWalletAccountModel.cs
+++ b/src/Neo.Wallet/Json/DevWalletAccountModel.cs
@@ -20,7 +20,7 @@
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES
-using Neo.Configuration.Json;
+using Neo.Configuration;
using Neo.Configuration.Json.Converters;
using Neo.Core;
using Neo.Core.Interfaces;
@@ -30,7 +30,7 @@
namespace Neo.Wallet.Json
{
- public class DevWalletAccountModel : WalletAccountModel, IMap
+ public class DevWalletAccountModel : WalletAccountModel, IMap
{
[JsonConverter(typeof(JsonStringHexFormatConverter))]
public override byte[]? Key { get => base.Key; set => base.Key = value; }
diff --git a/src/Neo.Wallet/Json/Nep6WalletAccountModel.cs b/src/Neo.Wallet/Json/Nep6WalletAccountModel.cs
index e05f6b5..3486a18 100644
--- a/src/Neo.Wallet/Json/Nep6WalletAccountModel.cs
+++ b/src/Neo.Wallet/Json/Nep6WalletAccountModel.cs
@@ -20,14 +20,14 @@
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES
-using Neo.Configuration.Json;
+using Neo.Configuration;
using Neo.Configuration.Json.Converters;
using Neo.Core.Interfaces;
using System.Text.Json.Serialization;
namespace Neo.Wallet.Json
{
- public class Nep6WalletAccountModel : WalletAccountModel, IMap
+ public class Nep6WalletAccountModel : WalletAccountModel, IMap
{
[JsonConverter(typeof(JsonStringByteArrayConverter))]
public override byte[]? Key { get => base.Key; set => base.Key = value; }
diff --git a/tests/Neo.Configuration.Tests/TestDefaults.cs b/tests/Neo.Configuration.Tests/TestDefaults.cs
index 6e40bb0..0721636 100644
--- a/tests/Neo.Configuration.Tests/TestDefaults.cs
+++ b/tests/Neo.Configuration.Tests/TestDefaults.cs
@@ -48,6 +48,7 @@ internal static class TestDefaults
new JsonStringEnumConverter(),
new JsonStringECPointConverter(),
new JsonStringUInt160Converter(),
+ new JsonIPEndPointConverter(),
}
};
}
diff --git a/tests/Neo.Configuration.Tests/Json/UT_ProtocolSettingsModel.cs b/tests/Neo.Configuration.Tests/UT_ProtocolSettingsOptions.cs
similarity index 78%
rename from tests/Neo.Configuration.Tests/Json/UT_ProtocolSettingsModel.cs
rename to tests/Neo.Configuration.Tests/UT_ProtocolSettingsOptions.cs
index 2758e3f..c7e8166 100644
--- a/tests/Neo.Configuration.Tests/Json/UT_ProtocolSettingsModel.cs
+++ b/tests/Neo.Configuration.Tests/UT_ProtocolSettingsOptions.cs
@@ -23,17 +23,18 @@
using Neo.Configuration.Json;
using Neo.Core.Cryptography.ECC;
using Neo.Core.VM;
+using System.Net;
-namespace Neo.Configuration.Tests.Json
+namespace Neo.Configuration.Tests
{
[TestClass]
- public class UT_ProtocolSettingsModel
+ public class UT_ProtocolSettingsOptions
{
[TestMethod]
public void TestPropertyValues()
{
- var jsonTestString = "{\"network\":810960196,\"addressVersion\":53,\"millisecondsPerBlock\":1000,\"maxTransactionsPerBlock\":512,\"memoryPoolMaxTransactions\":50000,\"maxTraceableBlocks\":2102400,\"hardforks\":{\"Aspidochelone\":666},\"initialGasDistribution\":5200000000000000,\"validatorsCount\":1,\"standbyCommittee\":[\"03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c\"],\"seedList\":[\"seed1.neo.org:10333\"]}";
- var actualProtocolOptionsModel = JsonModel.FromJson(jsonTestString, TestDefaults.JsonDefaultSerializerOptions);
+ var jsonTestString = "{\"network\":810960196,\"addressVersion\":53,\"millisecondsPerBlock\":1000,\"maxTransactionsPerBlock\":512,\"memoryPoolMaxTransactions\":50000,\"maxTraceableBlocks\":2102400,\"hardforks\":{\"Aspidochelone\":666},\"initialGasDistribution\":5200000000000000,\"validatorsCount\":1,\"standbyCommittee\":[\"03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c\"],\"seedList\":[\"127.0.0.1:10333\"]}";
+ var actualProtocolOptionsModel = JsonModel.FromJson(jsonTestString, TestDefaults.JsonDefaultSerializerOptions);
Assert.IsNotNull(actualProtocolOptionsModel);
@@ -50,17 +51,20 @@ public void TestPropertyValues()
Assert.AreEqual(666u, actualProtocolOptionsModel.HardForks[HardFork.Aspidochelone]);
Assert.IsNotNull(actualProtocolOptionsModel.StandbyCommittee);
+ Assert.HasCount(1, actualProtocolOptionsModel.StandbyCommittee);
Assert.AreEqual(ECPoint.Parse("03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c", ECCurve.SecP256r1), actualProtocolOptionsModel.StandbyCommittee[0]);
Assert.IsNotNull(actualProtocolOptionsModel.SeedList);
- Assert.AreEqual("seed1.neo.org:10333", actualProtocolOptionsModel.SeedList[0]);
+ Assert.HasCount(1, actualProtocolOptionsModel.SeedList);
+ Assert.AreEqual(IPAddress.Parse("127.0.0.1"), actualProtocolOptionsModel.SeedList[0].Address);
+ Assert.AreEqual(10333, actualProtocolOptionsModel.SeedList[0].Port);
}
[TestMethod]
public void TestObjectToProtocolSettings()
{
- var jsonTestString = "{\"network\":810960196,\"addressVersion\":53,\"millisecondsPerBlock\":1000,\"maxTransactionsPerBlock\":512,\"memoryPoolMaxTransactions\":50000,\"maxTraceableBlocks\":2102400,\"hardforks\":{\"Aspidochelone\":666},\"initialGasDistribution\":5200000000000000,\"validatorsCount\":1,\"standbyCommittee\":[\"03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c\"],\"seedList\":[\"seed1.neo.org:10333\"]}";
- var actualProtocolOptionsModel = JsonModel.FromJson(jsonTestString, TestDefaults.JsonDefaultSerializerOptions);
+ var jsonTestString = "{\"network\":810960196,\"addressVersion\":53,\"millisecondsPerBlock\":1000,\"maxTransactionsPerBlock\":512,\"memoryPoolMaxTransactions\":50000,\"maxTraceableBlocks\":2102400,\"hardforks\":{\"Aspidochelone\":666},\"initialGasDistribution\":5200000000000000,\"validatorsCount\":1,\"standbyCommittee\":[\"03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c\"],\"seedList\":[\"127.0.0.1:10333\"]}";
+ var actualProtocolOptionsModel = JsonModel.FromJson(jsonTestString, TestDefaults.JsonDefaultSerializerOptions);
Assert.IsNotNull(actualProtocolOptionsModel);
@@ -78,11 +82,14 @@ public void TestObjectToProtocolSettings()
Assert.IsNotNull(actualProtocolSettings.HardForks);
Assert.AreEqual(666u, actualProtocolSettings.HardForks[HardFork.Aspidochelone]);
- Assert.IsNotNull(actualProtocolSettings.StandbyCommittee);
- Assert.AreEqual(ECPoint.Parse("03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c", ECCurve.SecP256r1), actualProtocolSettings.StandbyCommittee[0]);
+ Assert.IsNotNull(actualProtocolOptionsModel.StandbyCommittee);
+ Assert.HasCount(1, actualProtocolOptionsModel.StandbyCommittee);
+ Assert.AreEqual(ECPoint.Parse("03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c", ECCurve.SecP256r1), actualProtocolOptionsModel.StandbyCommittee[0]);
- Assert.IsNotNull(actualProtocolSettings.SeedList);
- Assert.AreEqual("seed1.neo.org:10333", actualProtocolSettings.SeedList[0]);
+ Assert.IsNotNull(actualProtocolOptionsModel.SeedList);
+ Assert.HasCount(1, actualProtocolOptionsModel.SeedList);
+ Assert.AreEqual(IPAddress.Parse("127.0.0.1"), actualProtocolOptionsModel.SeedList[0].Address);
+ Assert.AreEqual(10333, actualProtocolOptionsModel.SeedList[0].Port);
}
}
}
diff --git a/tests/Neo.VM.Tests/Neo.VM.Tests.csproj b/tests/Neo.VM.Tests/Neo.VM.Tests.csproj
index 580c0eb..62edadf 100644
--- a/tests/Neo.VM.Tests/Neo.VM.Tests.csproj
+++ b/tests/Neo.VM.Tests/Neo.VM.Tests.csproj
@@ -19,6 +19,7 @@
+
diff --git a/tests/Neo.VM.Tests/TestUtilities.cs b/tests/Neo.VM.Tests/TestUtilities.cs
index 3504506..98b313d 100644
--- a/tests/Neo.VM.Tests/TestUtilities.cs
+++ b/tests/Neo.VM.Tests/TestUtilities.cs
@@ -20,9 +20,10 @@
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES
+using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
-using Neo.Core.Logging;
+using Neo.Platform.Hosting.Logging;
using Neo.VM.Extensions;
using System;
@@ -35,6 +36,8 @@ internal class TestUtilities
public static readonly ILoggerFactory TraceLoggerFactory = LoggerFactory.Create(logging =>
{
+ var manger = new ConfigurationManager();
+ logging.AddConfiguration(manger);
logging.ClearProviders();
logging.AddNeoPlatform();
logging.SetMinimumLevel(LogLevel.Trace);
@@ -52,6 +55,8 @@ static TestUtilities()
.AddLogging(
logging =>
{
+ var manger = new ConfigurationManager();
+ logging.AddConfiguration(manger);
logging.ClearProviders();
logging.AddNeoPlatform(); // Custom logger for our platform (file, debugger, console, etc)
logging.SetMinimumLevel(LogLevel.Trace);
diff --git a/tests/Neo.Wallet.Tests/TestDefaults.cs b/tests/Neo.Wallet.Tests/TestDefaults.cs
index 7c7f7f5..3871683 100644
--- a/tests/Neo.Wallet.Tests/TestDefaults.cs
+++ b/tests/Neo.Wallet.Tests/TestDefaults.cs
@@ -53,6 +53,7 @@ internal static class TestDefaults
new JsonStringEnumConverter(),
new JsonStringECPointConverter(),
new JsonStringUInt160Converter(),
+ new JsonIPEndPointConverter(),
}
};