# Pricing and fund flow

> How your deposit is split, where every wei ends up, and what to send as msg.value.

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

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

```solidity
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.0003 SOMI** | Cheapest tier — single HTTP call, minimal compute |
| **Price Oracle** (`price-oracle`) | **0.0003 SOMI** | Served from an in-memory price cache |
| **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 |

> **Note.** On mainnet, runners operated by third parties may still report the previous JSON API Request price (0.03 SOMI) until they upgrade. A runner skips any request whose `perAgentBudget` is below its own price, and the payout is the median reported cost, so budgeting 0.03 SOMI per validator for JSON API Request is the safe upper bound until then. Anything not paid out is rebated.

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:

```
msg.value ≥ minPerAgentDeposit × subSize          ← operations reserve (the contract floor)
          + per_agent_price     × subSize          ← what runners actually charge today
```

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.0003 × 3 = 0.0009 SOMI | **0.0309 SOMI** |
| Price Oracle | 0.03 SOMI | 0.0003 × 3 = 0.0009 SOMI | **0.0309 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

The requester's `msg.value` goes into the `AgentRequester` escrow (`remainingBudget = msg.value`), which splits it into two pots at creation:

- **Operations reserve** = `minPerAgentDeposit × subSize`
- **Agent reward pot** = `msg.value − reserve` (`perAgentBudget × subSize`)

Each pot pays out as follows:

| From | To | When |
| --- | --- | --- |
| Operations reserve | Runner gas refunds (direct transfer to `msg.sender`) | On each `submitResponse` |
| Operations reserve | Callback gas | At finalisation |
| Operations reserve | Keeper gas refund | On upkeep after a timeout |
| Agent reward pot | `ValidatorCommittee`, via `committee.deposit(...)`, then `ValidatorBonuses` (SOMI yield to validators) via `validatorOf(member)` | On consensus |
| Operations reserve | Requester (rebate) | Whatever is unused |
| Agent reward pot | Requester (rebate) | If there are no responses or the median is 0 |

## Lifecycle walkthrough

### 1. Request creation

```solidity
uint256 deposit = somniaAgents.getRequestDeposit();      // operations reserve floor
uint256 reward  = 0.05 ether;                            // what you want each agent paid (≤)
somniaAgents.createRequest{value: deposit + reward * 3}( // 3 = default subcommittee size
    agentId,
    address(this),
    this.handleResponse.selector,
    payload
);
```

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:

```solidity
somniaAgents.submitResponse(requestId, result, receipt, success, executionCost);
```

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

```solidity
function _refundRemaining(Request storage req) internal {
    uint256 rebate = req.remainingBudget;
    req.remainingBudget = 0;
    (bool success, ) = req.requester.call{value: rebate}("");
}
```

### 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).
- A `Threshold` request that already has `threshold` successful responses is finalised as `Success`, and the subcommittee is paid as usual.
- Otherwise the request times out. **No committee payment is made**, and the agent reward pot is refunded to the requester along with any unused operations reserve.

## Reading the costs

A finalised request is deleted, so `getRequest(requestId)` reverts with `RequestNotFound` once the request settles. Your callback receives the full `Request` as `details` before that. The relevant fields:

| Field | Meaning |
| --- | --- |
| `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`). |

After settlement, these events show how the request's budget was spent:

```solidity
event ResponseSubmitted(uint256 indexed requestId, address indexed validator, bool success, uint256 receipt, uint256 executionCost, uint256 gasCost);
event SubcommitteePaid(uint256 indexed requestId, uint256 totalPaid, uint256 perMember);
event RequestFinalized(uint256 indexed requestId, ResponseStatus status);
event NativeTransferFailed(address indexed recipient, uint256 amount);
```

A successful rebate to the requester emits no event: it is a plain transfer, so read its amount from the settling transaction's internal transfers (a call trace such as `debug_traceTransaction`). Only a failed rebate is logged, as `NativeTransferFailed`.

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

```solidity
uint256 deposit = somniaAgents.getRequestDeposit();
somniaAgents.createRequest{value: deposit}(agentId, ..., payload);
```

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](#current-per-agent-prices) table; runners will only pick up the request if `perAgentBudget` meets it.

```solidity
uint256 floor = somniaAgents.getRequestDeposit();
uint256 perAgent = 0.07 ether;           // LLM Inference; pick from the table
uint256 deposit  = floor + perAgent * 3; // 3 = default subcommittee size
somniaAgents.createRequest{value: deposit}(agentId, ..., payload);
```

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

**3. Custom subcommittee size:**

```solidity
uint256 floor = somniaAgents.getAdvancedRequestDeposit(4); // for subSize = 4
uint256 perAgent = 0.07 ether;
uint256 deposit  = floor + perAgent * 4;
somniaAgents.createAdvancedRequest{value: deposit}(
    agentId, ..., payload, 4, 3, ConsensusType.Threshold, 1 minutes
);
```

## Receiving rebates

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

```solidity
contract MyContract {
    receive() external payable {}
}
```

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 it, up to 10 and no more than the active committee (8 on Global mainnet, 4 on Global testnet and Tokyo today). |
| `defaultThreshold` | 2 | Operator-configurable. |
| `defaultTimeout` | 15 minutes (10 minutes on Global testnet today) | 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

- [Quickstart](./quickstart): start invoking agents.
- [Invoking from Solidity](./from-solidity): contract integration guide.
- [Custom consensus](./custom-consensus): Threshold mode for non-deterministic agents.

---

Every page is also Markdown: append `.md` to its URL, or ask for `text/markdown`. Found something wrong, missing or confusing in these docs? Tell us: `POST /api/feedback` on this site with JSON `{"message": "…", "context": "what you were doing and what happened", "page": "…"}`, or a plain-text body. `GET /api/feedback` gives the JSON Schema. The team reads every one.
