# Invoking from Solidity

> Call agents from smart contracts and receive results through callbacks.

This guide shows how to invoke Somnia Agents from smart contracts. Your contracts can call agents and receive responses via callbacks.

## Generate code from the explorer

The quickest way to start is from an agent's page in the [Somnia Explorer](https://explorer.somnia.network/agents):

1. Open [explorer.somnia.network/agents](https://explorer.somnia.network/agents) (mainnet) or [explorer.testnet.somnia.network/agents](https://explorer.testnet.somnia.network/agents) (testnet).
2. Select an agent from the list.
3. Find the method you want. Each method shows its inputs and outputs and a generated Solidity snippet.
4. Copy the snippet into your project.

The generated snippet covers:

- Agent method encoding (the payload)
- Sizing the deposit
- The `createRequest` call
- The callback that handles the response

To invoke an agent directly from the browser instead, use the standalone Agent Explorer app at [https://agents.somnia.network](https://agents.somnia.network) (mainnet) or [https://agents.testnet.somnia.network](https://agents.testnet.somnia.network) (testnet).

## How it works

| Step | From → to | Call or event |
| --- | --- | --- |
| 1 | Your contract → `SomniaAgents` | `createRequest{value: deposit}(agentId, callback, selector, payload)` |
| 2 | `SomniaAgents` → validators | `RequestCreated(..., perAgentBudget, subcommittee)` event |
| 3 | Validators → `SomniaAgents` | `submitResponse(result, receipt, success, executionCost)` |
| 4 | `SomniaAgents` → validators | Gas refund from the operations reserve |
| 5 | `SomniaAgents` | Reaches consensus |
| 6 | `SomniaAgents` → your contract | `handleResponse(requestId, responses[], status, details)` |
| 7 | `SomniaAgents` → committee | `deposit(subcommittee, [median × subSize])` |
| 8 | `SomniaAgents` → your contract | Rebate of unused budget |
| 9 | `SomniaAgents` | `RequestFinalized` event |

In words:

1. Your contract calls `createRequest()` with the agent ID, encoded payload, and a deposit.
2. The platform splits the deposit into an **operations reserve** (`minPerAgentDeposit × subSize`, which funds gas refunds, the callback and upkeep) and an **agent reward pot** (the rest). `perAgentBudget = rewardPot / subSize` is emitted in `RequestCreated` so runners know their cap.
3. Elected validators execute the agent and submit responses. Each submission is gas-refunded from the operations reserve.
4. Once consensus is reached, the platform calls your callback, then pays every elected subcommittee member the **median** of the reported `executionCost` values via the committee.
5. Any unused funds are rebated to the requester.

See [Gas fees](./gas-fees) for the full fund flow and details on sizing `msg.value`.

## Platform interface

```solidity
enum ConsensusType { Majority, Threshold }
enum ResponseStatus {
    None,       // 0 - Default zero value (uninitialized storage)
    Pending,    // 1 - Awaiting responses
    Success,    // 2 - Consensus reached normally
    Failed,     // 3 - Validators reported failure
    TimedOut    // 4 - Request timed out
}

struct Response {
    address validator;
    bytes result;
    ResponseStatus status;
    uint256 receipt;
    uint256 timestamp;
    uint256 executionCost;
}

struct Request {
    uint256 id;
    address requester;
    address callbackAddress;
    bytes4 callbackSelector;
    address[] subcommittee;
    Response[] responses;
    uint256 responseCount;
    uint256 failureCount;
    uint256 threshold;
    uint256 createdAt;
    uint256 deadline;
    ResponseStatus status;
    ConsensusType consensusType;
    uint256 remainingBudget;   // escrow remaining at any point in the lifecycle
    uint256 perAgentBudget;    // max each elected member can claim (set at creation)
}

interface IAgentRequester {
    // Events
    event RequestCreated(uint256 indexed requestId, uint256 indexed agentId, uint256 perAgentBudget, bytes payload, address[] subcommittee);
    event RequestFinalized(uint256 indexed requestId, ResponseStatus status);
    event SubcommitteePaid(uint256 indexed requestId, uint256 totalPaid, uint256 perMember);
    event CommitteeDepositFailed(uint256 indexed requestId, uint256 attemptedAmount);

    // Request Creation (payable - sends budget upfront)
    function createRequest(
        uint256 agentId,
        address callbackAddress,
        bytes4 callbackSelector,
        bytes calldata payload
    ) external payable returns (uint256 requestId);

    function createAdvancedRequest(
        uint256 agentId,
        address callbackAddress,
        bytes4 callbackSelector,
        bytes calldata payload,
        uint256 subcommitteeSize,
        uint256 threshold,
        ConsensusType consensusType,
        uint256 timeout
    ) external payable returns (uint256 requestId);

    // Query Functions
    function getRequest(uint256 requestId) external view returns (Request memory);
    function hasRequest(uint256 requestId) external view returns (bool);

    // Deposit helpers — query the exact msg.value required
    function getRequestDeposit() external view returns (uint256);
    function getAdvancedRequestDeposit(uint256 subcommitteeSize) external view returns (uint256);
}

interface IAgentRequesterHandler {
    function handleResponse(
        uint256 requestId,
        Response[] memory responses,
        ResponseStatus status,
        Request memory details
    ) external;
}
```

**`SomniaAgents` / `AgentRequester` addresses:**

| Network | Address |
| --- | --- |
| Mainnet (chain ID `5031`) | `0x5E5205CF39E766118C01636bED000A54D93163E6` |
| Testnet (chain ID `50312`) | `0x037Bb9C718F3f7fe5eCBDB0b600D607b52706776` |

The examples below use the testnet address; swap in the mainnet address for production.

## Basic example

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

enum ConsensusType { Majority, Threshold }
enum ResponseStatus {
    None,       // 0 - Default zero value (uninitialized storage)
    Pending,    // 1 - Awaiting responses
    Success,    // 2 - Consensus reached normally
    Failed,     // 3 - Validators reported failure
    TimedOut    // 4 - Request timed out
}

struct Response {
    address validator;
    bytes result;
    ResponseStatus status;
    uint256 receipt;
    uint256 timestamp;
    uint256 executionCost;
}

struct Request {
    uint256 id;
    address requester;
    address callbackAddress;
    bytes4 callbackSelector;
    address[] subcommittee;
    Response[] responses;
    uint256 responseCount;
    uint256 failureCount;
    uint256 threshold;
    uint256 createdAt;
    uint256 deadline;
    ResponseStatus status;
    ConsensusType consensusType;
    uint256 remainingBudget;   // escrow remaining at any point in the lifecycle
    uint256 perAgentBudget;    // max each elected member can claim (set at creation)
}

interface IAgentRequester {
    function createRequest(
        uint256 agentId,
        address callbackAddress,
        bytes4 callbackSelector,
        bytes calldata payload
    ) external payable returns (uint256 requestId);

    function getRequestDeposit() external view returns (uint256);
}

interface IJsonApiAgent {
    function fetchUint(string calldata url, string calldata selector, uint8 decimals)
        external returns (uint256);
}

contract PriceOracle {
    IAgentRequester public platform =
        IAgentRequester(0x037Bb9C718F3f7fe5eCBDB0b600D607b52706776);

    uint256 public constant JSON_API_AGENT_ID = 12345678901234567890; // Replace with actual agent ID from the explorer
    uint256 public constant SUBCOMMITTEE_SIZE = 3;             // matches the platform default
    uint256 public constant JSON_FETCH_COST_PER_AGENT = 0.0003 ether; // see Gas Fees → Current Per-Agent Prices

    uint256 public latestPrice;
    mapping(uint256 => bool) public pendingRequests;

    event PriceRequested(uint256 indexed requestId);
    event PriceReceived(uint256 indexed requestId, uint256 price);

    function requestBitcoinPrice() external payable returns (uint256 requestId) {
        // Encode the agent function call
        bytes memory payload = abi.encodeWithSelector(
            IJsonApiAgent.fetchUint.selector,
            "https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd",
            "bitcoin.usd",
            uint8(8)
        );

        // Safe deposit: contract floor + per-agent execution reward.
        // Floor only would satisfy the contract but runners would skip the
        // request because perAgentBudget < scheduledExecutionCost.
        uint256 reserve = platform.getRequestDeposit();                       // = minPerAgentDeposit × subSize
        uint256 reward  = JSON_FETCH_COST_PER_AGENT * SUBCOMMITTEE_SIZE;
        uint256 deposit = reserve + reward;
        requestId = platform.createRequest{value: deposit}(
            JSON_API_AGENT_ID,
            address(this),
            this.handleResponse.selector,
            payload
        );
        pendingRequests[requestId] = true;

        emit PriceRequested(requestId);
    }

    // Callback function - called by the platform when consensus is reached
    function handleResponse(
        uint256 requestId,
        Response[] memory responses,
        ResponseStatus status,
        Request memory details
    ) external {
        require(msg.sender == address(platform), "Only platform can call");
        require(pendingRequests[requestId], "Unknown request");

        delete pendingRequests[requestId];

        if (status == ResponseStatus.Success && responses.length > 0) {
            latestPrice = abi.decode(responses[0].result, (uint256));
            emit PriceReceived(requestId, latestPrice);
        }
    }

    // Allow receiving rebates
    receive() external payable {}
}
```

## Callback function signature

Your callback function must match this signature:

```solidity
function handleResponse(
    uint256 requestId,
    Response[] memory responses,
    ResponseStatus status,
    Request memory details
) external;
```

- `requestId`: The ID returned from `createRequest()`
- `responses`: Array of `Response` structs from validators (each contains `.result` bytes, `.status`, `.validator`, etc.)
- `status`: The overall request status — one of `Success` (2), `Failed` (3), or `TimedOut` (4)
- `details`: The full `Request` struct with metadata (subcommittee, threshold, deadline, etc.)

You can name the function anything, but the parameter types must match. The selector you pass to `createRequest` must match your function.

### Response status

| Status | Value | Meaning |
| --- | --- | --- |
| `None` | 0 | Default zero value (uninitialized storage) |
| `Pending` | 1 | Awaiting responses |
| `Success` | 2 | Validators reached consensus |
| `Failed` | 3 | Validators reported failure (success became impossible) |
| `TimedOut` | 4 | Request timed out before consensus was reached |

## Decoding responses

The `responses` array contains `Response` structs from validators. Each response has a `.result` field with ABI-encoded bytes and a `.status` field. The callback receives **all** responses submitted up to finalization. For `Majority` consensus, at least `threshold` responses will have identical `.result` bytes (the consensus value). For `Threshold` consensus, results may differ.

```solidity
// Decode from the first successful response
if (status == ResponseStatus.Success && responses.length > 0) {
    // Single uint256
    uint256 value = abi.decode(responses[0].result, (uint256));

    // Single string
    string memory text = abi.decode(responses[0].result, (string));

    // Multiple values
    (uint256 value, string memory message) = abi.decode(responses[0].result, (uint256, string));
}

// Iterate all responses (useful for Threshold consensus)
for (uint256 i = 0; i < responses.length; i++) {
    if (responses[i].status == ResponseStatus.Success) {
        uint256 value = abi.decode(responses[i].result, (uint256));
        // Process each validator's result...
    }
}
```

## Multiple callbacks

Use different callbacks for different response types:

```solidity
contract MultiAgentConsumer {
    IAgentRequester public platform =
        IAgentRequester(0x037Bb9C718F3f7fe5eCBDB0b600D607b52706776);

    // Request with uint256 response
    function requestPrice() external payable {
        bytes memory payload = abi.encodeWithSelector(...);

        platform.createRequest{value: msg.value}(
            PRICE_AGENT_ID,
            address(this),
            this.handlePriceResponse.selector,
            payload
        );
    }

    // Request with string response
    function requestText() external payable {
        bytes memory payload = abi.encodeWithSelector(...);

        platform.createRequest{value: msg.value}(
            TEXT_AGENT_ID,
            address(this),
            this.handleTextResponse.selector,
            payload
        );
    }

    function handlePriceResponse(
        uint256 requestId,
        Response[] memory responses,
        ResponseStatus status,
        Request memory details
    ) external {
        require(msg.sender == address(platform), "Only platform");
        if (status == ResponseStatus.Success && responses.length > 0) {
            uint256 price = abi.decode(responses[0].result, (uint256));
            // Process price...
        }
    }

    function handleTextResponse(
        uint256 requestId,
        Response[] memory responses,
        ResponseStatus status,
        Request memory details
    ) external {
        require(msg.sender == address(platform), "Only platform");
        if (status == ResponseStatus.Success && responses.length > 0) {
            string memory text = abi.decode(responses[0].result, (string));
            // Process text...
        }
    }

    receive() external payable {}
}
```

## Error handling

Handle all response statuses gracefully:

```solidity
function handleResponse(
    uint256 requestId,
    Response[] memory responses,
    ResponseStatus status,
    Request memory details
) external {
    require(msg.sender == address(platform), "Only platform");

    if (status == ResponseStatus.Failed) {
        emit AgentFailed(requestId);
        // Validators reported execution failure
        return;
    }

    if (status == ResponseStatus.TimedOut) {
        emit AgentTimedOut(requestId);
        // Request timed out - maybe retry
        return;
    }

    // Success
    if (responses.length == 0) {
        emit EmptyResponse(requestId);
        return;
    }

    // Decode and process response...
}
```

## Security considerations

1. **Always verify the caller**: Check that `msg.sender` is the platform contract
2. **Track pending requests**: Use a mapping to validate incoming callbacks
3. **Handle all statuses**: Check `status` — requests can succeed, fail, or time out
4. **Validate responses length**: Check `responses.length` before decoding
5. **Accept rebates**: Implement `receive()` so your contract can receive rebated funds

## Next steps

- [Gas fees](./gas-fees): understand costs.
- [Custom consensus](./custom-consensus): Threshold consensus and `createAdvancedRequest`.

---

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.
