NeoDebug Command Reference
August 31, 2026 · View on GitHub
NeoDebug (neodebug) is a source-level debugger for Neo N3 smart contracts. It is a
Debug Adapter Protocol host: an editor
launches it and speaks DAP over standard in/out, letting you set breakpoints in your C# (or
other supported language) source, step through execution, and inspect arguments, locals,
static fields, the evaluation stack, and contract storage.
NeoDebug debugs a recorded execution trace (a .neo-trace file). Because the execution is
a recording, you can step backward as well as forward — time-travel debugging.
Installation
NeoDebug is distributed as a .NET global tool:
dotnet tool install Neo.Debug -g
dotnet tool update Neo.Debug -g
Confirm the install with:
neodebug --version
Workflow
-
Compile your contract with debug information. The Neo C# compiler (
nccs) emits a<contract>.nef, a<contract>.manifest.json, and a<contract>.nefdbgnfo(a compressed*.debug.json, the NEP-19 source map). Use the extended, unoptimized debug build so every statement has a sequence point. -
Capture a trace. Produce a
.neo-tracefor the invocation you want to debug — for example with NeoTrace against a public transaction, or by starting a Neo-Express node withneoxp run --trace(which writes<txhash>.neo-tracefiles as it executes). -
Configure a launch configuration (see below) that points at the contract and the trace.
-
Debug. Launch from your editor's debug view. Set breakpoints on source lines; use continue, step in/out/over, and — because this is a recorded trace — step back and reverse continue to move backward through the execution.
Example: debug a local contract call
Assume Contract.csproj uses Neo.BuildTasks, produces bin/sc/Contract.nef, and exposes a
parameterless getValue method. From the contract project directory, build and deploy it to a
fresh Neo-Express instance, then invoke the method with tracing enabled:
dotnet build ./Contract.csproj
neoxp create --force --output ./debug.neo-express
neoxp contract deploy --input ./debug.neo-express ./bin/sc/Contract.nef genesis
neoxp contract run --input ./debug.neo-express --trace --account genesis Contract getValue
The final command prints the transaction hash and writes <transaction-hash>.neo-trace in the
current directory. Put that file in traces/, replace the placeholder in the launch configuration
below with its file name, set a breakpoint in the contract source, and start the Debug Neo
contract (trace) configuration from VS Code's Run and Debug view.
Launch configuration
neodebug is a DAP stdio host, so an editor (or any DAP client) spawns it and sends a launch
request whose configuration carries these properties:
| Property | Required | Description |
|---|---|---|
program | yes | Path to the compiled .nef. Its sibling .manifest.json and .nefdbgnfo/.debug.json are loaded automatically. |
invocation | yes | Either { "trace-file": "<path>" } to replay a recorded trace, or { "operation": "<method>", "args": [ ... ] } to deploy and run the contract live. |
signers | no | Non-empty array of unique Neo N3 addresses for a live invocation. Each signer uses CalledByEntry; the first account also determines the deployed contract hash. |
returnTypes | no | Array of cast hints (int, bool, string, hex, byte[], addr) for rendering the method's return values. The legacy return-types spelling is also accepted. |
sourceFileMap | no | Object remapping the document paths baked into the debug info to their location on this machine. |
A VS Code launch.json entry looks like:
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug Neo contract (trace)",
"type": "neo-contract",
"request": "launch",
"program": "${workspaceFolder}/bin/sc/Contract.nef",
"invocation": {
"trace-file": "${workspaceFolder}/traces/0xabc...neo-trace"
},
"returnTypes": [ "int" ]
}
]
}
To deploy and run the contract live instead, give the invocation an operation (and optional
args) rather than a trace-file; the contract is deployed into a fresh, single-block in-process
chain and the debugger stops at the call:
{
"name": "Debug Neo contract (live)",
"type": "neo-contract",
"request": "launch",
"program": "${workspaceFolder}/bin/sc/Contract.nef",
"signers": [ "NXV7ZhHiyM1aHXwpVsRZC6BwNFP2jghXAq" ],
"invocation": {
"operation": "transfer",
"args": [ "@NXV7ZhHiyM1aHXwpVsRZC6BwNFP2jghXAq", 100 ]
},
"returnTypes": [ "bool" ]
}
The repository includes a VS Code extension that registers the
neo-contractdebug type and launchesneodebugfrom yourPATH. Setneo-contract.debugAdapterPathin VS Code when you need to use a specific debugger build. Other DAP clients can also launchneodebugand send the same configuration.
Debug views
NeoDebug presents two views. The --debug-view option selects the initial view; a DAP client can
switch views later with NeoDebug's debugview request:
- Source (default) — steps and breakpoints follow your source lines.
- Disassembly — steps and breakpoints follow the NeoVM instructions, with the evaluation stack and slots exposed as raw values.
If source debug information is unavailable, a source-mode launch falls back to disassembly and reports the fallback in the debug console.
Evaluating expressions
In the debug console you can evaluate:
- named arguments, locals, and static fields by name;
- slots by index —
#arg0,#local1,#static0,#eval0,#result0; - contract storage rows —
#storage[...]; - with a leading cast —
(int),(bool),(string),(hex),(byte[]),(addr).
Replay vs. live
NeoDebug supports two ways to drive an invocation:
- Replay (
invocation.trace-file) — steps a recorded.neo-trace. Because the execution is a recording, you can step backward (time-travel). - Live (
invocation.operation) — deploys the contract into a fresh, single-block in-process chain and steps the call as it really executes. Live debugging cannot step backward.
The live launch runs against a throwaway local chain seeded only with the contract under debug; multi-contract scenarios, signer/account resolution against a Neo-Express chain, checkpoints, and oracle responses are not yet wired into the launcher.
Set signers to model the transaction accounts used by a live invocation. Every configured account
uses CalledByEntry, and the first account also determines the deployed contract hash. If signers
is omitted, the launcher uses the zero account. Live debugging follows normal Neo witness-scope
rules and does not fabricate signatures, so nested calls and checks for unrelated accounts still fail.