moxzi
Docs / Getting started / Your first actor

Your first actoralpha

How do I run an actor, call it, upgrade it, and watch state survive?

moxzid is a server that hosts the wasm moxzi build produced and speaks IC message semantics to it: one message at a time per actor, await as a commit point, a trapping message rolled back, upgrades that keep state. The constraint that shapes every example below is that request bodies are raw candid bytes, not JSON — the empty argument is the six bytes DIDL\x00\x00, never an empty body.

Build two versions#

cat > counter.mo <<'EOF'
persistent actor {
  var count : Nat = 0;
  public func inc() : async Nat { count += 1; count };
  public query func peek() : async Nat { count };
};
EOF
moxzi build counter.mo -o counter.wasm

Then a v2 that adds a method, so the upgrade has something to show:

cat > counter2.mo <<'EOF'
persistent actor {
  var count : Nat = 0;
  public func inc() : async Nat { count += 1; count };
  public query func peek() : async Nat { count };
  public query func doubled() : async Nat { count * 2 };
};
EOF
moxzi build counter2.mo -o counter2.wasm

Start the server#

A manifest names the actors to host:

cat > actors.json <<'EOF'
{ "actors": [ { "name": "counter", "wasm": "counter.wasm" } ] }
EOF
moxzid -m actors.json -s ./state

-s ./state is what makes the state durable. Without it the server is in-memory and everything dies with the process — the right default for a scratch run and the wrong one for anything else, so it is explicit either way. The default bind is 127.0.0.1:7000; a non-loopback bind without --auth-token is refused at startup, because POST /install runs arbitrary wasm.

Wait for readiness before sending traffic. /health only says the process is up; the listener binds before journal recovery finishes:

curl -s http://127.0.0.1:7000/health/ready     # -> ready

Call it#

printf 'DIDL\x00\x00' > unit
curl -s --data-binary @unit http://127.0.0.1:7000/call/counter/inc | xxd -p
curl -s --data-binary @unit http://127.0.0.1:7000/call/counter/inc | xxd -p
curl -s --data-binary @unit http://127.0.0.1:7000/query/counter/peek | xxd -p
4449444c00017d01
4449444c00017d02
4449444c00017d02

Those replies are candid: 4449444c is DIDL, 00 an empty type table, 01 one return value, 7d the type nat, and the last byte the value — 1, then 2, then 2. Any candid encoder (didc, @dfinity/candid, whatever dfx generate writes) produces and decodes these bytes.

The split between the two endpoints is the IC's: /call/… is replicated and its writes stick, /query/… is non-replicated and its writes roll back when it returns.

Upgrade it#

curl -s -X POST --data-binary @counter2.wasm http://127.0.0.1:7000/upgrade/counter
upgraded
curl -s --data-binary @unit http://127.0.0.1:7000/query/counter/peek    | xxd -p  # 4449444c00017d02
curl -s --data-binary @unit http://127.0.0.1:7000/query/counter/doubled | xxd -p  # 4449444c00017d04

The counter still says 2, and doubled — a method that did not exist a moment ago — answers 4. The upgrade runs the IC's own sequence: pre_upgrade, memory persists, the new module's start function runs over it, post_upgrade.

upgrade and install are different verbs on purpose. POST /install/<name> over an existing actor is refused, because installing would discard state.

Watch it survive a crash#

moxzid -m actors.json -s ./state & PID=$!
# … calls as above …
kill -9 $PID
moxzid -m actors.json -s ./state &
curl -s http://127.0.0.1:7000/health/ready
curl -s --data-binary @unit http://127.0.0.1:7000/query/counter/peek | xxd -p
ready
4449444c00017d02

Not a graceful stop — kill -9, no pre_upgrade, no checkpoint on the way out. The state comes back from a snapshot plus write-ahead-log replay. Two recovery paths exist and both are exercised by scripts/moxzid_crash.sh: snapshot (the default for actors at or below --snapshot-below, 50 MB) and pure log replay (--snapshot-below 0).

For an orderly backup, stop with SIGTERM instead: it finishes the in-flight message, checkpoints every actor and exits 0. Do not copy the state directory while the server is running — you will get torn writes.

The endpoints you will use first#

EndpointAuthWhat
GET /health/readyopenready once the manifest is installed and journals replayed
POST /call/<actor>/<method>tokencandid in, candid out; 402 means out of cycles, 400 a trap or reject
POST /query/<actor>/<method>tokensame, non-replicated
POST /upgrade/<name>tokenbody = wasm; keeps state
POST /install/<name>tokenbody = wasm; refused if the name exists; replies with the new actor's hex id
GET /logs/<actor>tokenDebug.print output, taken and cleared
GET /openthe inspector dashboard, in a browser

On a loopback bind with no --auth-token, the token checks are off — that is why the curls above carry no Authorization header. Set --auth-token (or MOXZID_TOKEN) and every endpoint except /health, /health/ready and /version needs Authorization: Bearer <token>.

Things you cannot do#

Next#

On this pageBuild two versionsStart the serverCall itUpgrade itWatch it survive a crashThe endpoints you will use firstThings you cannot doNext