Gas Fees

Invoking Somnia Agents requires payment in SOMI (mainnet) or STT (testnet). This page explains how the deposit you send is split, where every wei ends up, and what to send for msg.value. Amounts in the tables below are quoted in whole tokens — read them as SOMI on mainnet or STT on testnet; the split and rebate logic is identical on both networks.

TL;DR

  • getRequestDeposit() returns the operations-reserve floor (gas refunds, callback gas, finalisation overhead). It is not the deposit you should send in practice — pay it only and your request will time out.

  • Send msg.value = floor + (per_agent_price × subcommitteeSize), where per_agent_price matches the agent type (table below). The contract takes no fee; whatever isn't spent on operations or paid to agents is rebated.

  • Today, runners have fixed per-type prices baked into their software (see Current Per-Agent Prices). If you don't pay at least subcommitteeSize × per_agent_price on top of the floor, runners will skip your request and it will time out.

Two-Pot Model

When you call createRequest{value: deposit}, the contract immediately splits the deposit into two virtual pots — both are held in the same remainingBudget, but they have separate purposes:

Pot
Size
Funds

Operations reserve

minPerAgentDeposit × subcommitteeSize

Per-runner gas refunds, callback gas, finalisation overhead, keeper refunds for timed-out requests

Agent reward pot

msg.value − reserve

Median execution-cost payment to every elected subcommittee member, routed via the committee contract

The reserve is the floor the contract enforces:

require(msg.value >= minPerAgentDeposit * subcommitteeSize, InsufficientDeposit(...));

If you deposit exactly the minimum, perAgentBudget = 0 — and since agents watching RequestCreated see that cap up front, rational runners will skip the request entirely. The contract won't reject your call, but the request will almost certainly sit idle and time out. Deposit more than the floor if you want agents to actually do the work.

The per-agent share of the reward pot is computed once at request creation, stored on the Request, and emitted in RequestCreated:

perAgentBudget = (msg.value − minPerAgentDeposit × subcommitteeSize) / subcommitteeSize

perAgentBudget is the cap on what any single runner can claim — a runner's self-reported executionCost is clamped to perAgentBudget at submission.

Current Per-Agent Prices

Runners today use fixed per-type prices in their software (no on-chain price discovery yet — that will change). If your perAgentBudget is below the price for the agent type you're invoking, runners will see the RequestCreated event, decide it's not worth their compute, and skip the request — it will sit idle and time out.

Agent Type
Per-Agent Price
Notes

JSON API Request (json-fetch)

0.03 SOMI

Cheapest — single HTTP call, minimal compute

LLM Inference (llm-inference)

0.07 SOMI

GPU-backed model, dominant cost is token throughput

LLM Parse Website (llm-parse-website)

0.10 SOMI

LLM inference plus a browser session and page render

To get your request actually executed, the deposit you send must cover the operations reserve plus the per-agent price multiplied by the subcommittee size:

For the default subcommitteeSize = 3 and minPerAgentDeposit = 0.01 SOMI:

Agent Type

Operations reserve

Agent reward pot

Practical msg.value

JSON API Request

0.03 SOMI

0.03 × 3 = 0.09 SOMI

0.12 SOMI

LLM Inference

0.03 SOMI

0.07 × 3 = 0.21 SOMI

0.24 SOMI

LLM Parse Website

0.03 SOMI

0.10 × 3 = 0.30 SOMI

0.33 SOMI

Sending more than the practical amount is fine — anything that isn't claimed (because runners reported a smaller executionCost, or because some weren't elected, etc.) is rebated to you.

Future: Per-type fixed pricing is a stop-gap. Runners will eventually price each request based on observed resource consumption (container-seconds, tokens in/out, egress, etc.), and the on-chain median will reflect actual cost rather than a hard-coded constant.

Flow of Funds

spinner

Lifecycle Walkthrough

1. Request Creation

The contract computes perAgentBudget = reward (in this example) and emits RequestCreated(requestId, agentId, perAgentBudget, payload, subcommittee). Off-chain runners watching the event know exactly what their per-agent cap will be.

2. Runner Responses

Each elected subcommittee member runs the agent off-chain, then calls:

The runner's reported executionCost is capped at perAgentBudget and stored on the Response. The contract meters the gas this submission consumed and refunds it to the runner from the operations reserve.

3. Consensus → Subcommittee Payment

When consensus is reached (Success) or proven impossible (Failed), the contract:

  1. Calls your callback (handleResponse).

  2. Computes perMember = median(reported executionCosts).

  3. Calls committee.deposit{value: perMember × subcommitteeSize}(elected, [perMember, ..., perMember]) — every elected member gets the same amount, regardless of whether they responded in time.

Slow geographically-distributed runners that didn't beat consensus to the punch are paid the same as the responders. This intentionally removes any incentive to rush a low-quality response.

If the committee call reverts (e.g. a misbehaving validator-bonuses contract), the budget is restored and CommitteeDepositFailed is emitted — finalisation can't be DoS'd.

If accumulated per-submission gas refunds have eaten into the operations reserve enough that perMember × subcommitteeSize > remainingBudget, the contract clamps perMember down to remainingBudget / subcommitteeSize rather than reverting. With a sensibly-tuned minPerAgentDeposit this is rare, but it does mean a request with abnormally expensive submissions can pay runners less than the reported median.

4. Rebate

Whatever's left in remainingBudget (typically the unused portion of the operations reserve) is sent back to the requester:

5. Timeout Path

If the request expires before consensus, anyone can call upkeepRequests():

  • The keeper is reimbursed from the operations reserve (intrinsic gas amortised across the batch).

  • No committee payment is made — there are no responses to derive a median from.

  • The agent reward pot is refunded to the requester along with any unused operations reserve.

Reading the Costs

After finalisation, getRequest(requestId) returns the full Request struct. The relevant fields:

Field
Meaning

remainingBudget

Always 0 on a finalised, settled request (everything has been distributed).

perAgentBudget

The per-agent cap that was set at creation. Multiply by subcommittee.length to get the agent reward pot.

responses[i].executionCost

What runner i reported (already capped at perAgentBudget).

Two events let off-chain consumers reconstruct the full distribution:

What to Send for msg.value

There are three sensible patterns:

1. Floor only — not useful in practice. The contract will accept this, but perAgentBudget = 0 and agents will skip the request; it will time out without a response.

Listed for completeness. Don't use it except to smoke-test that the request-creation call itself works.

2. Floor + per-agent price (the normal case). Use the per-type price from the Current Per-Agent Prices table; runners will only pick up the request if perAgentBudget meets it.

Sending more than the listed price is fine — agents are paid min(reportedExecutionCost, perAgentBudget), so any margin is rebated.

3. Custom subcommittee size:

Receiving Rebates

Make sure your contract can receive native tokens — rebates are pushed automatically on finalisation:

If the rebate transfer fails (e.g. recipient out of gas), the contract emits NativeTransferFailed(recipient, amount) and the funds remain in the AgentRequester. They can be recovered via off-chain coordination with the operator.

Configuration Reference

Parameter
Default
Notes

minPerAgentDeposit

0.01 SOMI / STT

Operator-configurable. Sets the operations-reserve floor per agent.

defaultSubcommitteeSize

3

Operator-configurable. createRequest uses this; createAdvancedRequest lets you override (≤ 10).

defaultThreshold

2

Operator-configurable.

defaultTimeout

15 minutes

Operator-configurable.

Best Practices

  1. Use the deposit helpers as a floor, not the total. getRequestDeposit() returns the operations-reserve floor only. Add a per-agent reward on top.

  2. Implement receive(). Without it, rebate transfers will fail (silent — emitted as NativeTransferFailed) and your funds will be stuck.

  3. Watch RequestCreated.perAgentBudget. That's the value runners see and the cap they'll be paid up to.

  4. Don't rely on a specific median policy. A single dishonest reporter can't manipulate the median, but with only 2 successful responders, the upper-median lands on the higher of the two values.

Next Steps

Last updated