Somnia Agents / Invoking agents
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:
- Open somnia-platform-explorer.vercel.app/agents and pick the network with the chain selector.
- Select an agent from the list.
- Find the method you want. Each method shows its inputs and outputs and a generated Solidity snippet.
- Copy the snippet into your project.
The generated snippet covers:
- Agent method encoding (the payload)
- Sizing the deposit
- The
createRequestcall - 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 (mainnet) or 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:
- Your contract calls
createRequest()with the agent ID, encoded payload, and a deposit. - 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 / subSizeis emitted inRequestCreatedso runners know their cap. - Elected validators execute the agent and submit responses. Each submission is gas-refunded from the operations reserve.
- Once consensus is reached, the platform calls your callback, then pays every elected subcommittee member the median of the reported
executionCostvalues via the committee. - Any unused funds are rebated to the requester.
See Gas fees for the full fund flow and details on sizing msg.value.
Platform interface
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
// 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:
function handleResponse(
uint256 requestId,
Response[] memory responses,
ResponseStatus status,
Request memory details
) external;
requestId: The ID returned fromcreateRequest()responses: Array ofResponsestructs from validators (each contains.resultbytes,.status,.validator, etc.)status: The overall request status — one ofSuccess(2),Failed(3), orTimedOut(4)details: The fullRequeststruct 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.
// 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:
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:
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
- Always verify the caller: Check that
msg.senderis the platform contract - Track pending requests: Use a mapping to validate incoming callbacks
- Handle all statuses: Check
status— requests can succeed, fail, or time out - Validate responses length: Check
responses.lengthbefore decoding - Accept rebates: Implement
receive()so your contract can receive rebated funds
Next steps
- Gas fees: understand costs.
- Custom consensus: Threshold consensus and
createAdvancedRequest.