Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
6375fe6
SafeAttribute supports in properties (#1330)
erikzhang Jun 16, 2025
bd9f1e1
feat: add core Neo smart contract deployment framework
Jim8y Jul 9, 2025
e40f22b
fix: resolve code formatting issues and remove duplicate package refe…
Jim8y Jul 9, 2025
ef94799
chore: update neo submodule to latest commit
Jim8y Jul 9, 2025
bcae4a1
fix: address PR review comments
Jim8y Jul 9, 2025
9e3ef8d
fix: resolve code formatting issues
Jim8y Jul 9, 2025
f9bdb8e
test: add comprehensive unit tests for network magic retrieval
Jim8y Jul 9, 2025
b250af7
chore: update testnet RPC URL to Neo NGD endpoint
Jim8y Jul 9, 2025
7ae2d02
fix: correct NGD testnet RPC URL
Jim8y Jul 9, 2025
877d6ef
chore: update testnet RPC URL to Neo seed node
Jim8y Jul 9, 2025
eca436b
docs: add PR #1 test summary
Jim8y Jul 9, 2025
992f18e
fix: resolve code formatting issues in test files
Jim8y Jul 9, 2025
2f2f1ab
Update src/Neo.SmartContract.Deploy/Shared/ScriptBuilderHelper.cs
Jim8y Jul 17, 2025
de026c3
Update src/Neo.SmartContract.Deploy/Shared/ScriptBuilderHelper.cs
Jim8y Jul 17, 2025
12def91
Update src/Neo.SmartContract.Deploy/Shared/ScriptBuilderHelper.cs
Jim8y Jul 17, 2025
630a3a3
Update src/Neo.SmartContract.Deploy/Shared/ScriptBuilderHelper.cs
Jim8y Jul 17, 2025
ce8ccdd
Update src/Neo.SmartContract.Deploy/Shared/ScriptBuilderHelper.cs
Jim8y Jul 17, 2025
cb776b7
Update src/Neo.SmartContract.Deploy/Shared/ScriptBuilderHelper.cs
Jim8y Jul 17, 2025
da96daf
Update tests/Neo.SmartContract.Deploy.UnitTests/RpcIntegrationTests.cs
Jim8y Jul 17, 2025
92cbba3
feat(deploy): implement minimal artifact deployment + calls; trim sca…
Jim8y Sep 10, 2025
3e74910
Merge dev: update neo submodule to 8afce406; resolve conflicts in exa…
Jim8y Sep 22, 2025
18daa75
feat(deploy): add configurable toolkit with compilation and manifest …
Jim8y Sep 23, 2025
1dfe05d
chore: switch mainnet rpc default to coz endpoint
Jim8y Sep 23, 2025
8d46f2e
Merge branch 'dev' into pr1-core-deployment-framework
ajara87 Sep 26, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
176 changes: 176 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,17 @@ Code analyzers and linting tools to help write secure and efficient contracts.

Project templates for creating new NEO smart contracts with the proper structure and configurations.

### Neo.SmartContract.Deploy

A streamlined deployment toolkit that provides a simplified API for Neo smart contract deployment. Features include:

- **Artifact Deployment**: Deploy precompiled `.nef` and manifest artifacts with a single call
- **Network Support**: Target mainnet, testnet, private nets, or custom RPC endpoints
- **WIF Key Integration**: Sign deployment and invocation transactions directly with WIF keys
- **Contract Interaction**: Perform read-only calls and on-chain invocations against deployed contracts
- **Balance Checking**: Query GAS balances for deployment accounts
- **Configurable Runtime**: Tune confirmation behaviour and network profiles through `DeploymentOptions`

## Getting Started

### Prerequisites
Expand Down Expand Up @@ -309,6 +320,171 @@ The repository includes various example contracts that demonstrate different fea

Each example comes with corresponding unit tests that demonstrate how to properly test the contract functionality.

## Contract Deployment

This PR implements deployment from compiled artifacts (`.nef` + manifest) and basic RPC interactions (call, invoke, GAS balance, contract existence). Deploying from source projects (compilation) and multi-contract manifest deployment will arrive in future iterations.

The `Neo.SmartContract.Deploy` package provides a streamlined way to deploy contracts to the NEO blockchain using compiled artifacts.

### Installation

```shell
dotnet add package Neo.SmartContract.Deploy
```

### Basic Usage

```csharp
using Neo.SmartContract.Deploy;

// Create deployment toolkit
var deployment = new DeploymentToolkit()
.SetNetwork("testnet")
.SetWifKey("your-wif-key-here");

// Deploy from compiled artifacts
var result = await deployment.DeployArtifactsAsync(
"MyContract.nef",
"MyContract.manifest.json",
waitForConfirmation: true);

Console.WriteLine($"Contract deployed: {result.ContractHash}");
Console.WriteLine($"Transaction: {result.TransactionHash}");
```

### Artifact Deployment

```csharp
// Deploy with initialization parameters
var initParams = new object[] { "param1", 42, true };
var artifactsResult = await deployment.DeployArtifactsAsync(
"MyContract.nef",
"MyContract.manifest.json",
initParams,
waitForConfirmation: true);

Console.WriteLine($"Tx: {artifactsResult.TransactionHash}");
Console.WriteLine($"Expected Contract Hash: {artifactsResult.ContractHash}");

Example app: See `examples/DeploymentArtifactsDemo` for a minimal console that deploys from NEF + manifest and performs read-only calls.
```

```csharp
var request = new DeploymentArtifactsRequest("MyContract.nef", "MyContract.manifest.json")
.WithInitParams("owner", 100)
.WithConfirmationPolicy(waitForConfirmation: true, retries: 20, delaySeconds: 2);

await deployment.DeployArtifactsAsync(request, cancellationToken);
```

### Source Deployment

```csharp
var compileAndDeploy = await deployment.DeployAsync(
"contracts/MyContract/MyContract.csproj",
initParams: new object?[] { "owner", 1000 });

Console.WriteLine($"Contract hash: {compileAndDeploy.ContractHash}");
```

### Manifest Deployment

You can orchestrate multiple deployments via a JSON manifest:

```json
{
"network": "mainnet",
"wif": "Kx...",
"waitForConfirmation": true,
"confirmationRetries": 40,
"confirmationDelaySeconds": 2,
"contracts": [
{
"name": "Token",
"nef": "artifacts/Token.nef",
"manifest": "artifacts/Token.manifest.json",
"initParams": ["admin", 1_000_000]
},
{
"name": "Treasury",
"nef": "artifacts/Treasury.nef",
"manifest": "artifacts/Treasury.manifest.json",
"waitForConfirmation": false
}
]
}
```

```csharp
var deployments = await deployment.DeployFromManifestAsync("deployment.json");
foreach (var (name, info) in deployments)
{
Console.WriteLine($"{name}: {info.ContractHash} ({info.TransactionHash})");
}
```

### Network Configuration

```csharp
// Use predefined networks
deployment.SetNetwork("mainnet");
deployment.SetNetwork("testnet");
deployment.SetNetwork("local");

// Or use custom RPC URL
deployment.SetNetwork("https://my-custom-rpc.com:10332");

// Alternatively configure at construction time
var options = new DeploymentOptions { Network = NetworkProfile.TestNet };
var toolkit = new DeploymentToolkit(options: options);

// The same API exposes cancellation and confirmation tuning
using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(2));
var requestResult = await toolkit.DeployArtifactsAsync(
"MyContract.nef",
"MyContract.manifest.json",
waitForConfirmation: true,
confirmationRetries: 60,
confirmationDelaySeconds: 2,
cancellationToken: cts.Token);
```

### Contract Interaction

```csharp
// Call contract method (read-only)
var balance = await deployment.CallAsync<int>("contract-hash", "balanceOf", "account-address");

// Invoke contract method (state-changing)
var txHash = await deployment.InvokeAsync("contract-hash", "transfer", fromAccount, toAccount, amount);

// Check contract existence
var exists = await deployment.ContractExistsAsync("contract-hash");

// Get account balance
var gasBalance = await deployment.GetGasBalanceAsync();
```

### Configuration File Support

Create an `appsettings.json` file for configuration:

```json
{
"Network": {
"RpcUrl": "https://testnet1.neo.coz.io:443",
"Network": "testnet"
},
"Deployment": {
"GasLimit": 100000000,
"WaitForConfirmation": true
},
"Wallet": {
"Path": "wallet.json"
}
}
```

## Documentation

For detailed documentation on NEO smart contract development with .NET:
Expand Down
21 changes: 21 additions & 0 deletions examples/DeploymentArtifactsDemo/DeploymentArtifactsDemo.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<DisableExamplesPreBuild>true</DisableExamplesPreBuild>
</PropertyGroup>

<ItemGroup>
<!-- Remove references inherited from examples/Directory.Build.props that are only for contract projects -->
<ProjectReference Remove="..\..\src\Neo.Compiler.CSharp\Neo.Compiler.CSharp.csproj" />
<ProjectReference Remove="..\..\src\Neo.SmartContract.Analyzer\Neo.SmartContract.Analyzer.csproj" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\Neo.SmartContract.Deploy\Neo.SmartContract.Deploy.csproj" />
</ItemGroup>

</Project>
104 changes: 104 additions & 0 deletions examples/DeploymentArtifactsDemo/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
using Neo.SmartContract.Deploy;
using System.Text.Json;

static void PrintUsage()
{
Console.WriteLine("Usage:");
Console.WriteLine(" dotnet run -- --network <mainnet|testnet|http(s)://rpc> --wif <WIF> --nef <path> --manifest <path> [--wait]");
Console.WriteLine(" dotnet run -- --network <...> --call --contract <hash|address> --method <name> [--args '[\"arg1\",123,true]']");
Console.WriteLine();
Console.WriteLine("Examples:");
Console.WriteLine(" dotnet run -- --network testnet --wif Kx... --nef My.nef --manifest My.manifest.json --wait");
Console.WriteLine(" dotnet run -- --network testnet --call --contract 0x... --method symbol");
}

string? GetArg(string key)
{
for (int i = 0; i < args.Length - 1; i++)
if (string.Equals(args[i], key, StringComparison.OrdinalIgnoreCase))
return args[i + 1];
return null;
}

bool HasFlag(string key) => args.Any(a => string.Equals(a, key, StringComparison.OrdinalIgnoreCase));

if (args.Length == 0 || HasFlag("--help") || HasFlag("-h"))
{
PrintUsage();
return;
}

var network = GetArg("--network") ?? Environment.GetEnvironmentVariable("NEO_RPC_URL") ?? "testnet";
var wif = GetArg("--wif") ?? Environment.GetEnvironmentVariable("NEO_WIF");
var nef = GetArg("--nef");
var manifest = GetArg("--manifest");
var wait = HasFlag("--wait");

var doCall = HasFlag("--call");
var contract = GetArg("--contract");
var method = GetArg("--method");
var argsJson = GetArg("--args");

var toolkit = new DeploymentToolkit().SetNetwork(network);

try
{
if (!doCall)
{
if (string.IsNullOrWhiteSpace(wif) || string.IsNullOrWhiteSpace(nef) || string.IsNullOrWhiteSpace(manifest))
{
Console.Error.WriteLine("Missing required parameters for deployment.\n");
PrintUsage();
return;
}

toolkit.SetWifKey(wif);
var initParams = Array.Empty<object>();
var result = await toolkit.DeployArtifactsAsync(nef, manifest, initParams, waitForConfirmation: wait);
Console.WriteLine($"Transaction Hash: {result.TransactionHash}");
Console.WriteLine($"Expected Contract Hash: {result.ContractHash}");
}
else
{
if (string.IsNullOrWhiteSpace(contract) || string.IsNullOrWhiteSpace(method))
{
Console.Error.WriteLine("Missing required parameters for call.\n");
PrintUsage();
return;
}

object[] callArgs = Array.Empty<object>();
if (!string.IsNullOrWhiteSpace(argsJson))
{
try
{
var doc = JsonDocument.Parse(argsJson);
if (doc.RootElement.ValueKind == JsonValueKind.Array)
{
callArgs = doc.RootElement.EnumerateArray().Select(el => el.ValueKind switch
{
JsonValueKind.String => (object)el.GetString()!,
JsonValueKind.Number => el.TryGetInt64(out var l) ? (object)l : el.GetDouble(),
JsonValueKind.True => true,
JsonValueKind.False => false,
_ => el.ToString()
}).ToArray();
}
}
catch (Exception ex)
{
Console.Error.WriteLine($"Failed to parse --args JSON: {ex.Message}");
return;
}
}

var value = await toolkit.CallAsync<string>(contract, method, callArgs);
Console.WriteLine($"Result: {value}");
}
}
catch (Exception ex)
{
Console.Error.WriteLine($"Error: {ex.Message}");
Environment.ExitCode = 1;
}

14 changes: 14 additions & 0 deletions neo-devpack-dotnet.sln
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neo.SmartContract.Analyzer.
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Neo.Disassembler.CSharp", "src\Neo.Disassembler.CSharp\Neo.Disassembler.CSharp.csproj", "{FA988C67-43CF-4AE4-94FE-023AADFF88D6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neo.SmartContract.Deploy", "src\Neo.SmartContract.Deploy\Neo.SmartContract.Deploy.csproj", "{B8A5D9F3-8C7E-4A1B-9D2F-3F8A9B5C7E1A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neo.SmartContract.Deploy.UnitTests", "tests\Neo.SmartContract.Deploy.UnitTests\Neo.SmartContract.Deploy.UnitTests.csproj", "{A7B6C9D2-5E8F-4C3B-8A1D-2F9A8B5C7E4F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -128,6 +132,14 @@ Global
{FA988C67-43CF-4AE4-94FE-023AADFF88D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FA988C67-43CF-4AE4-94FE-023AADFF88D6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FA988C67-43CF-4AE4-94FE-023AADFF88D6}.Release|Any CPU.Build.0 = Release|Any CPU
{B8A5D9F3-8C7E-4A1B-9D2F-3F8A9B5C7E1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B8A5D9F3-8C7E-4A1B-9D2F-3F8A9B5C7E1A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B8A5D9F3-8C7E-4A1B-9D2F-3F8A9B5C7E1A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B8A5D9F3-8C7E-4A1B-9D2F-3F8A9B5C7E1A}.Release|Any CPU.Build.0 = Release|Any CPU
{A7B6C9D2-5E8F-4C3B-8A1D-2F9A8B5C7E4F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A7B6C9D2-5E8F-4C3B-8A1D-2F9A8B5C7E4F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A7B6C9D2-5E8F-4C3B-8A1D-2F9A8B5C7E4F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A7B6C9D2-5E8F-4C3B-8A1D-2F9A8B5C7E4F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -152,6 +164,8 @@ Global
{C2B7927F-AAA5-432A-8E76-B5080BD7EFB9} = {49D5873D-7B38-48A5-B853-85146F032091}
{F30E2375-012A-4A38-985B-31CB7DBA4D28} = {D5266066-0AFD-44D5-A83E-2F73668A63C8}
{FA988C67-43CF-4AE4-94FE-023AADFF88D6} = {79389FC0-C621-4CEA-AD2B-6074C32E7BCA}
{B8A5D9F3-8C7E-4A1B-9D2F-3F8A9B5C7E1A} = {79389FC0-C621-4CEA-AD2B-6074C32E7BCA}
{A7B6C9D2-5E8F-4C3B-8A1D-2F9A8B5C7E4F} = {D5266066-0AFD-44D5-A83E-2F73668A63C8}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {6DA935E1-C674-4364-B087-F1B511B79215}
Expand Down
40 changes: 40 additions & 0 deletions src/Neo.SmartContract.Deploy/DeploymentArtifactsRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System;

namespace Neo.SmartContract.Deploy;

public sealed record DeploymentArtifactsRequest
{
public DeploymentArtifactsRequest(
string nefPath,
string manifestPath,
object?[]? initializationParameters = null)
{
NefPath = nefPath ?? throw new ArgumentNullException(nameof(nefPath));
ManifestPath = manifestPath ?? throw new ArgumentNullException(nameof(manifestPath));
InitParams = initializationParameters ?? Array.Empty<object?>();
}

public string NefPath { get; init; }

public string ManifestPath { get; init; }

public object?[] InitParams { get; init; }

public bool? WaitForConfirmation { get; init; }

public int? ConfirmationRetries { get; init; }

public int? ConfirmationDelaySeconds { get; init; }

public DeploymentArtifactsRequest WithInitParams(params object?[] parameters)
=> this with { InitParams = parameters ?? Array.Empty<object?>() };

public DeploymentArtifactsRequest WithConfirmationPolicy(bool? waitForConfirmation, int? retries = null, int? delaySeconds = null)
=> this with
{
WaitForConfirmation = waitForConfirmation,
ConfirmationRetries = retries,
ConfirmationDelaySeconds = delaySeconds
};
}

Loading
Loading