Claude Code Plugins

Community-maintained marketplace

Feedback
0
0

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.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

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

::  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

::  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:

+$  state
  $:  %v1                          :: Version marker
      data=(map @ud entry)         :: Your data
      next-id=@ud                  :: ID counter
      config=settings              :: Configuration
  ==

Accessing State Fields

::  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.

::  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:

++  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:

    =/  input-field  field.cause
    
  2. Validate inputs:

    ?:  =(input-field 0)
      :_  state
      [%http-response 400 '{"error":"Field cannot be zero"}']~
    
  3. Compute new values:

    =/  new-id  +(next-id.state)
    =/  entry   [id data timestamp]
    
  4. Update state:

    =/  updated-state  state(data (~(put by data.state) key entry))
    
  5. Return effects and state:

    :_  updated-state
    :~  [%http-response 200 body]
        [%log 'Success']
    ==
    

Map Operations

Common Map Functions

::  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

=/  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

::  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

::  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

::  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

::  Literal cord (compile-time)
'hello'

::  Convert with crip (runtime)
(crip "hello")

::  Convert tape to cord
=/  t  "hello"
=/  c  (crip t)

Text Formatting

::  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)

::  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

++  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

++  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

::  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:

++  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

=/  timestamp  now.bowl
=/  entry  [id data timestamp ...]

Note: In the prover, we use now directly (it's implicitly from bowl).

Error Handling

Validation Pattern

::  Check condition
?:  (lth id.cause 1)
  ::  Return error
  :_  state
  :~  [%http-response 400 '{"error":"ID must be positive"}']
  ==

::  Continue with valid input
...

Not-Found Pattern

=/  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:

[%http-response code=@ud body=@t]     :: HTTP response
[%log message=@t]                     :: Log message
[%error message=@t]                   :: Error log

Multiple Effects

:_  state
:~  [%http-response 201 body]
    [%log 'SNARK submitted']
    [%log (crip "ID: {(scow %ud new-id)}")]
==

Compiling and Testing

Compilation

# 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:

    [%log 'Reached this point']
    [%log (crip "Value: {(scow %ud value)}")]
    
  2. Simplify complex expressions:

    ::  Instead of nested calls
    =/  step1  (operation1 input)
    =/  step2  (operation2 step1)
    =/  step3  (operation3 step2)
    step3
    
  3. Check types with hints:

    =/  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

=/  name  value           :: Bind variable
=/  name  ^-  @ud  42     :: With type hint

State Update

state(field value)                    :: Single field
state(field1 val1, field2 val2)      :: Multiple fields

Map Operations

(~(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

?:  condition  true-branch  false-branch   :: If-then-else
?~  maybe-val  null-branch  value-branch   :: If-null
?-  value  %a a  %b b  ==                  :: Pattern match

Effects

:_  state
:~  [%http-response 200 '{"ok":true}']
    [%log 'Done']
==

Example: Adding a New Poke

Let's add a %count-snarks command:

::  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:

hoonc prover/hoon/prover.hoon -o prover/out.jam

Additional Resources

Common Patterns from Prover Codebase

ID Generation

=/  new-id  next-id.state
:: ... use new-id ...
state(next-id +(next-id.state))

Building Entries

=/  entry  ^-  snark-entry
  :*  id
      proof
      inputs
      vk
      system
      submitter
      now
      %pending
      ~
      notes
  ==

Map Insert Pattern

=/  updated-state
  state(snarks (~(put by snarks.state) new-id entry), next-id +(next-id.state))

Response with State

:_  updated-state
:~  [%http-response 201 (crip (format-submit-response new-id))]
    [%log (crip "SNARK #{(scow %ud new-id)} submitted")]
==