Write and compile Hoon code following Nock conventions and NockApp patterns. Use when working with Hoon kernel code, modifying state structures, adding pokes/peeks, or debugging Hoon compilation.
Scanned 2/12/2026
Install via CLI
openskills install majiayu000/claude-skill-registry---
name: hoon-development
description: Write and compile Hoon code following Nock conventions and NockApp patterns. Use when working with Hoon kernel code, modifying state structures, adding pokes/peeks, or debugging Hoon compilation.
---
# Hoon Development Guide
This skill helps you write idiomatic Hoon code for the Prover NockApp kernel.
## Hoon Basics
### File Structure
```hoon
:: Comments start with ::
:: Documentation comments
::
|% :: Core definition (library of types and functions)
+$ type-name :: Type definition
structure
--
|_ =state :: Door (object with state)
++ arm-name :: Arm (method/function)
code
--
```
### Type Definitions
```hoon
:: Atoms (basic types)
@ud :: Unsigned decimal (0, 1, 42)
@t :: Text/cord ('hello')
@tas :: Term/symbol (%groth16, %pending)
@da :: Timestamp/date (~2024.1.1)
:: Complex types
(list @t) :: List of text
(map @ud @t) :: Map from numbers to text
(unit @t) :: Optional text (~ or [~ 'value'])
?(%a %b %c) :: Union of terms
:: Structures
+$ person
$: name=@t
age=@ud
email=@t
==
:: Unions (discriminated)
+$ action
$% [%create name=@t]
[%update id=@ud name=@t]
[%delete id=@ud]
==
```
## State Management
### State Structure
Your NockApp state should be versioned:
```hoon
+$ state
$: %v1 :: Version marker
data=(map @ud entry) :: Your data
next-id=@ud :: ID counter
config=settings :: Configuration
==
```
### Accessing State Fields
```hoon
:: Read field
=/ current-id next-id.state
:: Read nested field
=/ first-entry (~(get by data.state) 1)
```
### Updating State Immutably
**IMPORTANT**: Never mutate state directly. Always create a new state.
```hoon
:: Update single field
state(next-id +(next-id.state))
:: Update multiple fields
state(data new-data, next-id +(next-id.state))
:: Update with complex computation
=/ new-data (~(put by data.state) key value)
=/ new-id +(next-id.state)
state(data new-data, next-id new-id)
```
## The ++poke Pattern
All input commands flow through `++poke`:
```hoon
++ poke
|= =cause :: Take a cause as input
^- [effects=(list effect) _state] :: Return effects and new state
?- -.cause :: Pattern match on cause tag
%command-one
:: Handle command one
:_ state :: Return state first (reversed)
:~ [%http-response 200 body] :: Effect list
==
%command-two
:: Handle command two
=/ updated-state state(...)
:_ updated-state
:~ [%http-response 201 body]
[%log 'Action completed']
==
==
```
### Key Patterns
1. **Extract inputs from cause**:
```hoon
=/ input-field field.cause
```
2. **Validate inputs**:
```hoon
?: =(input-field 0)
:_ state
[%http-response 400 '{"error":"Field cannot be zero"}']~
```
3. **Compute new values**:
```hoon
=/ new-id +(next-id.state)
=/ entry [id data timestamp]
```
4. **Update state**:
```hoon
=/ updated-state state(data (~(put by data.state) key entry))
```
5. **Return effects and state**:
```hoon
:_ updated-state
:~ [%http-response 200 body]
[%log 'Success']
==
```
## Map Operations
### Common Map Functions
```hoon
:: Insert or update
=/ new-map (~(put by old-map) key value)
:: Lookup (returns unit)
=/ maybe-value (~(get by map) key)
?~ maybe-value
:: Handle not found
:: Handle found, use u.maybe-value
:: Delete
=/ new-map (~(del by old-map) key)
:: Check existence
=/ exists (~(has by map) key)
:: Convert to list
=/ pairs ~(tap by map) :: List of [key value]
:: Get size
=/ count ~(wyt by map)
```
### Safe Map Access Pattern
```hoon
=/ maybe-entry (~(get by snarks.state) id.cause)
?~ maybe-entry
:: Not found - return 404
:_ state
:~ [%http-response 404 '{"error":"Not found"}']
==
:: Found - use u.maybe-entry
=/ entry u.maybe-entry
:: ... continue with entry
```
## List Operations
```hoon
:: Create list
=/ my-list ~['a' 'b' 'c']
=/ empty-list ~
:: Add to front
=/ new-list [item my-list]
:: Iterate/map
=/ doubled (turn my-list |=(x=@ud (mul x 2)))
:: Filter
=/ evens (skim my-list |=(x=@ud =(0 (mod x 2))))
:: Fold/reduce
=/ sum (roll my-list add)
:: Check if empty
?~ my-list
:: Empty case
:: Non-empty case, i.my-list is head, t.my-list is tail
```
## Control Flow
### Conditionals
```hoon
:: If-then-else (for values)
?: condition
value-if-true
value-if-false
:: If-null check
?~ maybe-value
handle-null
handle-value :: u.maybe-value is the unwrapped value
:: Pattern matching
?- value
%option-a result-a
%option-b result-b
%option-c result-c
==
```
### Variable Binding
```hoon
:: Bind variable
=/ name value
:: Bind with type
=/ name ^- @ud 42
:: Sequential bindings
=/ a 1
=/ b 2
=/ c (add a b)
c :: Returns 3
```
## Text and String Operations
### Creating Text
```hoon
:: Literal cord (compile-time)
'hello'
:: Convert with crip (runtime)
(crip "hello")
:: Convert tape to cord
=/ t "hello"
=/ c (crip t)
```
### Text Formatting
```hoon
:: Format number to text
=/ num-text (scow %ud 42) :: "42"
=/ id-text (scow %ud id.cause) :: Format ID
:: Convert text to tape for manipulation
=/ tape-form (trip 'hello') :: "hello"
:: Concatenate (as tapes)
=/ result (weld "hello" " world")
=/ cord-result (crip result) :: 'hello world'
```
### JSON-ish String Building (Simplified)
```hoon
:: Simple response
'{"success":true,"id":42}'
:: With interpolation (manual)
=/ id-str (scow %ud id)
(crip (weld "{\"id\":" (weld id-str "}")))
```
**Note**: The prover uses helper functions for JSON formatting. See `++format-submit-response` in prover.hoon.
## Common Helper Functions
### From the Prover Codebase
```hoon
++ format-submit-response
|= id=@ud
^- tape
(weld "{\"success\":true,\"id\":" (weld (trip (scow %ud id)) "}"))
++ format-snark-detail
|= [id=@ud entry=snark-entry]
^- tape
:: Build JSON response string
...
```
### Creating Your Own Helpers
```hoon
++ build-error-response
|= message=@t
^- @t
(crip (weld "{\"error\":\"" (weld (trip message) "\"}")))
++ format-list-response
|= items=(list @t)
^- @t
:: Convert list to JSON array
...
```
## Type Hints and Casts
```hoon
:: Specify return type
=/ value ^- @ud 42
:: Cast to type
=/ entry ^- snark-entry
[id proof inputs vk system submitter now %pending ~ notes]
:: Useful for complex structures
=/ new-state ^- state
state(data new-data, next-id new-id)
```
## The Bowl (Context)
In NockApp pokes, you have access to a `bowl` with context:
```hoon
++ poke
|= [=cause =bowl:cask] :: Bowl is available
^- [(list effect:cask) _state]
:: Access bowl fields:
now.bowl :: Current timestamp (@da)
our.bowl :: Our identity
:: etc.
```
**Common use**: Get current timestamp
```hoon
=/ timestamp now.bowl
=/ entry [id data timestamp ...]
```
**Note**: In the prover, we use `now` directly (it's implicitly from bowl).
## Error Handling
### Validation Pattern
```hoon
:: Check condition
?: (lth id.cause 1)
:: Return error
:_ state
:~ [%http-response 400 '{"error":"ID must be positive"}']
==
:: Continue with valid input
...
```
### Not-Found Pattern
```hoon
=/ maybe-entry (~(get by data.state) id.cause)
?~ maybe-entry
:_ state
:~ [%http-response 404 '{"error":"Not found"}']
[%log (crip "Entry {(scow %ud id.cause)} not found")]
==
:: Entry exists, use u.maybe-entry
...
```
## Effects
Common effect types:
```hoon
[%http-response code=@ud body=@t] :: HTTP response
[%log message=@t] :: Log message
[%error message=@t] :: Error log
```
### Multiple Effects
```hoon
:_ state
:~ [%http-response 201 body]
[%log 'SNARK submitted']
[%log (crip "ID: {(scow %ud new-id)}")]
==
```
## Compiling and Testing
### Compilation
```bash
# Compile Hoon to Nock
hoonc prover/hoon/prover.hoon -o prover/out.jam
# Check for syntax errors
hoonc --check prover/hoon/prover.hoon
```
### Common Compilation Errors
1. **Mint failure**: Type mismatch
- Check your type annotations (`^- type value`)
- Ensure structure matches type definition
2. **Find failure**: Unknown name
- Check spelling of variables and arms
- Ensure variables are in scope (`=/` binding)
3. **Nest failure**: Type doesn't fit expected shape
- Check structure field order
- Verify all required fields are present
### Debugging Tips
1. **Add log effects**:
```hoon
[%log 'Reached this point']
[%log (crip "Value: {(scow %ud value)}")]
```
2. **Simplify complex expressions**:
```hoon
:: Instead of nested calls
=/ step1 (operation1 input)
=/ step2 (operation2 step1)
=/ step3 (operation3 step2)
step3
```
3. **Check types with hints**:
```hoon
=/ value ^- expected-type expression
```
## Best Practices
1. **Always version your state**: `$: %v1 ...`
2. **Use type hints for complex values**: `^- type value`
3. **Validate inputs early**: Check before updating state
4. **Keep arms focused**: One responsibility per arm
5. **Use helper functions**: Extract formatting and validation
6. **Log important operations**: Help with debugging
7. **Handle errors explicitly**: Don't assume success
8. **Update state immutably**: Use `state(field new-value)`
9. **Test incrementally**: Compile after each change
10. **Follow existing patterns**: Match the style in prover.hoon
## Quick Reference
### Variable Binding
```hoon
=/ name value :: Bind variable
=/ name ^- @ud 42 :: With type hint
```
### State Update
```hoon
state(field value) :: Single field
state(field1 val1, field2 val2) :: Multiple fields
```
### Map Operations
```hoon
(~(put by map) key value) :: Insert/update
(~(get by map) key) :: Lookup (returns unit)
(~(del by map) key) :: Delete
~(tap by map) :: Convert to list
```
### Conditionals
```hoon
?: condition true-branch false-branch :: If-then-else
?~ maybe-val null-branch value-branch :: If-null
?- value %a a %b b == :: Pattern match
```
### Effects
```hoon
:_ state
:~ [%http-response 200 '{"ok":true}']
[%log 'Done']
==
```
## Example: Adding a New Poke
Let's add a `%count-snarks` command:
```hoon
:: 1. Add to cause type
+$ cause
$% ...
[%count-snarks ~]
==
:: 2. Implement in ++poke
++ poke
|= [=cause =bowl:cask]
^- [(list effect:cask) _state]
?- -.cause
...
%count-snarks
=/ count ~(wyt by snarks.state)
=/ response (crip (weld "{\"count\":" (weld (trip (scow %ud count)) "}")))
:_ state
:~ [%http-response 200 response]
[%log (crip "Total SNARKs: {(scow %ud count)}")]
==
==
```
Done! Compile and test:
```bash
hoonc prover/hoon/prover.hoon -o prover/out.jam
```
## Additional Resources
- Hoon School: https://developers.urbit.org/guides/core/hoon-school
- Hoon Standard Library: https://developers.urbit.org/reference/hoon
- NockApp Documentation: Check the nockup project docs
## Common Patterns from Prover Codebase
### ID Generation
```hoon
=/ new-id next-id.state
:: ... use new-id ...
state(next-id +(next-id.state))
```
### Building Entries
```hoon
=/ entry ^- snark-entry
:* id
proof
inputs
vk
system
submitter
now
%pending
~
notes
==
```
### Map Insert Pattern
```hoon
=/ updated-state
state(snarks (~(put by snarks.state) new-id entry), next-id +(next-id.state))
```
### Response with State
```hoon
:_ updated-state
:~ [%http-response 201 (crip (format-submit-response new-id))]
[%log (crip "SNARK #{(scow %ud new-id)} submitted")]
==
```
No comments yet. Be the first to comment!