Guides
- Quickstart
- Authentication and keys
- Key protection
- Models, aliases and fallbacks
- Automatic model choice (silicon/auto)
- Chat completions
- Streaming
- Structured outputs and tools
- Logprobs
- Vision
- Responses, Claude and Ollama
- Embeddings, rerank, audio, images
- Files and batches
- Documents and search
- LoRA adapters
- Project tags and costs
- Prefix caching
- Headers, rate limits and spend caps
- Errors
- Privacy and retention
- Verifiable receipts
- Data residency
- Pricing
- Billing
- Pay per request (x402)
- On-chain oracle
- Agent credit (backed compute lines)
- Status
- SDKs
- Swift SDK
- Integrations
- Run a node
- Architecture
- Changelog
API reference
Guide
On-chain oracle
A smart contract asks a question, a Silicon Network model answers it, and the answer comes back on chain with proof of which machine produced it.
Not deployed yet. The contract and the worker that answers are built and tested, but no oracle contract is live on any chain, so there is no address to call. This page describes how it will work; the address will be published here once it is deployed.
What it does
The oracle is a contract on Robinhood Chain (chain id 4663, an Arbitrum Orbit chain with gas in ETH). Your contract calls it with a model, a prompt and a token limit, and pays in ETH. An oracle worker run by Silicon Network picks the request up, runs it through the same API every other caller uses, and posts the answer back to the oracle contract, which calls your contract with it.
Every answer carries a receipt bundle: the gateway's signed verifiable receipt for that inference (which node served it, its operator, region and attestation) together with the exact request and response bytes the receipt hashes. Anyone can check it against the gateway's published keys.
How a request flows
| Step | Who | What happens |
|---|---|---|
| 1. request | your contract | Calls request(model, prompt, maxTokens, callback, callbackGasLimit) with at least quote(model, maxTokens, promptBytes, callbackGasLimit) in ETH. The callback is your contract itself or none. The oracle emits Requested and holds the payment. While no oracle signer is active the request is refused (NoActiveSigner), since nobody could answer it. |
| 2. inference | oracle worker | Reads the event, sends the prompt to the gateway asking for a receipt, and checks the receipt before posting anything. |
| 3. fulfill | oracle worker | Calls fulfill with the answer and the receipt bundle. The oracle checks that the signer is an active oracle signer and that the answer is for exactly this request. |
| 4. callback | oracle contract | Calls onSiliconAnswer on your contract with the callback gas limit your request chose and paid for. If your callback reverts, the answer still stands and stays in the Fulfilled event. |
| refund | anyone | A request still unanswered after its deadline (the oracle's timeout, one hour by default) can be refunded with refund(id): the whole payment is credited back to the requester. |
| reject | oracle worker | A request that can never be answered (a prompt URI that does not match its hash, a prompt too large to answer on chain, a prompt host too slow to answer) is rejected before its deadline: a reject fee (the model's base price) is kept and the rest credited back at once; onSiliconRejected tells your contract, with at most 100,000 gas. After the deadline no rejection is possible, only the full refund. |
Credited ETH (refunds, rejections minus the reject fee, and anything paid above the price) is withdrawn with claim(to) by the address that is owed it. When your contract is the requester, give it a function that calls claim.
From a contract
Implement the two callbacks, check that msg.sender is the oracle and that the id is an open question your contract asked, and send the quoted price with the request. The oracle only calls back the contract that made the request, so the callback is always address(this); choose its gas (20,000 to 2,000,000), which the price includes. Keep the callback cheap: store a hash or a short prefix and read the full answer from the event.
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.24;
interface ISiliconOracle {
function quote(string calldata model, uint32 maxTokens, uint32 promptBytes, uint32 callbackGasLimit)
external view returns (uint256);
function request(
string calldata model,
string calldata prompt,
uint32 maxTokens,
address callback,
uint32 callbackGasLimit
) external payable returns (uint256 id);
function claim(address payable to) external;
}
interface ISiliconOracleConsumer {
function onSiliconAnswer(uint256 id, bytes calldata answer, bytes32 answerHash, string calldata answerUri) external;
function onSiliconRejected(uint256 id, string calldata reason) external;
}
contract AskOnce is ISiliconOracleConsumer {
uint32 constant CALLBACK_GAS = 200_000;
ISiliconOracle public immutable oracle;
mapping(uint256 => bool) public open;
mapping(uint256 => bytes32) public answerHashes;
constructor(ISiliconOracle oracle_) {
oracle = oracle_;
}
function ask(string calldata question) external payable returns (uint256 id) {
// send at least oracle.quote("llama-3.2-3b", 128, uint32(bytes(question).length), CALLBACK_GAS);
// the callback must be this contract: the oracle only calls back the requester
id = oracle.request{value: msg.value}("llama-3.2-3b", question, 128, address(this), CALLBACK_GAS);
open[id] = true;
}
function onSiliconAnswer(uint256 id, bytes calldata, bytes32 answerHash, string calldata) external {
require(msg.sender == address(oracle), "not the oracle");
require(open[id], "not a question this contract asked");
open[id] = false;
answerHashes[id] = answerHash; // the full answer is in the oracle's Fulfilled event
}
function onSiliconRejected(uint256 id, string calldata) external {
require(msg.sender == address(oracle), "not the oracle");
require(open[id], "not a question this contract asked");
open[id] = false;
}
}A fuller example, with state per request and a credit withdrawal, is ExampleConsumer.sol in the repository, next to the oracle itself.
Without a callback
Pass address(0) as the callback and 0 as its gas, and read the answer from the oracle's Fulfilled(id, signer, answerHash, answer, answerUri, receipt) event instead, off chain. statusOf(id) tells you where a request is: 1 pending, 2 fulfilled, 3 rejected, 4 refunded.
Long prompts
A prompt too large for calldata can be kept off chain: requestWithUri(model, promptHash, promptUri, promptBytes, maxTokens, callback, callbackGasLimit) with an https:// or ipfs:// URI, the sha256 of the prompt bytes and their length, which the price counts. The worker fetches it (uncompressed) and answers only if it is no longer than declared and the hash matches; otherwise the request is rejected, the reject fee kept and the rest credited back.
What it costs
Requests are paid in ETH on Robinhood Chain. The price covers the gas the oracle spends posting the answer and the inference itself, and is set per model by the oracle's owner: a base price per request, plus a price per token of maxTokens, per prompt byte, and per unit of the callback gas you ask for. Read it on chain with quote(model, maxTokens, promptBytes, callbackGasLimit) right before you request; a model the oracle does not offer reverts. The price of a request is fixed when it is made. A rejected request keeps the base price as its reject fee; a request that times out is refunded in full.
The oracle has no connection to SIL. Payment is in ETH only, for the gas and the inference.
Checking an answer
The receipt is carried on chain, not verified there: the gateway signs with ed25519, which the EVM cannot check cheaply. What the contract does check is that the answer came from an active oracle signer and is for exactly the request that was made, not that the answer is right. If your contract moves value on an answer, verify the receipt off chain first, or hold the value behind a dispute window. To check where an answer came from, take the receipt bytes of the Fulfilled event. They are this JSON:
{
"v": 1,
"kind": "silicon.oracle.receipt",
"chain_id": 4663,
"oracle": "0x<the oracle contract>",
"request_id": "1",
"request_tx": "0x<the transaction that made the request>",
"gateway_request_id": "<Silicon-Request-Id>",
"request": "<base64 of the exact JSON body sent to the gateway>",
"response": "<base64 of the exact JSON body the gateway answered>",
"receipt": { "v": 1, "node": { ... } | null, "gateway": { ... } }
}Then:
- Verify
receiptwith the SDKs' receipt verifier againstGET https://api.siliconnetwork.io/v1/receipts/keys, passingrequestandresponse(decoded) as the request and response bytes. See Verifiable receipts. - Check that
requestis the question that was asked: it is canonical JSON with the event's model, prompt and maxTokens, temperature 0, for example{"max_tokens":128,"messages":[{"content":"<prompt>","role":"user"}],"model":"llama-3.2-3b","stream":false,"temperature":0}. - Check that the answer on chain is
response'schoices[0].message.content. - Read the machine from the gateway statement's
route(node id, operator, region, attestation) and, when present, the node's own signed receipt.
Receipts are software attestation, as everywhere on the network: the gateway's statement is the platform's, the node's is its operator's. Neither is a hardware root of trust.
Who you trust: the oracle's owner chooses its signers, and a signer could post any bytes as an answer. A signer the owner adds later becomes active only two days after, and never for a request made before that, so adding one cannot reach the payments of requests already made. signers(), isSigner(address) and signerActiveAt(address) show the signers, pending ones included.
Limits
| What | Limit |
|---|---|
| Inline prompt | Set by the owner (8 KB at deployment by default, 64 KB at most) |
| Prompt by URI | https:// or ipfs://, public addresses only, uncompressed, 64 KB and at most the size declared |
| maxTokens | Per model, set by the owner |
| Deadline | The oracle's timeout after the request (one hour by default) |
| Callback gas | Chosen per request, 20,000 to 2,000,000, and priced; at most 100,000 for a rejection |
| Answers | One per request, at most once; inline in the transaction |
The oracle can be paused by its owner, which stops new requests and answers. Refunds and claims keep working while paused, so ETH held for a request always comes back.