Skip to main content

Contract Commands (modal contract / modal c)

Manage contracts — create, commit, push, pull, and inspect.

Create​

modal c create [OPTIONS]

Creates a new contract in the current directory.

What it creates:

.contract/           # Contract metadata
├── config.json # Contract configuration
├── commits/ # Commit storage
└── HEAD # Current commit reference
state/ # Working state directory

Options:

OptionDescription
--dir <DIR>Directory path where the contract will be created (defaults to current directory)
--signer <ID or PASSFILE>A key allowed to extend the contract; repeat for several. The genesis commit posts the keys at /signers/<n>.id, a model and the rule that every later commit is signed by one of them. The contract's key signs the set for the hash lane (.contract/signer_set.json). Fixed at creation
--key <PASSFILE>The contract's key, instead of a new one: the contract's id is that key's id. A miner makes the contract its blocks nominate this way, to take their MOD. One contract per key
--output <FORMAT>Output format: text or json

Commit​

modal c commit [OPTIONS]

Create a new commit from the contract working directories, a single state path, or an inline domain action.

Options:

OptionDescription
--path <PATH>State path to write for a single POST-style commit
--value <VALUE>Value for the single-path commit; strings, numbers, and JSON are accepted
--post <PATH=VALUE>Post a value at a path in this commit whether or not it changed; repeat for several paths. A .text, .md, .id or .date value is kept as text; any other is read as JSON, else as text. Alone, or beside --all or --path
--method <METHOD>Commit method for the single-path commit (default: post)
--dir <DIR>Contract directory (defaults to current directory)
--output <FORMAT>Output format: text or json
--sign <PASSFILE>Sign with a passfile path or identity name; repeat to attach multiple signatures. Each signs the contract id and the whole commit except its signatures (see Commit signatures)
--all, -aCommit all changed state/, rules/, reposts/, and model/default.modality files. Staged REPOST dests emit method: repost. With --method create, send, recv or invoke, that action joins the same commit, so a SEND and the flag it sets are one commit.
--message, -m <MSG>Commit message
--action <JSON>Commit an inline JSON domain action or read it from a .json file path
--asset-id <ASSET_ID>Asset ID for CREATE and SEND commits; on a RECV, the asset it states it receives
--asset-contract <CONTRACT_ID>Creator of a received asset: on a SEND, the held asset to send on; on a RECV, the creator it states. Omit it for the contract's own asset
--memo <JSON>JSON the receiver of a SEND reads, recorded with it; on a RECV, the memo it states the SEND carries
--quantity <QUANTITY>Asset quantity for CREATE commits
--divisibility <DIVISIBILITY>Asset divisibility for CREATE commits: every amount of the asset must be a multiple of it (1 lets any whole number of units move)
--decimals <PLACES>Display decimals for CREATE commits: how a wallet shows amounts (8 shows 150000000 as 1.5). No rule reads it
--gas-limit <GAS>The most gas the commit may use, signed into its head. Without it, the network's default applies. The commit prints the gas it uses. See Gas
--payer <ID or PASSFILE>The wallet that pays the commit's gas where gas is priced. Its key must also sign (--sign). See Gas
--gas-tip <UNITS>A tip per gas for the sequencer that orders the commit. Rounds fill highest tip first
--max-gas-price <UNITS>The most the commit pays per gas, base and tip together; a block priced higher refuses it, uncharged
--to-contract <TO_CONTRACT>Destination contract ID for SEND commits
--amount <AMOUNT>Amount for SEND commits; on a RECV, the amount it states it receives. Apply refuses a RECV whose statement differs from its SEND
--send-commit-id <SEND_COMMIT_ID>Source SEND commit ID for RECV commits
--send-index <N>Which SEND of that commit a RECV takes, from 0 (default); emitted SENDs count in invoke order
--theory <v0|v2|v3>Predicate theory local verify runs (default v3, what the testnet runs). Use v0 for a network whose network.json leaves predicate_theory_version unset, such as the bundled devnets, and v2 for a network that sets v2. v3 is v2 with numbers compared exactly. v2 and v3 refuse dead model edges that v0 accepts, and accept rules that v0 refuses, such as always([+SEND -any_signed(/claimants)] false). See Predicate theory

Examples:

# Commit all changes with signature
modal c commit --all --sign alice -m "Add escrow rules"

# Commit all changes with multiple member signatures
modal c commit --all --sign alice --sign bob -m "Replace witness"

# Pay out and mark the flag in one commit
modal c commit --all --method send --asset-id drops --to-contract <WALLET_ID> --amount 10 --sign carol

# Send on 100 of a received asset, with a memo for the receiver
modal c commit --method send --asset-contract <CREATOR_ID> --asset-id tokA --to-contract <POOL_ID> --amount 100 --memo '{"op":"swap","min_out":300}'

# Receive it, stating what arrives; apply refuses the RECV if it differs
modal c commit --method recv --send-commit-id <SEND_ID> --asset-contract <CREATOR_ID> --asset-id tokB --amount 362

# Commit one state file
modal c commit --path /notes.text --value "signed update" --sign alice

# Restate several facts together, even ones that did not change
modal c commit --post /run/sha.text=4f2a9c --post /run/passed.bool=true --sign ci

# Commit a domain action
modal c commit --action '{"type":"DEPOSIT","amount":100}' --sign alice

Under the default --theory v3, modal c commit refuses a model edge whose labels can never hold together, such as +num_lt(/escrow/paid.num,"100") beside +num_gte(/escrow/paid.num,"100"). With --theory v0, the commit is accepted and the output previews what v3 would change about it, as a warning. Sequencers and validators refuse the commit if the network sets predicate_theory_version to v3, as the testnet does. With --output json, the findings are under theory_preview.

Checkout​

modal c checkout [OPTIONS]

Extract committed state to the working state/ directory.

Options:

OptionDescription
--dir <DIR>Contract directory (defaults to current directory)

Status​

modal c status [OPTIONS]
modal status # alias for modal c status

Shows:

  • Current commit
  • Modified files
  • Staged changes
  • Rule validation status

Options:

OptionDescription
--dir <DIR>Contract directory (defaults to current directory)
--remote <NAME>Remote name to compare with (default: origin)
--output <FORMAT>Output format: text or json

Diff​

modal c diff [OPTIONS]

Show changes between working state and committed state.

Options:

OptionDescription
--dir <DIR>Contract directory (defaults to current directory)
--output <FORMAT>Output format: text or json

Log​

modal c log [OPTIONS]

Show commit history.

Options:

OptionDescription
--dir <DIR>Contract directory (defaults to current directory)
--limit <N>, -n <N>Limit number of commits shown
--output <FORMAT>Output format: text or json

Example output:

abc123 (HEAD) Add escrow rules [alice] 2024-01-15 10:30:00
def456 Initial contract setup [alice] 2024-01-15 10:00:00

Set​

modal c set [OPTIONS] <PATH> <VALUE>

Set a state file value.

Options:

OptionDescription
--dir <DIR>Contract directory (defaults to current directory)

Examples:

# Set text value
modal c set /config/name.text "My Contract"

# Set boolean
modal c set /flags/active.bool true

Repost​

modal c repost <SOURCE_CONTRACT> <SOURCE_PATH> [DEST_PATH]

Snapshot a value from another contract into this one so formulas can name the dest path.

Arguments:

ArgDescription
SOURCE_CONTRACTSource contract ID
SOURCE_PATHPath on the source (e.g. /parties/alice.id)
DEST_PATHOptional dest in this contract. Default: /reposts/<source_id><source_path>

Options:

OptionDescription
--from-dir <DIR>Read the source from a local contract directory instead of the hub
--dir <DIR>Dest contract directory (defaults to current directory)
modal c repost abc123 /notes/hello.text
modal c repost abc123 /parties/alice.id /parties/alice.id
modal c repost abc123 /notes/hello.text --from-dir ../source-contract
modal commit --all

Add Rule​

modal c add-rule --name <NAME> [OPTIONS] <FORMULA>

Write a named rule file under rules/. The command creates rules/ if needed and wraps the formula as export default rule { starting_at $PARENT ... }.

Options:

OptionDescription
--name <NAME>Rule name written as rules/<name>.modality
--starting-at <ANCHOR>Rule anchor; only $PARENT (the default) is supported
--dir <DIR>Contract directory (defaults to current directory)
modal c add-rule --name authorized \
'always([-signed_by(/parties/alice.id) -signed_by(/parties/bob.id)] false)'

Existing rule files are not overwritten. Run modal c commit --all after adding a rule.

AI​

Configure a provider first with modal ai set. Then:

modal c ai suggest-rule <PROMPT>

Suggest a Modality rule formula from a plain-language prompt. Use the printed formula with modal c add-rule. Encodings follow the formula cookbook; witness models follow the model cookbook. If no provider is configured, the command fails with a hint to run modal ai set --provider openai|anthropic|grok|bedrock|ollama|cursor-agent.

modal c ai suggest-rule "after this commit either alice or bob must sign"
always([-signed_by(/parties/alice.id) -signed_by(/parties/bob.id)] false)

That printed formula is example output; yours may differ.

Set Named ID​

modal c set-named-id [OPTIONS] <PATH> <NAME>

Set a .id file from a passfile path or a passfile name that resolves in the standard passfile locations.

Options:

OptionDescription
--dir <DIR>Contract directory (defaults to current directory)
modal c set-named-id /parties/alice.id alice
modal c set-named-id /parties/alice.id example/alice

Get​

modal c get <PATH> [OPTIONS]

Get contract or state information.

Options:

OptionDescription
--commit <HASH>Get from specific commit
--rawOutput raw bytes
modal c get /parties/alice.id
modal c get /data/config.json --commit abc123

ID Commands​

# Get contract ID
modal c id

# Get current commit ID
modal c commit-id

Push​

modal c push [OPTIONS]

Push commits to a hub or to chain sequencers.

Remote formats:

  • Hub: http://hub.example.com/contracts/<id>
  • Chain: /ip4/<addr>/tcp/<port>/p2p/<peer_id>

Options:

OptionDescription
--remote <URL>Target node multiaddress or hub URL; also saves it under the remote name
--remote-name <NAME>Remote name (default: origin)
--dir <DIR>Contract directory (defaults to current directory)
--node-dir <DIR>Node directory for identity/config when using P2P remotes
--hub-creds <FILE>Hub credentials file for HTTP hub remotes
--output <FORMAT>Output format: text or json
--revealPush the commits as reveals of hash commitments made with anchor. The node refuses a reveal whose body does not hash to its commit id, or whose hash was never anchored. Chain remotes only

A contract's log is linear. On chain sequencers, a commit must extend the contract's current head: if another commit was already sequenced on the same parent, the sequencer refuses yours ("forks the contract"). Pull, then commit again on the new head.

A commit's id is the SHA-256 of the commit as compact JSON, {"body":[...],"head":{...}}: each action's fields and the head's in the order modal writes them, and the keys of every object inside a value sorted. A node recomputes the id from the body and head it receives and refuses a commit that does not hash to it; nothing of that push is queued. Write commits with modal c commit, or hash them the same way.

On chain remotes, push, pull, anchor and replay start a short-lived node. It logs warnings and errors to stderr, so stdout carries only the command's output (--output json parses as is). Set RUST_LOG=info for more.

Anchor​

modal c anchor [OPTIONS]

Anchor commit hashes on a network's hash lane, without their bodies. For each commit, anchor signs a hash commitment, grinds its hashtax against the node's current epoch anchor, and submits it. A certified sequencer block orders the hash. Send the bodies later with push --reveal. See Hash Commitments.

Options:

OptionDescription
--remote <MULTIADDR>Target node (defaults to the remote's URL; not saved)
--remote-name <NAME>Remote name (default: origin)
--dir <DIR>Contract directory (defaults to current directory)
--node-dir <DIR>Node directory for identity/config
--commit <ID>Commit to anchor; repeat for several. Defaults to the commits not yet pushed
--sign <PASSFILE>Key that signs the hash commitments. Defaults to a one-off key. For a contract created with --signer, one of its signers
--bits <N>Leading zero bits of work to grind for (default: the network's floor). More work wins a full block
--statusShow what the node has for each commit (unknown, anchored, revealed) instead of anchoring
--output <FORMAT>Output format: text or json
modal c anchor --remote /ip4/127.0.0.1/tcp/10101/ws/p2p/12D3KooW...
modal c anchor --status --commit <commit id>
modal c push --reveal

A network without a hash lane refuses anchor. For a contract created with --signer, the genesis hash commitment posts the signer set with the contract key's signature. Once it is certified, a hash commitment signed by any other key is refused.

Pull​

modal c pull [URL] [OPTIONS]

Pull commits from a hub or chain.

Options:

OptionDescription
[URL]Full contract URL to clone, such as https://hub/contracts/<id>
--remote <URL>Target node multiaddress or hub URL
--remote-name <NAME>Remote name (default: origin)
--dir <DIR>Contract directory (defaults to current directory)
--node-dir <DIR>Node directory for identity/config when using P2P remotes
--hub-creds <FILE>Hub credentials file for HTTP hub remotes
--contract-id <ID>Start a copy of this contract in --dir, which must not hold one yet, and pull its sequenced commits from --remote (saved as the remote)
--output <FORMAT>Output format: text or json

A node returns a contract's sequenced commits in log order, each after its parent. Pull checks that each hashes to its id, moves HEAD when it was unset or at the last pulled commit, and writes the state and rules files.

# Anyone with the contract id can take a copy and commit on its head
modal c pull --contract-id 12D3KooW... --dir ./pool --remote /ip4/127.0.0.1/tcp/10101/ws/p2p/12D3KooW...

Replay​

modal c replay [OPTIONS]

Fetch a sequenced contract prefix (and posted WASM, if any) and re-check it locally. The original node is not trusted: the same accumulated rules used on sequenced apply are run again. That is independent replay.

# Stranger: fetch from a sequencer and verify
modal c replay --remote /ip4/127.0.0.1/tcp/10101/ws/p2p/<peer> \
--contract-id <id> --through <commit> --save prefix.json

# Later, offline
modal c replay --artifact prefix.json

Options:

OptionDescription
--artifact <FILE>Verify a saved replay artifact (no network)
--save <FILE>Write the fetched or local artifact as JSON
--remote <MULTIADDR>Sequencer to fetch /contract/replay from
--contract-id <ID>Contract id when fetching without --dir
--through <COMMIT>Sequenced tip to replay through
--dir <DIR>Local contract directory (used when not fetching)
--node-dir <DIR>Node directory for identity/config on P2P remotes
--output <FORMAT>Output format: text or json
--theory <v0|v2|v3>Predicate theory to replay a local --dir under (default v3, as modal c commit verifies). A fetched or saved artifact names its own theory

Theory​

modal c theory [OPTIONS]

Show what the predicate theory derives from the accepted contract:

  • dead edges: model transitions whose labels no commit can satisfy together, including any transition that needs a predicate the validator never evaluates (see Standard Predicates);
  • edges dead after a step: transitions whose labels contradict what every way into their state keeps unchanged (through -modifies), so no run takes them (a warning; contracts may end);
  • committed .theory.json declarations the theory cannot read. The validator does not evaluate wasm yet, so a +wasm(...) transition is dead whatever its declaration says;
  • each move out of the current state, marked as open, blocked, or forced.

A move is blocked when accepted state already makes one of its labels false. It is forced when it is the only open move out of a state that has more than one. The view is read-only. Sequencers and validators enforce the network's predicate_theory_version (V0 unless the network names one).

Options:

OptionDescription
--dir <DIR>Contract directory (defaults to current directory)
--theory <VERSION>Theory version to preview: v3 (default), v2, v1, or v0. Networks refuse v1. v3's entailment is v2's
--output <FORMAT>Output format: text or json

Pack / Unpack​

# Pack contract into portable file
modal c pack --output contract.modal

# Unpack contract file
modal c unpack contract.modal --output ./my-contract

Options:

CommandOptionDescription
pack--output <FILE>, -o <FILE>Output .contract file path
pack--dir <DIR>Contract directory (defaults to current directory)
unpack<INPUT>Input .contract file path
unpack--output <DIR>, -o <DIR>Output directory
unpack--forceOverwrite an existing output directory

Assets​

modal c assets [OPTIONS]

Manage contract assets.

Options:

OptionDescription
--listList all assets
--add <PATH>Add asset
--remove <PATH>Remove asset

WASM Upload​

modal c wasm-upload <WASM_FILE> [OPTIONS]

Upload a WASM module for custom predicates.

Options:

OptionDescription
--name <NAME>Module name
--sign <PASSFILE>Sign upload