Daimon

talus protocol · developers

Call Daimon's LLM tool from a Talus Protocol task.

You use your own Sui address; Daimon never holds your keys. You pre-pay USDC into an on-chain escrow ticket, a Talus Protocol task calls tech.daimon.llm@1, the tool settles the real cost from the ticket, and the rest comes back to your Daimon account. Live on Sui mainnet.

Also live: tech.daimon.chat@1, where you set a USDC budget once and sign a short voucher per call instead of minting a ticket: chat@1 guide. Everything else on this page is how to call tech.daimon.llm@1.

Steps

The call, step by step

01deposit
suiUSDC
Create a Daimon identity (a shared object plus an OwnerCap you keep) and deposit USDC into its account.
02mint a ticket
suimax_cost → escrow
Commit to the exact request (model and prompt) and move at most max_cost out of your account into escrow.
03run the task
talus protocolSUI fees
Schedule a one-vertex DAG that calls the tool with your identity id, ticket seq, model and prompt.
04settle
suiactual cost
The tool calls OpenRouter, settles the actual cost (x 1.20) from the ticket, and returns the rest of the escrow to your account.
05read the answer
talus protocolfree
Inspect the finished execution for the completion, then close the task to get unused prepay back.

If the tool or operator never settles, anyone can return the escrow to your account after the ticket expires.

Constants

NameValue
Daimon package, original id (object types, pre-existing events)0x670f3f97acf3e6a9a7e8a090e84e5c76d42d596ecf4e6178483aa1c091da9403
Daimon package call target of this guide (v2 published-at, has daimon::llm). Use for every call target0x57eee22c2765db36ebe7ab3b99678de8228959da47363224043fa78746983c1c
Config (shared)0x2790f87aa70112d1f435e5777d0e5ddab07d4b89361a3d08653f45d5d298ebaa
USDC type0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC
Clock0x6
Tooltech.daimon.llm@1, host https://nexus.daimon.run
Tool id (Talus Protocol mainnet registry)0x58ce31efb014578daf0d1099f72ad466f38eb7cfac172a4e588850a3704d7100
Published DAG (one vertex, immutable)0xd688f096124d578039c307b1fcc1d91a6ec51b838eb23596bff77ae85623a840

Types of objects you create (Aion, OwnerCap) carry the original id. The new llm events (TicketMinted, TicketSettled, TicketReclaimed) carry the v2 id; Deposited and AgentCreated keep the original id. A future upgrade changes the call target only. Check the tool is registered before minting:

nexus tool inspect --tool-fqn tech.daimon.llm@1     # "is registered", Compatibility: current

Price and limits

  • One call = one committed ticket = one request: identity_id, ticket_seq, model, and either prompt or messages (optional thread_id for provider prompt-cache stickiness).
  • A paid OpenRouter slug that escrow serves (/docs/nexus/models). :free slugs are refused.
  • At most 1000 output tokens per call, 2000 for the models with a measured throughput floor (deepseek/deepseek-v4.1-flash, deepseek/deepseek-v4.1-flash-1017). A model escrow does not serve is refused before any spend, ticket refunded in full: model_unbounded_reasoning (it may reason without being asked), model_unbounded_fees (it searches the web on its own), model_unpriced (a fee the cap cannot account for).
  • Price = actual OpenRouter cost x 1.20, settled from the ticket and clipped to the ticket's max_cost. Whatever is left of the escrow returns to your Daimon account on settle. The Talus Protocol invocation price is 0, but every call also costs a network fee in SUI from your own wallet (about 0.08 SUI, see Network fee in SUI).
  • Limits (Config): account balance at most 50 USDC, at most 1 USDC escrow per ticket, at most 5 USDC per UTC day (open escrows count toward the day until settled or refunded).
  • Ticket TTL is 24 h. The tool serves a ticket only while at least 6 h of TTL remain, so run the task within 18 h of minting. max_cost is in micro-USDC (1 USDC = 1,000,000).
  • Only committed tickets (kind 0) are served; open vouchers (kind 1) are refused by the tool.
  • Do not send secrets. Every input and output port is public on chain: identity_id, ticket_seq, model, prompt or messages (including any system message), thread_id, and the result (completion, cost_micro, cached_tokens, or the error reason). The operator also stores the completion for exactly-once replay. Keep nothing private in a system prompt or a thread id.

Network fee in SUI

Besides the USDC price, every call costs SUI, paid from the wallet that schedules the task. It goes to the Talus Protocol network and Sui validators, not to Daimon. One run measured about 0.075 to 0.09 SUI net on mainnet (budget 0.08 SUI per call; it varies with the Sui gas price).

PartPaid byNotes
Schedule transaction gas + Talus Protocol execution feesyour SUIThe task prepay you lock is 0.1 SUI; the unused part returns on close.
Close transaction gasyour SUIReleases the task and refunds the unused prepay.
Mint transaction gasyour SUIA separate small Sui transaction before the task (not part of the 0.075 to 0.09 figure).
Create identity, deposit USDCyour SUIOnce per identity, not per call.

A call that is refused or fails still spends the schedule and close fees. The USDC is refunded; the SUI is not. Check the model and the request before you mint.

Models and prices

/docs/nexus/models lists every model escrow serves with its price per million tokens (OpenRouter + 20%), the output cap and what is excluded. The same data is a download: models.json, generated from the proxy's own rules.

Size max_cost for the worst case so the whole answer fits: ceil((prompt_bound x max(input, cache_write, 2 x input) + request_fee + T x max(output, reasoning)) x 1.2), with prompt_bound = request bytes + 64 + 32 + 16 x messages and T = min(1000, or the larger ceiling of a measured model above, the model's output limit). A lower cap cuts the answer short, or is refused before any spend (cap_too_low, ticket refunded, SUI spent).

Setup

You need the sui CLI (1.81 tested), jq, python3, a funded mainnet address with SUI and USDC, and the nexus CLI (see Run the task). The commands use sui client ptb; append --dry-run to print the effects without signing. A full run (identity, deposit, mint, one task) costs about 0.1 SUI plus the USDC call cost; most of the SUI is the task prepay, and what the task does not use comes back when you close it.

export PKG=0x57eee22c2765db36ebe7ab3b99678de8228959da47363224043fa78746983c1c
export CONFIG=0x2790f87aa70112d1f435e5777d0e5ddab07d4b89361a3d08653f45d5d298ebaa
export USDC=0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::USDC
sui client switch --env mainnet
sui client chain-identifier     # must print Hex: 35834a8a

If your client has no mainnet env yet:

sui client new-env --alias mainnet --rpc https://fullnode.mainnet.sui.io:443

The 1.81 CLI talks gRPC to that URL, so the official fullnode works; a JSON-RPC-only provider (for example sui-rpc.publicnode.com) fails with grpc-status header missing.

Create your identity

agent::new creates a shared Aion (your identity) and sends the OwnerCap to you.

sui client ptb --move-call $PKG::agent::new --gas-budget 50000000

Find the ids afterwards (<digest> is printed by the command):

sui client tx-block <digest> --json | jq -r '.objectChanges[] | select(.type=="created") | "\(.objectType) \(.objectId)"'
# 0x670f…9403::agent::Aion     <- AION
# 0x670f…9403::agent::OwnerCap <- CAP
export AION=0x…   CAP=0x…

The AgentCreated event carries the same agent_id (= AION). Keep CAP private: it can withdraw your balance. The AION id is public and goes into every call.

Deposit USDC

Anyone can fund an identity; the account is created on the first deposit (account::deposit). List your USDC coin objects and pick one. 5000000 is 5 USDC; a deposit that would push the balance above 50 USDC aborts (106).

sui client balance --with-coins     # USDC section: coinId, balance
export COIN=0x…                     # a USDC coin with enough balance
sui client ptb \
  --split-coins @$COIN "[5000000]" --assign c \
  --move-call $PKG::account::deposit "<$USDC>" @$CONFIG @$AION c.0 \
  --gas-budget 50000000
sui client tx-block <digest> --json | jq '.events[] | select(.type|endswith("::account::Deposited")) | .parsedJson'

Commit to the request

The ticket commits to the exact request, so the tool cannot be made to run anything else on your escrow. Download daimon_llm_commitment.py (standalone, Python 3 standard library only) and run it:

python3 daimon_llm_commitment.py $AION qwen/qwen3-235b-a22b-2507 --prompt "Say hello in five words."          # hex
python3 daimon_llm_commitment.py $AION qwen/qwen3-235b-a22b-2507 --messages @messages.json                     # chat history
python3 daimon_llm_commitment.py $AION qwen/qwen3-235b-a22b-2507 --prompt "Say hello in five words." --ptb     # [b0,b1,…]

messages.json is [{"role":"system","content":"…"},{"role":"user","content":"…"}] (roles system, user, assistant; at least one user; at most 64 messages; no other keys). The model string and the prompt or messages you commit must be byte-identical to what you give the task later. thread_id is not committed.

Preimage: sha256("daimon.llm.ticket.v1" ‖ 0x00 ‖ agent_id(32) ‖ u32le len(model) ‖ model ‖ u32le len(body) ‖ body), where body is the RFC 8785 JSON of {"messages":[…]}. To check your copy of the script, this reference vector must print the commitment on the second line:

python3 daimon_llm_commitment.py 0x000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f z-ai/glm-5.3-flash --prompt hello
# 4398dcef4f5a47fa80aa640f26586f303cbcd5f5124ba4106ec2f316dc7d620b

Mint the ticket

llm::mint_ticket<USDC>(config, aion, owner_cap, max_cost, commitment: Option<vector<u8>>, kind: u8, clock): u64. kind is 0u8. max_cost is the most this call may cost (micro-USDC, at most 1,000,000); it is moved out of your account into escrow now.

export MAX_COST=100000    # 0.10 USDC
export BYTES=$(python3 daimon_llm_commitment.py $AION qwen/qwen3-235b-a22b-2507 --prompt "Say hello in five words." --ptb)
sui client ptb \
  --make-move-vec "<u8>" "$BYTES" --assign cm \
  --move-call 0x1::option::some "<vector<u8>>" cm --assign opt \
  --move-call $PKG::llm::mint_ticket "<$USDC>" @$CONFIG @$AION @$CAP $MAX_COST opt 0u8 @0x6 \
  --gas-budget 50000000

The return value (the sequence number) is not printed by the CLI; read it from the TicketMinted event:

sui client tx-block <digest> --json \
  | jq '.events[] | select(.type|endswith("::events::TicketMinted")) | .parsedJson'
# {"agent_id":"0x…","seq":"0","max_cost":"100000","kind":0,"commitment":"<base64>","minted_ms":"…","expires_ms":"…"}
export SEQ=0

seqis per identity and never reused. Wait a few seconds for Daimon's indexer to see the mint; the tool answers "ticket not found yet" until then.

Run the task

One-time setup of the Talus Protocol CLI, nexus (macOS arm64 binary shown). The rc.final CLI speaks gRPC, so use a gRPC-capable fullnode URL such as the one below. The manifest is the rc.final mainnet one.

gh release download v2.0.0-rc.final -R Talus-Network/nexus-sdk -p "nexus-cli-2.0.0-rc.final-aarch64-apple-darwin.tar.gz"
curl -O https://storage.googleapis.com/production-talus-sui-objects/v2.0.0-rc.final/objects.mainnet.toml
nexus conf set --sui.rpc-url https://fullnode.mainnet.sui.io --nexus.objects ./objects.mainnet.toml --sui.pk <base64 key>
nexus gas deposit --amount 100000000 --sui-gas-coin <sui coin id>   # moves SUI into the address balance used for task prepay

One run used about 0.033 SUI of Talus Protocol fees (gas_fee_mist in the execution events), so 0.1 SUI of prepay is enough.

Daimon already published a one-vertex DAG. A published DAG is an immutable object, so anyone can schedule tasks on it:

export DAG=0xd688f096124d578039c307b1fcc1d91a6ec51b838eb23596bff77ae85623a840

Or publish your own copy (about 0.016 SUI) from daimon-llm-mainnet.json:

nexus dag validate --path daimon-llm-mainnet.json
nexus dag publish --path daimon-llm-mainnet.json --sui-gas-coin <sui coin id>
export DAG=0x…   # the DAG id it prints

Schedule one occurrence. Input is keyed by vertex name; prompt is the same text you committed:

nexus task schedule --dag-id $DAG --entry-group default \
  --input-json "{\"llm\":{\"identity_id\":\"$AION\",\"ticket_seq\":$SEQ,\"model\":\"qwen/qwen3-235b-a22b-2507\",\"prompt\":\"Say hello in five words.\"}}" \
  --prepay-amount-mist 100000000 --occurrence-budget-mist 100000000 \
  --refund-recipient <your address> --now --sui-gas-coin <sui coin id>

For chat history use "messages":[…] instead of "prompt" (give exactly one; the DAG defaults the other port to null). Pass --sui-gas-coin on every write, or the CLI also tries the empty address balance for gas.

Read the answer

nexus execution inspect --task-id <task id> --occurrence-id 0          # "Execution finished successfully"
nexus task close --task-id <task id> --sui-gas-coin <sui coin id>       # refunds unused prepay

The text view only says which variant ended (ok or err). The output ports are in the --json records, hex-encoded:

nexus execution inspect --task-id <task id> --occurrence-id 0 --json \
  | jq -r '.[] | select(.event_type=="EndStateReachedEvent") | .event.variant_ports_to_data.contents[]
           | "\(.key.name) \(.value.One.value.InlineData_hex)"' \
  | while read k h; do echo "$k = $(printf %s "$h" | xxd -r -p)"; done

The tool's output is one of:

{"ok":{"completion":"…","cost_micro":4321,"cached_tokens":0,"ticket_seq":0}}
{"err":{"reason":"…"}}

cost_micro is what was settled from the ticket (markup included); 0 means the call was refunded in full. Confirm on chain: TicketSettled (escrow, actual) appears in the settle transaction a few seconds after the completion, and your account balance is back up by max_cost - actual. Read the balance from the identity's dynamic fields (sui client ptb --dev-inspect did not print return values in 1.81):

curl -s https://graphql.mainnet.sui.io/graphql -H 'content-type: application/json' -d \
 '{"query":"{object(address:\"'$AION'\"){dynamicFields{nodes{name{type{repr}} value{... on MoveValue{json}}}}}}"}' \
 | jq '.data.object.dynamicFields.nodes[] | .value.json'
# AccountKey<USDC>: {"balance":"…","spent_today":"…",…}   LlmKey: {"escrowed_total":"0","open_tickets":"0",…}

The refund of unused prepay arrives as a SUI coin object, not in the address balance, so a second task needs a new nexus gas deposit. Scheduling the identical input again returns the stored answer from the operator (no second upstream call, no second charge, the ticket is not touched), which is safe for retries.

Mainnet proof run (2026-10-13). Ticket seq 0 with max_cost 200000 (0.2 USDC): actual 545 micro-USDC settled, 199,455 returned, about 29 s from task schedule to the finished execution.

Refunds, reclaim, withdraw

  • Normal case: settle pays actual to the treasury and returns the rest to your account. Nothing to do.
  • Upstream error or refusal before spend: settled at 0, full refund; the ticket is consumed. Mint a new one to retry.
  • Transient errors (retry in the message): the ticket stays reserved; retry the identical request with the same ticket.
  • Never settled (tool or operator outage): after the TTL (expires_ms from the event) anyone can return the whole escrow to your account. It never goes to the caller.
sui client ptb --move-call $PKG::llm::reclaim_expired "<$USDC>" @$AION $SEQ @0x6 --gas-budget 50000000

Before expiry this aborts 207; for an unknown or already-closed seq it aborts 205. Reclaim works while the Config is paused and on a retired identity.

Withdraw your balance (owner only; never touches escrowed funds):

sui client ptb --move-call $PKG::account::withdraw "<$USDC>" @$AION @$CAP 1000000 @<recipient address> --gas-budget 50000000

Read-only helpers: llm::ticket_info<USDC>(&Aion, seq), llm::has_ticket(&Aion, seq), llm::llm_state(&Aion).

Troubleshooting

Tool err.reason strings

ReasonMeaning / fix
request does not match the ticket commitmentModel, prompt or messages differ from what you committed. Recompute with the script from the exact strings.
this ticket is already bound to a different requestThe ticket was already reserved for another request. Mint a new ticket.
ticket not found yet: the mint is not indexed, retry in a few secondsMint not indexed yet, or wrong identity_id/ticket_seq. Retry; check the event.
ticket already settled / ticket was reclaimed by its owner / ticket is closedTicket is spent. Mint a new one.
ticket is expired or about to expire; mint a new oneLess than 6 h of TTL left, or expired. Mint a new one (reclaim the old after expiry).
open vouchers are not supported by this tool version; mint a committed ticketYou minted kind 1. Use kind 0u8.
model must be a paid OpenRouter slug (letters, digits and / - _ . : ~, no :free)Fix the model string.
proxy 422 model_unbounded_reasoning / model_unbounded_fees / model_unpriced: … (nothing charged)Escrow does not serve that model (it may reason unasked, searches the web on its own, or has a fee the cap cannot bound). Refunded in full; mint a new ticket for a model on /docs/nexus/models.
proxy 400 cap_too_low: … (nothing charged)max_cost cannot cover even one output token of that model. Refunded in full; mint a new ticket with a higher max_cost.
proxy 503 escrow_daily_cap / escrow_ledger_unavailable: … (nothing charged)Daimon's daily escrow spend limit is reached (until 00:00 UTC), or its spend ledger cannot be read. Refunded in full; mint a new ticket later.
give either prompt or messages, not both / give prompt or messages / prompt must not be emptyInput shape.
messages must hold 1 to 64 entries / message role must be system, user or assistant / messages needs at least one user messageInput shape.
request is larger than 200000 bytesShorten the request.
identity_id must be 0x followed by 1 to 64 hex characters / identity_id is not valid hexPass your Daimon identity id.
thread_id must be 1-128 characters of letters, digits and _ : . -Fix or drop thread_id.
request_in_flight: the same request is already being served, retry shortlyAnother leader holds the same request. Wait and retry.
… (nothing charged) / … (charged N micro-USDC for the spend upstream)Upstream failed. First: refunded in full. Second: the upstream billed that amount; it is settled and the rest refunded.
… the ticket stays reserved, retry in a few minutes / … retryTransient. Retry the identical request.
reserve: temporarily unavailable (settle_backlog / settle_unavailable), retry later / reserve: temporarily unavailable, retry laterOperator side: settles are behind or failing, or this identity already holds its share of unsettled calls (about 5 USDC at a time). Nothing was reserved or charged; retry the identical request later.
this ticket was already refunded without an answer / … (this ticket was already settled without an answer)The call ended without a stored answer. Mint a new ticket.
the llm tool is not configured on this host / … misconfigured (issuer rejected the service token)Operator side. Report it.

Move aborts

Read as MoveAbort(… module: "llm" or "account" …, code) in the failed transaction or dry-run.

CodeModuleMeaning
100accountOwnerCap is not for this identity
101 / 201account / llmConfig paused (mint and settle are blocked; reclaim is not)
102accountWrong coin type; only the USDC type above is accepted
103accountZero amount (max_cost or deposit is 0)
104accountNo account yet; deposit first
105accountAccount bound to a different Config
106accountDeposit would exceed the 50 USDC account cap
107accountInsufficient balance for the escrow
108accountmax_cost above the per-ticket limit (1 USDC, or your own lower max_charge)
109accountDay limit (5 USDC, or your own lower limit) reached; escrow counts until settled
115accountIdentity is retired
202llmBad kind (use 0)
203llmCommitment must be exactly 32 bytes for kind 0
204llmOpen voucher too large (not relevant for kind 0)
205llmTicket does not exist (wrong seq, or already settled or reclaimed)
207llmreclaim_expired before expires_ms
208llmTicket belongs to another Config

Other

  • sui client fails with -32601: your client points at a JSON-RPC URL that was switched off; use https://fullnode.mainnet.sui.io:443 (the 1.81 CLI uses gRPC). grpc-status header missing means the URL only speaks JSON-RPC.
  • Dry-run any sui client ptb command first by appending --dry-run.
  • Stale Talus Protocol manifest or unknown tool: nexus tool inspect --tool-fqn tech.daimon.llm@1 must show the tool as available.