Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
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
38 changes: 38 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,44 @@ dotnet test

### Creating a New Smart Contract

The `nccs` CLI can scaffold a ready-to-build project (and optional tests) in one step. If you have installed the tool globally you can run `nccs`; otherwise prefix commands with `dotnet run --project src/Neo.Compiler.CSharp/Neo.Compiler.CSharp.csproj --`:

```bash
dotnet run --project src/Neo.Compiler.CSharp/Neo.Compiler.CSharp.csproj -- templates
dotnet run --project src/Neo.Compiler.CSharp/Neo.Compiler.CSharp.csproj -- new HelloWorld --template Basic --with-tests
cd HelloWorld
dotnet tool restore
dotnet build && dotnet tool run nccs HelloWorld.csproj
cd ../HelloWorld.UnitTests
dotnet test
```

Prefer a one-liner? Use the helper script:

```bash
./scripts/new_contract_with_tests.sh HelloWorld Basic
```
The second argument is optional (defaults to `Basic`). Available templates: `Basic`, `NEP17`, `NEP11`, `Ownable`, `Oracle`.

To compose features, pass them after the template (or instead of it) and the helper will forward them as `--features`, for example:

```bash
./scripts/new_contract_with_tests.sh ComboToken Basic NEP17 Ownable Oracle
```

Looking for more than the starter contracts? The advanced templates ship with opinionated scaffolds:
- Combine feature flags (for example `--features NEP17 Ownable Oracle`) to get an oracle-enabled token without memorising a dedicated template name.
- Need owner rotation on NFTs? `--features NEP11 Ownable` scaffolds an NEP-11 contract with owner management built in.

You can also compose features without memorising special template names. Mix and match with `--features`:

```bash
dotnet run --project src/Neo.Compiler.CSharp/Neo.Compiler.CSharp.csproj -- \
new ComboToken --template Basic --features NEP17 Ownable Oracle
```

Prefer to wire things up manually? You can still:

1. Create a new class library project targeting .NET 9.0 or later
2. Add a reference to the Neo.SmartContract.Framework package
3. Create a class that inherits from `SmartContract`
Expand Down
15 changes: 15 additions & 0 deletions eng/NeoDevPack.Version.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<Project>
<PropertyGroup>
<NeoVersionPrefix>3.8.1</NeoVersionPrefix>
<NeoVersionSuffix>*</NeoVersionSuffix>
</PropertyGroup>

<PropertyGroup>
<NeoPackageVersion Condition="'$(NeoVersionSuffix)' == ''">$(NeoVersionPrefix)</NeoPackageVersion>
<NeoPackageVersion Condition="'$(NeoVersionSuffix)' != ''">$(NeoVersionPrefix)-$(NeoVersionSuffix)</NeoPackageVersion>
</PropertyGroup>

<PropertyGroup>
<NeoAnalyzerVersion>$(NeoPackageVersion)</NeoAnalyzerVersion>
</PropertyGroup>
</Project>
56 changes: 23 additions & 33 deletions examples/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

<Import Project="..\eng\NeoDevPack.Version.props" />

<PropertyGroup Condition="'$(IsTestProject)' != 'true'">
<Copyright>2024 The Neo SmartContract Examples Project</Copyright>
<VersionPrefix>3.8.1</VersionPrefix>
<VersionPrefix>$(NeoVersionPrefix)</VersionPrefix>
<TargetFramework>net9.0</TargetFramework>
<Authors>The Neo SmartContract Examples Project</Authors>
<PackageIcon>neo.png</PackageIcon>
Expand All @@ -19,51 +21,40 @@
<Nullable>enable</Nullable>
<ImplicitUsings>disable</ImplicitUsings>
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>

<!-- Force library project style instead of DotnetToolReference -->
<OutputType>Library</OutputType>
<RestoreProjectStyle>PackageReference</RestoreProjectStyle>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<ImplicitUsings>disable</ImplicitUsings>
<GenerateGlobalAssemblyInfo>false</GenerateGlobalAssemblyInfo>

<!-- Explicitly override any DotnetToolReference settings -->
<EnableDefaultItems>true</EnableDefaultItems>
<EnableDefaultCompileItems>true</EnableDefaultCompileItems>
<EnableDefaultEmbeddedResourceItems>true</EnableDefaultEmbeddedResourceItems>
<EnableDefaultNoneItems>true</EnableDefaultNoneItems>
<UsingMicrosoftNETSdk>true</UsingMicrosoftNETSdk>

<!--
Analyzer Configuration:
Set UseLocalAnalyzer to 'false' when Neo.SmartContract.Analyzer 3.8.1+ is available on NuGet
Set UseLocalAnalyzer to 'true' to use the local project reference (current default)
-->
<UseLocalAnalyzer>true</UseLocalAnalyzer>
<NeoAnalyzerVersion>3.8.1</NeoAnalyzerVersion>

<!-- Allow contributors to opt-in to the local analyzer -->
<UseLocalAnalyzer Condition="'$(UseLocalAnalyzer)' == ''">false</UseLocalAnalyzer>
<NeoAnalyzerVersion>$(NeoAnalyzerVersion)</NeoAnalyzerVersion>
</PropertyGroup>

<ItemGroup Condition="'$(IsTestProject)' != 'true'">
<PackageReference Include="Neo.SmartContract.Framework" Version="3.8.1"/>
<PackageReference Include="Neo.SmartContract.Testing" Version="3.8.1"/>

<!--
Local Project Reference for Analyzer (Current - when UseLocalAnalyzer=true)
Use this when developing against the local source code
-->
<ProjectReference Include="..\..\src\Neo.SmartContract.Analyzer\Neo.SmartContract.Analyzer.csproj"
<PackageReference Include="Neo.SmartContract.Framework" Version="$(NeoPackageVersion)"/>
<PackageReference Include="Neo.SmartContract.Testing" Version="$(NeoPackageVersion)"/>

<!-- Local Project Reference for Analyzer (opt-in) -->
<ProjectReference Include="..\..\src\Neo.SmartContract.Analyzer\Neo.SmartContract.Analyzer.csproj"
Condition="'$(UseLocalAnalyzer)' == 'true'">
<OutputItemType>Analyzer</OutputItemType>
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
</ProjectReference>

<!--
NuGet Package Reference for Analyzer (Future - when UseLocalAnalyzer=false)
Use this when Neo.SmartContract.Analyzer is available on NuGet
To switch: Set <UseLocalAnalyzer>false</UseLocalAnalyzer> above
-->
<PackageReference Include="Neo.SmartContract.Analyzer"
Version="$(NeoAnalyzerVersion)"

<!-- NuGet Analyzer (default) -->
<PackageReference Include="Neo.SmartContract.Analyzer"
Version="$(NeoAnalyzerVersion)"
Condition="'$(UseLocalAnalyzer)' == 'false'">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
Expand All @@ -88,15 +79,14 @@
<ManifestFilePath>$(OutputPath)$(AssemblyName).manifest.json</ManifestFilePath>
<NccsProject>$(MSBuildThisFileDirectory)..\src\Neo.Compiler.CSharp\Neo.Compiler.CSharp.csproj</NccsProject>
</PropertyGroup>

<!-- Generate artifact using nccs with additional parameters if unit test project exists -->
<Message Text="Checking for unit test project: $(UnitTestProjectDir)" Condition="Exists('$(UnitTestProjectDir)')" />
<MakeDir Directories="$(TestingArtifactsDir)" Condition="Exists('$(UnitTestProjectDir)') AND !Exists('$(TestingArtifactsDir)')" />

<!-- Use nccs to generate artifacts for unit tests -->
<Exec Command="dotnet run --project &quot;$(NccsProject)&quot; -- &quot;$(MSBuildProjectFile)&quot; --base-name &quot;$(ArtifactClassName)&quot; --output &quot;$(TestingArtifactsDir)&quot; --generate-artifacts Source"
ContinueOnError="true"
Condition="Exists('$(UnitTestProjectDir)')" />

<Exec Command="dotnet tool run nccs --base-name &quot;$(ArtifactClassName)&quot; --output &quot;$(TestingArtifactsDir)&quot; --generate-artifacts Source &quot;$(MSBuildProjectFile)&quot;"
ContinueOnError="true"
Condition="Exists('$(UnitTestProjectDir)') AND Exists('$(MSBuildProjectDirectory).config/dotnet-tools.json')" />
</Target>

</Project>
32 changes: 27 additions & 5 deletions guidance/GETTING-STARTED-GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,27 @@ For the best development experience, install:

Let's create a simple "Hello World" smart contract.

### Step 1: Create a New Project
### Step 1 (Recommended): Scaffold with the CLI

```bash
# From the neo-devpack-dotnet repository root
dotnet run --project src/Neo.Compiler.CSharp/Neo.Compiler.CSharp.csproj -- templates
dotnet run --project src/Neo.Compiler.CSharp/Neo.Compiler.CSharp.csproj -- new HelloWorld --template Basic --with-tests

cd HelloWorld
dotnet tool restore
dotnet build
dotnet tool run nccs HelloWorld.csproj

cd ../HelloWorld.UnitTests
dotnet test
```

This creates both the contract and a MSTest project pre-wired with `Neo.SmartContract.Testing`.

If you prefer to build things manually, follow the optional steps below.

### Step 1 (Manual): Create a New Project

```bash
# Create a new directory for your contract
Expand All @@ -120,7 +140,8 @@ Edit `HelloWorldContract.csproj`:
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Neo.SmartContract.Framework" Version="3.8.1" />
<!-- Use the version from eng/NeoDevPack.Version.props -->
<PackageReference Include="Neo.SmartContract.Framework" Version="3.8.1-*" />
</ItemGroup>
</Project>
```
Expand Down Expand Up @@ -206,8 +227,9 @@ From your project directory, compile the contract:
# Build the project first
dotnet build

# Compile to NEO bytecode (from the neo-devpack-dotnet root directory)
dotnet run --project src/Neo.Compiler.CSharp/Neo.Compiler.CSharp.csproj -- HelloWorldContract/HelloWorldContract.csproj
# Compile to NEO bytecode with the local tool manifest
dotnet tool restore
dotnet tool run nccs HelloWorldContract.csproj
```

**Verify compilation:**
Expand Down Expand Up @@ -721,4 +743,4 @@ public class ProxyContract : SmartContract

---

Congratulations! You've completed the Getting Started guide. You now have the foundation to build powerful smart contracts on NEO. Happy coding!
Congratulations! You've completed the Getting Started guide. You now have the foundation to build powerful smart contracts on NEO. Happy coding!
74 changes: 74 additions & 0 deletions scripts/new_contract_with_tests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#!/usr/bin/env bash

# Helper script for scaffolding a smart contract together with its MSTest project.
# Usage:
# ./scripts/new_contract_with_tests.sh MyContract [Template]
# ./scripts/new_contract_with_tests.sh MyContract [Template] [Feature...]
# Template defaults to "Basic". Available templates: Basic, NEP17, NEP11, Ownable, Oracle.
# Features can be provided as additional arguments (e.g. "NEP17 Ownable Oracle") or comma separated values.

set -euo pipefail

if [[ $# -lt 1 ]]; then
echo "Usage: $0 <ContractName> [Template] [Feature ...]" >&2
exit 1
fi

contract_name="$1"
shift

template="Basic"
features=()

if [[ $# -gt 0 ]]; then
template="$1"
Comment on lines +17 to +24
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The arguments should be sanitized because it goes to the command line

# Sanitize: allow only alphanumeric, underscore, and dash
if [[ ! "$contract_name" =~ ^[a-zA-Z0-9_-]+$ ]]; then
  echo "Error: Invalid contract name. Only letters, numbers, '_' and '-' are allowed."
  exit 1
fi

shift

# If the template argument looks like inline features, treat it as such
if [[ "$template" == "--features" ]]; then
template="Basic"
elif [[ "$template" == -* ]]; then
echo "Unexpected option '$template'. Pass features without flags." >&2
exit 1
elif [[ "$template" == *","* || "$template" == *"+"* ]]; then
features+=("$template")
template="Basic"
fi
fi

if [[ $# -gt 0 ]]; then
features+=("$@")
fi

feature_args=()
if [[ ${#features[@]} -gt 0 ]]; then
for feature in "${features[@]}"; do
if [[ -n "$feature" ]]; then
feature_args+=(--features "$feature")
fi
done
fi

script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
repo_root="$(cd "${script_dir}/.." && pwd)"

if [[ ${#feature_args[@]} -gt 0 ]]; then
echo "Scaffolding contract '${contract_name}' with template '${template}' and features '${features[*]}' (tests included)..."
else
echo "Scaffolding contract '${contract_name}' with template '${template}' (tests included)..."
fi
dotnet run --project "${repo_root}/src/Neo.Compiler.CSharp/Neo.Compiler.CSharp.csproj" -- \
new "${contract_name}" --template "${template}" "${feature_args[@]}" --with-tests

cat <<EOF

Next steps (from the scaffold output):
cd ${contract_name}
dotnet tool restore
dotnet build
dotnet tool run nccs ${contract_name}.csproj

cd ../${contract_name}.UnitTests
dotnet test

EOF
4 changes: 3 additions & 1 deletion src/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">

<Import Project="..\eng\NeoDevPack.Version.props" />

<PropertyGroup>
<Copyright>2015-2025 The Neo Project</Copyright>
<Company>The Neo Project</Company>
<VersionPrefix>3.8.1</VersionPrefix>
<VersionPrefix>$(NeoVersionPrefix)</VersionPrefix>
<Authors>The Neo Project</Authors>
<PackageProjectUrl>https://github.com/neo-project/neo-devpack-dotnet</PackageProjectUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
Expand Down
30 changes: 28 additions & 2 deletions src/Neo.Compiler.CSharp/Neo.Compiler.CSharp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
<Description>Neo.Compiler.CSharp</Description>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<AssemblyInformationalVersion>$(NeoPackageVersion)</AssemblyInformationalVersion>
</PropertyGroup>

<ItemGroup>
Expand All @@ -24,13 +25,38 @@
<PackageReference Include="System.CommandLine.NamingConventionBinder" Version="2.0.0-beta4.22272.1" />
</ItemGroup>

<PropertyGroup>
<NeoSourceRoot>..\..\neo\src</NeoSourceRoot>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\neo\src\Neo.Extensions\Neo.Extensions.csproj" />
<ProjectReference Include="..\..\neo\src\Neo\Neo.csproj" />
<ProjectReference Include="..\Neo.SmartContract.Framework\Neo.SmartContract.Framework.csproj">
<Aliases>scfx</Aliases>
</ProjectReference>
</ItemGroup>

<ItemGroup Condition="Exists('$(MSBuildThisFileDirectory)..\Neo.SmartContract.Testing\Neo.SmartContract.Testing.csproj') and Exists('$(MSBuildThisFileDirectory)$(NeoSourceRoot)\Neo\Neo.csproj')">
<ProjectReference Include="..\Neo.SmartContract.Testing\Neo.SmartContract.Testing.csproj" />
</ItemGroup>

<ItemGroup Condition="!Exists('$(MSBuildThisFileDirectory)..\Neo.SmartContract.Testing\Neo.SmartContract.Testing.csproj') or !Exists('$(MSBuildThisFileDirectory)$(NeoSourceRoot)\Neo\Neo.csproj')">
<PackageReference Include="Neo.SmartContract.Testing" Version="$(NeoPackageVersion)" />
</ItemGroup>

<ItemGroup Condition="Exists('$(MSBuildThisFileDirectory)$(NeoSourceRoot)\Neo\Neo.csproj')">
<ProjectReference Include="..\..\neo\src\Neo.Extensions\Neo.Extensions.csproj" />
<ProjectReference Include="..\..\neo\src\Neo\Neo.csproj" />
<ProjectReference Include="..\..\neo\src\Neo.IO\Neo.IO.csproj" />
<ProjectReference Include="..\..\neo\src\Neo.Json\Neo.Json.csproj" />
<ProjectReference Include="..\..\neo\src\Neo.VM\Neo.VM.csproj" />
</ItemGroup>

<ItemGroup Condition="!Exists('$(MSBuildThisFileDirectory)$(NeoSourceRoot)\Neo\Neo.csproj')">
<PackageReference Include="Neo" Version="$(NeoPackageVersion)" />
<PackageReference Include="Neo.Extensions" Version="$(NeoPackageVersion)" />
<PackageReference Include="Neo.IO" Version="$(NeoPackageVersion)" />
<PackageReference Include="Neo.Json" Version="$(NeoPackageVersion)" />
<PackageReference Include="Neo.VM" Version="$(NeoPackageVersion)" />
</ItemGroup>

</Project>
Loading