Skip to content

Instantly share code, notes, and snippets.

@paolino
Created May 5, 2026 18:49
Show Gist options
  • Select an option

  • Save paolino/96f933b1186145861853152ba77cd30a to your computer and use it in GitHub Desktop.

Select an option

Save paolino/96f933b1186145861853152ba77cd30a to your computer and use it in GitHub Desktop.
Amaru Treasury — UTxO & script dependency graph (Mermaid)

Amaru Treasury — UTxO & Script Dependency Graph

Source: pragma-org/amaru-treasury (main)

The system has three NFT-pinned UTxO families (Scopes, Registry, Treasury) plus a permissions script that holds no UTxO and is invoked through the withdraw-zero pattern. Dependencies split into bake-time (script parameters fixed at deploy) and run-time (which UTxOs must appear as input or reference input in a tx).


Bake-time parameter graph

flowchart TD
    seedS[seed UTxO for scopes]
    seedR[seed UTxO for registry]
    Scope{{Scope tag}}

    S["scopes validator<br/>policy = SCOPES_NFT"]
    P["permissions(scopes_nft, scope)"]
    R["treasury_registry(seed, scope)<br/>policy = REG_NFT[scope]"]
    T["Sundae treasury(cfg)<br/>cfg.registry_token = REG_NFT[scope]<br/>cfg.permissions.* = hash(P)"]

    seedS --> S
    seedR --> R
    Scope --> P
    Scope --> R
    S -- SCOPES_NFT --> P
    P -- "permissions script hash" --> T
    R -- "REG_NFT[scope]" --> T
Loading

Run-time UTxO dependency graph

flowchart TD
    SU["<b>Scopes UTxO</b> (NFT-pinned)<br/>Datum: Scopes {<br/>  core_development,<br/>  ops_and_use_cases,<br/>  network_compliance,<br/>  middleware : MultisigScript<br/>}"]
    RU["<b>Registry UTxO</b> per scope (NFT-pinned)<br/>Datum: ScriptHashRegistry {<br/>  treasury : Script h_t,<br/>  vendor   : Script h_v<br/>}<br/>Immutable: old == new"]
    TU["<b>Treasury UTxO</b> per scope (Sundae)<br/>Datum: TreasuryConfiguration {<br/>  registry_token,<br/>  permissions { reorganize, sweep, fund=AnyOf[], disburse },<br/>  expiration,<br/>  payout_upperbound = 0<br/>}<br/>Holds: withdrawn ADA"]
    P["permissions script (no UTxO)<br/>withdraw-zero gate<br/>Reorganize → owner<br/>Sweep / Disburse → owner + 1 other<br/>Fund → False"]
    VU["Vendor UTxO(s)<br/>per disbursement"]
    GA[(PRAGMA General Assembly multisig)]

    TU -- "withdraw 0 (delegates auth)" --> P
    P  -- "reference input" --> SU
    TU -- "reference input" --> RU
    TU -- "disburse / sweep" --> VU
    VU -- "reference input on cancel" --> RU
    GA -- "spend (mutate)" --> SU
    GA -- "spend (immutable)" --> RU
Loading

Why each arrow exists

Arrow Direction Reason
Treasury → Permissions runtime, withdraw-zero Sundae's treasury delegates who can sweep / disburse / reorganize to the permissions script. TreasuryConfiguration.permissions.* literally stores the permissions script hash.
Permissions → Scopes runtime, reference input expect_scopes(self.reference_inputs, scopes_nft) — owner credentials are dynamic, so permissions reads them from the Scopes UTxO each time. Decouples ownership rotation from script hashes.
Treasury → Registry runtime, reference input Sundae's vendor logic needs the treasury & vendor script hashes (e.g. for cancel-to-treasury). It locates them via the registry_token policy baked into TreasuryConfiguration.
Scopes UTxO ← admin spend Mutable but only by PRAGMA General Assembly multisig (must_be_approved_by_general_assembly). New datum must still match the Scopes shape.
Registry UTxO ← admin spend Spendable only by GA, and with_state forces old_datum == new_datum — effectively immutable until the NFT is burned.
Both NFT mints ← seed UTxO one-shot The two trap validators are parameterised by a seed OutputReference so the policy id is unique and minting can only happen once.
Vendor → Registry runtime, reference input Vendor outputs use the registry to find the treasury hash to return to on cancel.

One-sentence summary

The treasury holds the money but knows nothing about owners; it asks permissions; permissions asks the scopes UTxO; the registry tells everyone where the treasury and vendor scripts live — all three lookups are by NFT policy id, so addresses can rotate without re-parameterising scripts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment