Skip to content
Open
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
32 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
24239c5
Merge remote-tracking branch 'origin/dev' into pr1-core-deployment-fr…
Jim8y Oct 24, 2025
0ae7817
Refine deployment toolkit RPC handling
Jim8y Oct 24, 2025
922ae56
Merge branch 'dev' into pr1-core-deployment-framework
Jim8y Oct 25, 2025
fe30520
Merge branch 'dev' into pr1-core-deployment-framework
shargon Oct 28, 2025
de11b80
Merge branch 'dev' into pr1-core-deployment-framework
Jim8y Nov 5, 2025
2440213
Throttle network magic retries and expand coverage
Jim8y Nov 6, 2025
31783e0
Extend deployment request/options APIs and docs
Jim8y Nov 6, 2025
db44f0a
Merge branch 'dev' into pr1-core-deployment-framework
Jim8y Nov 8, 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
118 changes: 118 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:

- **Simple API**: Easy-to-use methods for deploying contracts from source code or artifacts
- **Network Support**: Support for mainnet, testnet, and private network deployment
- **WIF Key Integration**: Direct signing with WIF (Wallet Import Format) keys
- **Contract Interaction**: Call and invoke contract methods after deployment
- **Balance Checking**: Monitor GAS balances for deployment accounts
- **Manifest Deployment**: Deploy multiple contracts from deployment manifests

## Getting Started

### Prerequisites
Expand Down Expand Up @@ -309,6 +320,113 @@ 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 are not included.

The `Neo.SmartContract.Deploy` package provides a streamlined way to deploy contracts to the NEO blockchain. It supports deployment from source code, compiled artifacts, and deployment manifests.

### 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 source code
var result = await deployment.DeployAsync("MyContract.cs");

if (result.Success)
{
Console.WriteLine($"Contract deployed: {result.ContractHash}");
Console.WriteLine($"Transaction: {result.TransactionHash}");
}
else
{
Console.WriteLine($"Deployment failed: {result.ErrorMessage}");
}
```

### Artifact Deployment

```csharp
// Deploy with initialization parameters
var initParams = new object[] { "param1", 42, true };
var result = await deployment.DeployAsync("MyContract.cs", initParams);

// Deploy from compiled artifacts (.nef + manifest)
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.

// Deploy multiple contracts from manifest
var manifestResult = await deployment.DeployFromManifestAsync("deployment-manifest.json");
```

### 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");
```

### 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.IsNullOrEmpty(wif) || string.IsNullOrEmpty(nef) || string.IsNullOrEmpty(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.IsNullOrEmpty(contract) || string.IsNullOrEmpty(method))
{
Console.Error.WriteLine("Missing required parameters for call.\n");
PrintUsage();
return;
}

object[] callArgs = Array.Empty<object>();
if (!string.IsNullOrEmpty(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;
}

2 changes: 1 addition & 1 deletion examples/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
OutputItemType="Analyzer" ReferenceOutputAssembly="false"/>
</ItemGroup>

<Target Name="ExecuteBeforeBuild" BeforeTargets="PreBuildEvent">
<Target Name="ExecuteBeforeBuild" BeforeTargets="PreBuildEvent" Condition="'$(DisableExamplesPreBuild)' != 'true'">
<MSBuild Projects="..\..\src\Neo.Compiler.CSharp\Neo.Compiler.CSharp.csproj" Targets="Build" ContinueOnError="true"/>
<Exec Command="dotnet ..\..\src\Neo.Compiler.CSharp\bin\Debug\net9.0\nccs.dll &quot;$(MSBuildProjectFile)&quot;" ContinueOnError="true"/>
</Target>
Expand Down
2 changes: 1 addition & 1 deletion neo
Submodule neo updated 699 files
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
13 changes: 12 additions & 1 deletion src/Neo.Compiler.CSharp/ABI/AbiMethod.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,19 @@ public AbiMethod(IMethodSymbol symbol)
: base(symbol, symbol.GetDisplayName(true), symbol.Parameters.Select(p => p.ToAbiParameter()).ToArray())
{
Symbol = symbol;
Safe = symbol.GetAttributes().Any(p => p.AttributeClass!.Name == nameof(scfx::Neo.SmartContract.Framework.Attributes.SafeAttribute));
Safe = GetSafeAttribute(symbol) != null;
if (Safe && symbol.MethodKind == MethodKind.PropertySet)
throw new CompilationException(symbol, DiagnosticId.SafeSetter, "Safe setters are not allowed.");
ReturnType = symbol.ReturnType.GetContractParameterType();
}

private static AttributeData? GetSafeAttribute(IMethodSymbol symbol)
{
AttributeData? attribute = symbol.GetAttributes().FirstOrDefault(p => p.AttributeClass!.Name == nameof(scfx::Neo.SmartContract.Framework.Attributes.SafeAttribute));
if (attribute != null) return attribute;
if (symbol.AssociatedSymbol is IPropertySymbol property)
return property.GetAttributes().FirstOrDefault(p => p.AttributeClass!.Name == nameof(scfx::Neo.SmartContract.Framework.Attributes.SafeAttribute));
return null;
}
}
}
1 change: 1 addition & 0 deletions src/Neo.Compiler.CSharp/Diagnostic/DiagnosticId.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,6 @@ static class DiagnosticId
public const string CapturedStaticFieldNotFound = "NC3007";
public const string InvalidType = "NC3008";
public const string InvalidArgument = "NC3009";
public const string SafeSetter = "NC3010";
}
}
Loading
Loading