# OneSource REST API Documentation
> Reference and guides for the OneSource REST API and MCP Server: REST and MCP access to live Ethereum data. API-key subscription (Stripe), x402, or MPP payment.
This file contains all documentation content in a single document following the llmstxt.org standard.
## Allowance
Returns how much of an ERC20 token a spender is approved to transfer from an owner via eth_call on allowance(owner, spender). Use before calling transferFrom, or to audit outstanding approvals for security review.
Request
---
## Block number
Returns the latest mined Ethereum block height via eth_blockNumber against a live node. No caching - every call queries the upstream RPC fresh. Use this to sync an agent to the chain tip, to detect progress or stalls, or as a cheap liveness ping before issuing more expensive RPC calls.
Request
---
## Block by number
Returns the block header plus its transaction hashes for a given block number, tag (latest, pending, safe, finalized), or hex block number via eth_getBlockByNumber. Live from the upstream RPC node.
Request
---
## Call (simulate)
Simulates a contract read via eth_call - returns the raw ABI-encoded result without submitting a transaction. Pass 'to' (contract address) and 'data' (ABI-encoded calldata); optional 'from', 'value', and 'block' override the caller, msg.value, and block tag.
Request
---
## Chain ID
Returns the EIP-155 chain ID via eth_chainId, queried live from the upstream RPC node. Verify a backend is serving the expected chain before signing an EIP-155 transaction.
Request
---
## Contract code
Returns the deployed bytecode at an address via eth_getCode. Empty bytecode means the address is an EOA (wallet); non-empty means it's a contract. Response includes a bytecode_size field for quick classification.
Request
---
## Contract introspection
Introspects a contract via eth_call - probes name(), symbol(), decimals(), and supportsInterface() to report what token standards (ERC20, ERC721, ERC1155) the contract claims to implement. Ideal for classifying an unknown contract.
Request
---
## ENS resolve
Resolves a *.eth ENS name to its address (forward lookup) or an address to its primary ENS name (reverse lookup) via eth_call on the ENS Registry and Resolver contracts. Auto-detects direction from the input format.
Request
---
## ERC1155 balance
Fetches an account's balance of a specific ERC1155 token_id by eth_call-ing balanceOf(address, uint256). Supports any ERC1155 contract - gaming items, editions, semi-fungible assets.
Request
---
## ERC20 balance
Fetches an account's balance of a specific ERC20 token by eth_call-ing balanceOf(address). Returns the raw balance plus the token's decimals and symbol for convenience. Works for any ERC20 contract.
Request
---
## ERC20 transfers
Specialized eth_getLogs query that filters on the ERC20 Transfer(address,address,uint256) topic and optionally narrows to a token contract or a specific wallet (sender or recipient). Decodes the indexed from/to and raw value.
Request
---
## ERC721 tokens
Lists every ERC721 token_id held by an owner by combining balanceOf(owner) with tokenOfOwnerByIndex(owner, i) across a batched set of eth_call requests. Requires the contract to implement the ERC721Enumerable extension.
Request
---
## Estimate gas
Estimates the gas a transaction would consume via eth_estimateGas. Accepts the same 'to', 'data', 'from', 'value' fields as /call. A failing estimate usually means the transaction would revert - use this to pre-flight a complex interaction before paying gas.
Request
---
## Event logs
Queries contract event logs via eth_getLogs with optional filters on contract address, topic0 (event signature hash), and block range. Returns the raw logs - address, topics, data, block_number, tx_hash.
Request
---
## Live balance
Fetches the wallet's native ETH balance (eth_getBalance) and, when you pass up to 20 ERC20 contract addresses in the optional 'tokens' param, batches balanceOf, symbol(), and decimals() for those contracts. Tokens are queried, not discovered: the call finds no holdings and computes no portfolio value. Balances are raw RPC hex quantities. Fields it could not fetch are listed in 'warnings'; a field missing for that reason is unknown for this response, not unsupported by the contract.
Request
---
## Network info
Batches eth_chainId, eth_blockNumber, eth_gasPrice, and the latest block header into one round-trip, and adds the network's characteristics: layer, stack, block time, native currency, and ENS/mempool/indexed availability. Three fields govern how you read other endpoints: avg_block_time_ms (cadence runs ~12s to ~100ms, so size block ranges from it, not Ethereum priors), stale with head_age_seconds (a frozen node looks like an idle chain otherwise), and gas_price (excludes L1 data cost on L2s).
Request
---
## NFT metadata
Calls tokenURI(tokenId) (ERC721) or uri(tokenId) (ERC1155) via eth_call, resolves ipfs:// URIs through a gateway, and returns the resolved metadata JSON alongside the raw URI. Works for most compliant NFT contracts.
Request
---
## NFT owner
Returns the current owner of a specific ERC721 NFT via eth_call on ownerOf(tokenId). Reverts for unminted or burned token IDs - the error surfaces as an RPC error.
Request
---
## Nonce
Returns the number of transactions sent from this address via eth_getTransactionCount. Pass block="pending" (default) for the next-nonce an EIP-1559 or legacy transaction should use, or a confirmed tag for historical nonces. Use this to build a correctly-nonced transaction or to diagnose stuck transactions.
Request
---
## Pending block
Returns the pending block via eth_getBlockByNumber("pending"). Where a public mempool exists (ethereum, sepolia) these are transactions broadcast but not yet mined, useful for watching unconfirmed activity or front-running risk. Single-sequencer networks have no public pending pool, so the call reports roughly the next block rather than a queue. An empty result there means the concept does not apply, not that the chain is idle; check the mempool field from /api/chain/network-info.
Request
---
## Proxy implementation
Reads well-known proxy storage slots via eth_getStorageAt to detect EIP-1967, UUPS, and Transparent proxy patterns, and returns the current implementation address. Also surfaces the admin slot when present.
Request
---
## Transaction receipt
Returns the mined transaction receipt - status (success/failed), gas used, effective gas price, emitted event logs, and block inclusion data - via eth_getTransactionReceipt. Returns null if the transaction is pending or unknown to the node.
Request
---
## Storage slot
Reads a 32-byte storage slot directly from a contract's storage via eth_getStorageAt. Useful for inspecting EIP-1967 proxy implementation slots, packed variables, and storage layouts that aren't exposed through a public function.
Request
---
## Total supply
Returns the total supply of a token contract via eth_call on totalSupply(). Works for ERC20 (raw token units) and ERC721 (count of minted NFTs) contracts that implement the standard.
Request
---
## Transaction
Returns the full transaction object - from, to, value, gas, nonce, calldata, block inclusion - via eth_getTransactionByHash. Returns null if the transaction is pending eviction or unknown to the node.
Request
---
## API Reference
Every OneSource chain-RPC endpoint, one page each, generated from the OpenAPI 3.1 spec. All 25 endpoints live under `/api/chain/*` on `https://api.onesource.io` and proxy a single live RPC call. For Deepstate market data (`/deepstate/v1/*`), see [Deepstate Market Data](/deepstate/): it isn't part of this generated reference.
Each page lists parameters, response schema, status codes, and per-call price, and has a **Send** button that runs the request against a live node from the browser.
Pick an endpoint from the sidebar, or start with [`/api/chain/network-info`](./chain-network-info) to check connectivity.
## Reading a page
Alongside the standard parameter and response tables, each endpoint carries three OpenAPI vendor extensions (custom `x-` fields that sit outside the OpenAPI standard). These are what the MCP server and the x402 Bazaar index:
| Field | What it holds |
|---|---|
| `x-payment-info` | Per-call price in USDC, the `payTo` address, and which rails accept it (x402, MPP) |
| `x-keywords` | Search terms an agent can match the endpoint on |
| `x-use-cases` | Plain-language examples of when to call it |
## Networks
Every endpoint takes an optional `network` query parameter. The same call can mean different things per chain, so see [Choosing a network](../getting-started/direct-rest/first-request#choosing-a-network) for the supported values and what differs between them.
## Machine-readable
- **Endpoint catalog for agents:** [`/llms-full.txt`](pathname:///llms-full.txt)
- **OpenAPI spec:** [`https://api.onesource.io/openapi.json`](https://api.onesource.io/openapi.json)
- **Deepstate's OpenAPI spec:** [`https://api.onesource.io/deepstate/openapi.yaml`](https://api.onesource.io/deepstate/openapi.yaml)
---
## OneSource - EVM RPC for AI Agents
Pay-per-call EVM RPC for AI agents, on Ethereum mainnet, the Sepolia testnet, and Robinhood Chain (select with the network query parameter; defaults to ethereum). Every endpoint proxies a single live RPC call: wallet and token balances, blocks, transactions, receipts, event logs, ENS resolution, contract introspection, and NFT metadata. No API keys, accounts, or subscriptions required; pay per request in USDC on Base via x402, or pathUSD or USDC.e on Tempo via MPP.
---
## Deepstate Market Data
Deepstate is an on-chain central limit order book protocol on Robinhood Chain (chain id `4663`). OneSource serves its market data, order books, trades, candles, stats, maker analytics, gas cost-to-quote, and depth history, as a separate route family on the same host as the rest of the API.
- **Base URL:** `https://api.onesource.io/deepstate/v1/...`
- **OpenAPI spec:** [`/deepstate/openapi.yaml`](https://api.onesource.io/deepstate/openapi.yaml) (free, no auth)
- **Network:** Robinhood Chain only. These routes take no `network` parameter.
## Path convention
A OneSource route's first path segment names its data family and the second names that family's own version, so `/deepstate/v1/*` versions independently of `/api/chain/*` (the original chain-RPC family, which keeps its own conventions under a different first segment).
## Authenticating and paying
The same three access paths as the rest of the API work here: `Authorization: Bearer ` with an active subscription, or pay per call with x402 (USDC on Base) or MPP (Tempo). See [Getting Started](/getting-started/) to pick one.
```ts
const account = privateKeyToAccount(process.env.X402_PRIVATE_KEY as `0x${string}`);
const client = new x402Client();
registerExactEvmScheme(client, { signer: account });
const paidFetch = wrapFetchWithPayment(fetch, client);
const res = await paidFetch('https://api.onesource.io/deepstate/v1/markets');
const { markets } = await res.json();
```
See [x402 on Base](/getting-started/x402-and-mpp/x402-base) for the full client setup, including AgentCash and the MCP server, or [MPP on Tempo](/getting-started/x402-and-mpp/mpp-tempo) for the Tempo equivalent.
## Pricing
| Routes | Price per call |
|---|---|
| `/deepstate/v1/markets`, `/book/{book}`, `/trades/{book}`, `/candles/{book}`, `/stats/{book}` | $0.005 |
| `/deepstate/v1/analytics/*` (`makers`, `cost-to-quote`, `depth-history`) | $0.02 |
| `/deepstate/v1/health`, `/deepstate/openapi.yaml` | Free |
## Response shape
Deepstate routes return the payload directly, with no `{data, error, meta}` envelope: a successful call's body *is* the result. A failure is `{ "error": "", "message": "" }`. See [Errors](#errors) below.
`Cache-Control` varies by route: most are `public, max-age=5`, `/deepstate/v1/markets` is `public, max-age=300` (it's static, compiled-in configuration), and `/deepstate/v1/book/{book}` is `no-store`, since a snapshot names the exact block it was read at and is never meant to be cached.
## Identifying a book
Pass the canonical uppercase slug (`NVDA-USDG`, `DEEP-USDG`) or the 32-byte hex book id. A lowercase slug is accepted and normalized. Call `GET /deepstate/v1/markets` first for the current list.
`book`/`trades`/`candles`/`stats` take `book` as a path segment (`/deepstate/v1/book/NVDA-USDG`); the analytics routes take it as a **required** query parameter (`?book=NVDA-USDG`), since each is denominated in that one book's own unit and can't be summed across books.
Each market in `GET /deepstate/v1/markets` carries a `status`:
| Status | Meaning |
|---|---|
| `live` | Trading normally, price feed confirmed |
| `price_decode_pending` | Book is live but its price key isn't decoded yet; don't build a valuation on its price fields |
| `legacy` | The book's token was superseded (resting orders remain, no new liquidity expected) |
| `registered` | Book exists on-chain but has no orders yet and no reward hook; exclude it from anything that walks book state until it flips to `live` |
A market also carries `has_rewarder` and, when true, `reward_token`, the token its maker-rewards hook pays out. `reward_token` differs by market and can change over time (see the DGP-3 note below), so check it per market rather than assuming one token protocol-wide.
## DGP-3 migration (2026-09-22)
Deepstate governance proposal DGP-3 executed at block `69224687`, replacing the STATE and DEEP tokens with a single token, **2DEEP** (`0x62177377c361bf76F12612aA153a9b405bef9FF2`, 18 decimals):
- Legacy DEEP redeems 1:1 into 2DEEP, permanently.
- Legacy STATE redeems into 2DEEP at a fixed rate (`state_to_2deep`, about 3.0155 2DEEP per STATE).
- The DEEP/USDG book is now `legacy`: its resting orders are still real, but the book has no reward hook and no new liquidity is expected. A new 2DEEP/USDG book exists with status `registered` (no orders yet).
- NVDA/USDG's maker-rewards hook was replaced by Rewarder V3, which pays out 2DEEP instead of DEEP (100M 2DEEP released over 365 days, scaled by the book's spread) and is documented as `rewarder_epoch: v3` below.
- The STATE vault is retired (`GET /v1/vault/history` reports `status: "retired"` and `retired_at_block`).
- 2DEEP's market price comes from the Uniswap v4 USDG/2DEEP pool.
Two routes track this directly: [`GET /v1/token/2deep`](#get-deepstatev1token2deep) for 2DEEP's own supply and float, and [`GET /v1/migration`](#get-deepstatev1migration) for redemption progress.
The reward and governance routes on the OpenAPI spec (`/v1/rewards/*`, `/v1/governance/*`) carry two fields to watch when a window spans the cutover:
- **`rewarder_epoch`**: `v1` (pre-DGP-3, paid DEEP), `v3` (DGP-3 onward, pays 2DEEP), or `mixed` for a window straddling the cutover. `reward_token` is always present (falling back to the market's current token, 2DEEP, on a `mixed` read); `reward_tokens` lists both denominations when the read actually spans the cutover.
- **`governor`**: `v1` or `v2`, naming which Governor contract executed a given proposal, independent of `parameters.governor`, which always describes the currently deployed Governor (V2, voting on 2DEEP, as of DGP-3).
See the [OpenAPI spec](https://api.onesource.io/deepstate/openapi.yaml) for the full schema of both routes.
## Endpoints
Full parameter, schema, and status-code detail lives in the [OpenAPI spec](https://api.onesource.io/deepstate/openapi.yaml). The ten routes below are the curated surface; a few other routes exist on the same spec but aren't part of this reference.
### `GET /deepstate/v1/markets`
The configured Deepstate markets, with token layout and pool/router addresses. Static configuration, safe to cache client-side. Call this first; every other route below takes a book identifier from its response.
```bash
curl https://api.onesource.io/deepstate/v1/markets \
-H "Authorization: Bearer $ONESOURCE_API_KEY"
```
```json
{
"markets": [
{
"book_id": "0xdf941c235503a5d2e67aee5dea00f2965f99421c0d034bd77f924c05c66bf399",
"book_slug": "NVDA-USDG",
"book_label": "NVDA/USDG",
"base_token": { "address": "0xd0601CE157Db5bdC3162BbaC2a2C8aF5320D9EEC", "symbol": "NVDA", "decimals": 18 },
"quote_token": { "address": "0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168", "symbol": "USDG", "decimals": 6 },
"has_rewarder": true,
"reward_token": { "address": "0x62177377c361bf76F12612aA153a9b405bef9FF2", "symbol": "2DEEP", "decimals": 18 },
"status": "live"
}
]
}
```
### `GET /deepstate/v1/book/{book}`
Order-book snapshot: bids descending, asks ascending, with each price level's resting size. Reflects the last completed on-chain state read rather than every intervening block; `block_number` names the exact height it was read at.
| Parameter | In | Description |
|---|---|---|
| `book` | path | Book slug or id (required) |
| `depth` | query | Max price levels per side, 1-500, default 50 |
```bash
curl "https://api.onesource.io/deepstate/v1/book/NVDA-USDG?depth=50" \
-H "Authorization: Bearer $ONESOURCE_API_KEY"
```
```json
{
"book_id": "0xdf941c235503a5d2e67aee5dea00f2965f99421c0d034bd77f924c05c66bf399",
"book_label": "NVDA/USDG",
"block_number": 52685677,
"finality": "head",
"bids": [{ "price_raw": "216.48219249", "size_raw": "3263516552", "side": "bid", "maker": "0x884a..." }],
"asks": [{ "price_raw": "220.47924566", "size_raw": "32945180000", "side": "ask", "maker": "0x7C3e..." }],
"summary": { "best_bid_raw": "216.48219249", "best_ask_raw": "220.47924566", "mid_raw": "218.48071908", "spread_bps": 182.9476 }
}
```
### `GET /deepstate/v1/trades/{book}`
Trade tape, newest first: each fill's price, size, side, and the block it happened in.
| Parameter | In | Description |
|---|---|---|
| `book` | path | Book slug or id (required) |
| `limit` | query | Max trades returned, 1-500, default 100 |
| `before` | query | Return trades strictly below this block number, for paging backward |
```bash
curl "https://api.onesource.io/deepstate/v1/trades/NVDA-USDG?limit=100" \
-H "Authorization: Bearer $ONESOURCE_API_KEY"
```
```json
{
"book_id": "0xdf941c235503a5d2e67aee5dea00f2965f99421c0d034bd77f924c05c66bf399",
"book_label": "NVDA/USDG",
"trades": [
{
"block_number": 52685331,
"transaction_hash": "0xa1c3...",
"side": "bid",
"attribution": "exact",
"price_raw": "216.47999901",
"base_amount_raw": "7381992258086111446",
"quote_amount_raw": "1598069869"
}
],
"next_before": 52685331
}
```
A trade's `attribution` marks how confidently it was priced: `exact` fills carry a measured fee split, `unpriced` fills omit `price_raw` and the amount fields entirely rather than publish a wrong number.
### `GET /deepstate/v1/candles/{book}`
OHLCV candles. Open/high/low/close are real trade prices, not recomputed; volumes are exact sums.
| Parameter | In | Description |
|---|---|---|
| `book` | path | Book slug or id (required) |
| `tf` | query | Timeframe: `1m`, `5m`, `15m`, `30m`, `1h`, `4h`, `1d`, `1w` (required) |
| `from` / `to` | query | Inclusive/exclusive window bounds, RFC 3339 or unix seconds |
```bash
curl "https://api.onesource.io/deepstate/v1/candles/NVDA-USDG?tf=1h" \
-H "Authorization: Bearer $ONESOURCE_API_KEY"
```
```json
{
"book_id": "0xdf941c235503a5d2e67aee5dea00f2965f99421c0d034bd77f924c05c66bf399",
"book_label": "NVDA/USDG",
"tf": "1h",
"candles": [
{
"t": "2026-09-02T14:00:00Z",
"open_raw": "216.43128523",
"high_raw": "220.47999716",
"low_raw": "215.70112004",
"close_raw": "216.47999901",
"volume_quote_raw": "412330119842",
"trades": 14,
"open_bucket": false
}
]
}
```
### `GET /deepstate/v1/stats/{book}`
Rolling 24h / 7d / 30d volume and price change, plus the latest traded price.
| Parameter | In | Description |
|---|---|---|
| `book` | path | Book slug or id (required) |
```bash
curl https://api.onesource.io/deepstate/v1/stats/NVDA-USDG \
-H "Authorization: Bearer $ONESOURCE_API_KEY"
```
```json
{
"book_id": "0xdf941c235503a5d2e67aee5dea00f2965f99421c0d034bd77f924c05c66bf399",
"book_label": "NVDA/USDG",
"last_price_raw": "216.48219249",
"windows": {
"24h": { "volume_quote_raw": "500000000000", "trades": 24, "change_pct": -1.82, "high_raw": "220.47999716", "low_raw": "215.70112004" },
"7d": { "volume_quote_raw": "19800000000000", "trades": 171, "change_pct": 2.41 }
}
}
```
### `GET /deepstate/v1/analytics/makers`
Per-maker analytics over a time window: time spent at the top of book, current resting notional, fill count and fill rate, and maker rewards earned. Rows are keyed by maker (resting-order owner) address.
| Parameter | In | Description |
|---|---|---|
| `book` | query | Book slug or id (**required**) |
| `from` / `to` | query | Inclusive/exclusive window bounds, RFC 3339 or unix seconds |
| `limit` | query | Max rows returned, 1-500, default 50 |
```bash
curl "https://api.onesource.io/deepstate/v1/analytics/makers?book=NVDA-USDG" \
-H "Authorization: Bearer $ONESOURCE_API_KEY"
```
```json
{
"book_id": "0xdf941c235503a5d2e67aee5dea00f2965f99421c0d034bd77f924c05c66bf399",
"book_label": "NVDA/USDG",
"rewards_available": true,
"rows": [
{
"maker": "0x6cf19308C22FC82ea620Fa0B3E94948d20f27B96",
"time_at_top_bid_seconds": 41820,
"notional_rested_raw": "82441000000",
"fill_count": 194,
"fill_rate": 0.86,
"deep_earned_raw": "1811294000000000000000"
}
]
}
```
`rewards_available` is `false` on a book with no reward hook (DEEP/USDG, since DGP-3); `deep_earned_raw` is omitted from every row on such a book rather than reported as zero. Despite its name, `deep_earned_raw` is denominated in whatever token the book's `reward_token` currently is, 2DEEP for NVDA/USDG since DGP-3, not necessarily DEEP; check `GET /deepstate/v1/markets` for the market's current `reward_token` before formatting it.
### `GET /deepstate/v1/analytics/cost-to-quote`
Gas spent resting and cancelling orders, bucketed over time: total fee, median fee, and how much of the fee is L2-to-L1 data cost.
| Parameter | In | Description |
|---|---|---|
| `book` | query | Book slug or id (**required**) |
| `from` / `to` | query | Inclusive/exclusive window bounds, RFC 3339 or unix seconds |
| `res` | query | Bucket width: `1h` or `1d`, default `1d` |
```bash
curl "https://api.onesource.io/deepstate/v1/analytics/cost-to-quote?book=NVDA-USDG&res=1d" \
-H "Authorization: Bearer $ONESOURCE_API_KEY"
```
```json
{
"book_id": "0xdf941c235503a5d2e67aee5dea00f2965f99421c0d034bd77f924c05c66bf399",
"book_label": "NVDA/USDG",
"resolution": "1h",
"buckets": [
{ "bucket": "2026-09-02T14:00:00Z", "tx_count": 402, "rests": 219, "cancels": 198, "total_fee_raw": "48973600000000000", "l1_share_pct": 18.75 }
],
"totals": { "tx_count": 790, "rests": 427, "cancels": 385, "total_fee_raw": "100178400000000000" }
}
```
### `GET /deepstate/v1/analytics/depth-history`
A depth heatmap: resting order size by price level over time, bucketed and optionally restricted to one side.
| Parameter | In | Description |
|---|---|---|
| `book` | query | Book slug or id (**required**) |
| `side` | query | Restrict to `bid` or `ask`. Default both |
| `from` / `to` | query | Inclusive/exclusive window bounds, RFC 3339 or unix seconds |
| `res` | query | Bucket width: `1h` or `1d`, default `1d` |
```bash
curl "https://api.onesource.io/deepstate/v1/analytics/depth-history?book=NVDA-USDG&res=1h" \
-H "Authorization: Bearer $ONESOURCE_API_KEY"
```
```json
{
"book_id": "0xdf941c235503a5d2e67aee5dea00f2965f99421c0d034bd77f924c05c66bf399",
"book_label": "NVDA/USDG",
"resolution": "1h",
"size_token": { "address": "0x5fc5360D0400a0Fd4f2af552ADD042D716F1d168", "symbol": "USDG", "decimals": 6 },
"points": [
{ "bucket": "2026-09-02T15:00:00Z", "side": "bid", "price_raw": "216.480000000000000000", "size_raw": "5213000000", "order_count": 4, "denominated_order_count": 4 },
{ "bucket": "2026-09-02T15:00:00Z", "side": "ask", "price_raw": "220.480000000000000000", "order_count": 3, "denominated_order_count": 0 }
],
"coverage": { "order_count": 253, "denominated_order_count": 129 }
}
```
Each level's `size_raw` is denominated in the book's `size_token` (`token0`, which is not always the quote token). It's omitted, never reported as zero, when every resting order at that level can't be attributed a size; `coverage` reports the split across the whole response so a caller can tell how complete the heatmap is.
### `GET /deepstate/v1/token/2deep`
2DEEP's supply, float, and migration figures, read live from chain rather than from an indexed snapshot. `float_raw` is `total_supply_raw − rewarder_balance_raw − endowment_locked_raw`: the NVDA/USDG reward pool and the team's multi-year grant are both accounted for in `total_supply_raw` but neither is spendable today. `price_usdg`/`price_usdg_raw` come from the Uniswap v4 2DEEP/USDG pool and are **omitted, not `null`**, until that pool has its first trade; `price_source` names the pool either way, so a caller can tell "no price yet" from "wrong pool".
`legacy_deep_total_supply_raw` is old DEEP's current total supply, not what is left to redeem: about 793M of it (`unissued_legacy_rewarder_raw`) is stranded forever in the retired Rewarder V1 contract and was never going to be traded in by anyone. For the actual redeemable figure, use `GET /v1/migration`'s `remaining_deep_redeemable_raw` below.
```bash
curl https://api.onesource.io/deepstate/v1/token/2deep \
-H "Authorization: Bearer $ONESOURCE_API_KEY"
```
```json
{
"token": { "address": "0x62177377c361bf76F12612aA153a9b405bef9FF2", "symbol": "2DEEP", "decimals": 18 },
"as_of_block": 70869077,
"finality": "head",
"total_supply_raw": "394783654228296379381065133",
"supply_cap_raw": "3000000000000000000000000000",
"float_raw": "163284044836851443119333362",
"rewarder_balance_raw": "98249089661542150686844051",
"endowment_locked_raw": "133250519729902785574887720",
"migration_liability_raw": "206689100066582620020137004",
"issued_deep_liability_raw": "68227180426607635915937407",
"initial_endowment_raw": "88581042885678265722915858",
"unissued_legacy_rewarder_raw": "793310900933417380045066419",
"state_to_2deep_wad": "3015535497731148791",
"state_to_2deep": 3.0155354977311486,
"legacy_deep_total_supply_raw": "833197789107693136084037755",
"legacy_state_total_supply_raw": "45916196225894870554169371",
"price_usdg_raw": "0.001129324479477382",
"price_usdg": 0.001129324479477382,
"price_source": "uniswap_v4:0x2d420ffa43f657344796377daf5b7f5b558892df6cc31a788f271c0cd9077611"
}
```
### `GET /deepstate/v1/migration`
DEEP/STATE-to-2DEEP trade-in progress since DGP-3 executed: redeemed totals and counts for each legacy token, the STATE-to-2DEEP `ratio`, and what's left outstanding. Backed by `prod.robinhood.deepstate_events`, the same indexed event stream `GET /v1/governance/delegates` and `GET /v1/rewards/leaderboard` read.
| Parameter | In | Description |
|---|---|---|
| `from` | query | Inclusive lower bound, RFC 3339 or unix seconds. Defaults to the DGP-3 execution timestamp; there's no trade-in activity before it |
| `to` | query | Exclusive upper bound, RFC 3339 or unix seconds. Defaults to now |
`from`/`to` bound the whole response, not just `daily`: the totals, the redemption counts, and the daily series are all computed over that same window.
```bash
curl https://api.onesource.io/deepstate/v1/migration \
-H "Authorization: Bearer $ONESOURCE_API_KEY"
```
```json
{
"ratio": "3015535497731148791",
"executed_at_block": 69224687,
"executed_at": "2026-09-22T00:23:44Z",
"deep_redeemed_raw": "18468042479697253554365749",
"deep_redemptions": 22,
"state_redeemed_raw": "33505118097276757585354227",
"state_redemptions": 20,
"twodeep_minted_for_state_raw": "101035872978012388120835794",
"remaining_deep_redeemable_raw": "21418845694578502484605587",
"remaining_state_redeemable_raw": "12411078128618112968815144",
"from": "2026-09-22T00:23:44Z",
"truncated": false,
"daily": [
{
"day": "2026-09-22",
"deep_redeemed_raw": "18137409339286248572655232",
"state_redeemed_raw": "32081526580564103543727113"
},
{
"day": "2026-09-23",
"deep_redeemed_raw": "330633140411004981710517",
"state_redeemed_raw": "1423591516712654041627114"
}
]
}
```
The counts above are a live capture while the events index is still being backfilled after DGP-3, so they read lower than the chain's actual redemption count at the same moment; treat them as illustrative of shape, not as a final tally. That gap is inherent to reading from an indexed event stream rather than the chain itself: totals can lag or undercount while a backfill is in progress, independent of the `truncated` flag below.
`remaining_deep_redeemable_raw` / `remaining_state_redeemable_raw` are what's left to trade in: old DEEP stranded, unreachable, in the previous reward contract is excluded from the remaining figure since it was never going to be redeemed. These two fields are always computed from all-time totals, even when `from`/`to` narrow the rest of the response to a shorter window.
`truncated` is `true` when the window held more redemption events than one read caps at; when it is, every total and count in the response (not just `daily`) is a floor rather than an exact figure. A `501` response means the underlying event stream hasn't shipped yet (`not_ready: "prod.robinhood.deepstate_events"`).
## Errors
Deepstate errors are a single flat shape: `{ "error": "", "message": "" }`. An anonymous call to a paid route gets the same `402` payment challenge as any other paid endpoint (see [x402 on Base](/getting-started/x402-and-mpp/x402-base) or [MPP on Tempo](/getting-started/x402-and-mpp/mpp-tempo)); a Bearer call with an invalid or missing key gets the same gateway auth errors described in [Error codes → Gateway errors](/guides/error-codes#gateway-errors-auth-payment-routing). Deepstate does not use the `{data, error, meta}` envelope that `/api/chain/*` routes do.
## MCP tools
`@one-source/mcp` exposes these eight Deepstate tools, all category `deepstate`. Unlike the chain tools, none take a `network` parameter: Deepstate data is always Robinhood Chain.
| Tool | Endpoint | What it returns |
|---|---|---|
| `1s_ds_markets` | `GET /deepstate/v1/markets` | The configured Deepstate markets, with token layout and pool/router addresses |
| `1s_ds_book` | `GET /deepstate/v1/book/{book}` | Order-book snapshot: bids descending, asks ascending |
| `1s_ds_trades` | `GET /deepstate/v1/trades/{book}` | Trade tape, newest first |
| `1s_ds_candles` | `GET /deepstate/v1/candles/{book}` | OHLCV candles |
| `1s_ds_stats` | `GET /deepstate/v1/stats/{book}` | Rolling 24h/7d/30d volume and price change |
| `1s_ds_makers` | `GET /deepstate/v1/analytics/makers` | Per-maker time-at-top, resting notional, fills, and maker rewards earned (token varies by market; see `1s_ds_markets`) |
| `1s_ds_cost_to_quote` | `GET /deepstate/v1/analytics/cost-to-quote` | Gas spent resting and cancelling orders |
| `1s_ds_depth_history` | `GET /deepstate/v1/analytics/depth-history` | Depth heatmap: resting size by price level over time |
They're priced and authenticated like their REST counterparts (see [Pricing](#pricing) above). See [MCP tool reference](/getting-started/mcp/tool-reference#deepstate-market-data-8) for the full tool catalog alongside the chain tools.
## Next
- [Getting Started](/getting-started/): pick an access path
- [MCP tool reference](/getting-started/mcp/tool-reference): every tool the MCP server exposes
- [Error codes](/guides/error-codes): the chain routes' error shapes (Deepstate's is simpler; see [Errors](#errors) above)
---
## Getting Started
Three access paths share the same REST API. Pick the one that matches your application. This page and the guides below focus on the `/api/chain/*` endpoints; [Deepstate Market Data](/deepstate/) and [The Standard Reserve](/standard/) use the same three access paths under their own route families and pricing.
## API Key
Sign up at [app.onesource.io/signup](https://app.onesource.io/signup), subscribe, and authenticate with a Bearer API key. One plan, predictable per-month pricing billed through Stripe. It sets an account-wide request quota, shared across every key you create.
```bash
curl https://api.onesource.io/api/chain/network-info \
-H "Authorization: Bearer sk_your_api_key_here"
```
→ [API key setup guide](./api-key/subscribe)
## MCP Server
If your workflow is in Claude Code, Claude Desktop, Cursor, or Windsurf, install the [`@one-source/mcp`](https://www.npmjs.com/package/@one-source/mcp) MCP server. It exposes the OneSource REST API as MCP tools, all prefixed `1s_` (live-chain tools end in `_live`), alongside free documentation tools that answer questions about the API itself.
```bash
claude mcp add onesource -e ONESOURCE_API_KEY=sk_… -- npx -y @one-source/mcp@latest
```
Works with either an API key (Bearer) or an x402 wallet (`X402_PRIVATE_KEY`).
→ [MCP setup guide](./mcp/install)
## x402 / MPP
Pay per call from an agent wallet, no signup. Two protocols:
- **x402** on Base mainnet, paid in USDC. Use the [`@x402/fetch`](https://www.npmjs.com/package/@x402/fetch) client or AgentCash.
- **MPP** on Tempo, paid in USDC.e (or pathUSD). Tempo CLI or MPP-aware fetch clients work.
Per-endpoint pricing is published in `meta.cost_usdc` on every response and in the `x-payment-info` extension on each OpenAPI operation. [Deepstate market data](/deepstate/) is priced flat instead ($0.005 per call, $0.02 for analytics).
→ [x402 / MPP setup guide](./x402-and-mpp/wallet-setup)
## Verify Access
Whichever path you pick, the same one-call sanity check works for all of them:
```bash
curl https://api.onesource.io/api/chain/network-info \
-H "Authorization: Bearer $ONESOURCE_API_KEY"
```
Expected response:
```json
{
"data": { "chain_id": "0x1", "block_number": "0x14abcde", "gas_price": "0x3b9aca00" },
"error": null,
"meta": { "endpoint": "/api/chain/network-info", "request_id": "..." }
}
```
Calls run against Ethereum mainnet by default. Add `?network=sepolia` or `?network=robinhood` to any `/api/chain/*` request to target the Sepolia testnet or Robinhood Chain instead. See [Choosing a network](./direct-rest/first-request#choosing-a-network).
---
## Make your first request
A single HTTP call confirms your subscription is active and your API key works.
## curl
```bash
curl https://api.onesource.io/api/chain/network-info \
-H "Authorization: Bearer $ONESOURCE_API_KEY"
```
Expected response:
```json
{
"data": {
"chain_id": "0x1",
"block_number": "0x14abcde",
"gas_price": "0x3b9aca00"
},
"error": null,
"meta": { "endpoint": "/api/chain/network-info", "request_id": "..." }
}
```
`block_number` is the live mainnet tip; it should be close to the current block reported on https://etherscan.io. To hit another chain instead, add `?network=sepolia` for the Sepolia testnet (the `chain_id` then comes back as `0xaa36a7`) or `?network=robinhood` for Robinhood Chain (`0x1237`). See [Choosing a network](../direct-rest/first-request#choosing-a-network).
## Node.js (fetch)
```ts
const res = await fetch('https://api.onesource.io/api/chain/network-info', {
headers: { Authorization: `Bearer ${process.env.ONESOURCE_API_KEY}` },
});
const { data, error } = await res.json();
if (error) throw new Error(`${error.code}: ${error.message}`);
console.log(`Chain ${parseInt(data.chain_id, 16)} at block ${parseInt(data.block_number, 16)}`);
```
## Python (httpx)
```python
resp = httpx.get(
'https://api.onesource.io/api/chain/network-info',
headers={'Authorization': f"Bearer {os.environ['ONESOURCE_API_KEY']}"},
)
resp.raise_for_status()
body = resp.json()
print(f"Chain {int(body['data']['chain_id'], 16)} at block {int(body['data']['block_number'], 16)}")
```
## Go
```go
req, _ := http.NewRequest("GET", "https://api.onesource.io/api/chain/network-info", nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("ONESOURCE_API_KEY"))
resp, err := http.DefaultClient.Do(req)
if err != nil { return err }
defer resp.Body.Close()
var body struct {
Data struct{ ChainID, BlockNumber, GasPrice string } `json:"data"`
Error *struct{ Code, Message string } `json:"error"`
Meta map[string]any `json:"meta"`
}
json.NewDecoder(resp.Body).Decode(&body)
```
## Response envelope
Every `/api/*` response uses the same shape:
```json
{ "data": { ... }, "error": null, "meta": { "endpoint": "...", "request_id": "..." } }
```
On failure, `data` is `null` and `error` is an object with `code` and `message`. See [Response envelope](../direct-rest/response-envelope) for the full reference.
## What can go wrong
| Status | What it means | What to do |
|---|---|---|
| `401` | Missing or invalid `Authorization` header. | Confirm the env var is set and holds the full key you copied when you created it. If you've lost the key, create a replacement on the dashboard. |
| `403` | Your subscription isn't active. | Check your subscription status at [app.onesource.io](https://app.onesource.io), or call the endpoint with x402 / MPP instead. |
| `5xx` | Upstream RPC node, facilitator, or unhandled service-side issue. | Retry with exponential backoff. |
Your plan meters request volume: a daily and a monthly quota, reported back on every response as `X-Quota-Day-Used` / `X-Quota-Day-Limit` and `X-Quota-Month-Used` / `X-Quota-Month-Limit`. Concurrency is not metered. See [Usage quotas](./usage-quotas).
## Next
- Browse the [API Reference](/api-reference/) for the full endpoint catalog.
- If you're wiring this into an AI assistant, switch to the [MCP server](../mcp/install) instead of raw REST.
---
## Retrieve your API key
# Create and retrieve your API key
You create and manage OneSource API keys on the **OneSource dashboard** at [app.onesource.io](https://app.onesource.io), under **API Keys**. Keys start with `sk_`.
## Create a key (copy it now: it's shown only once)
1. Sign in at [app.onesource.io](https://app.onesource.io).
2. Open **API Keys** and click **Create new API key**. Optionally name it (for example `production` or `staging`).
3. The full `sk_...` value is shown **once**, in a dialog, at creation time. Copy it then and store it somewhere safe.
After you close that dialog, the dashboard only ever shows a **masked prefix** (`sk_xxxx…yyyy`) for the key, never the full value again. There's no "reveal" button on the list and support can't recover the key for you. If you lose a key, you don't retrieve it: you [create a replacement and delete the old one](#replacing-a-key).
## Manage your keys
The **API Keys** table lists every key on your account:
| Column | What it shows |
|---|---|
| **Name** | The label you gave the key (or `default`). |
| **Key** | The masked prefix (`sk_xxxx…yyyy`), enough to tell keys apart. |
| **Created** | When the key was created. |
| **Last used** | When the key last authenticated a request (or `Never`). |
| **Enabled** | A toggle to disable or re-enable the key without deleting it. |
| **Remove** | The trash icon permanently revokes the key. |
- **Disable vs delete:** switch the **Enabled** toggle off to suspend a key while keeping it on the account (useful for parking a key you might bring back); use the trash icon to revoke it for good.
- **Revocation isn't instant.** The API gateway caches successful key validations for up to ~30 seconds, so a disabled or deleted key can keep working for that long before it's rejected.
- Create as many keys as you like (one per environment is a common pattern). All keys on your account **share the same daily and monthly request quota**, see [Usage quotas](./usage-quotas).
## Store it in your application
A few patterns that work well:
- **Server-side workloads:** set `ONESOURCE_API_KEY` as an environment variable in your hosting platform (Vercel, Fly.io, Railway, Heroku config vars; ECS task definition; Kubernetes secret).
- **Local development:** copy the key into a `.env` file (and add `.env` to `.gitignore`).
- **CI:** store the key as a masked CI secret and inject as an environment variable at build time.
Treat the key like any other production secret. Don't commit it to source control, and don't log it.
### Node.js
```ts
const apiKey = process.env.ONESOURCE_API_KEY;
if (!apiKey) throw new Error('ONESOURCE_API_KEY is not set');
const res = await fetch('https://api.onesource.io/api/chain/network-info', {
headers: { Authorization: `Bearer ${apiKey}` },
});
```
### Python
```python
api_key = os.environ['ONESOURCE_API_KEY']
resp = httpx.get(
'https://api.onesource.io/api/chain/network-info',
headers={'Authorization': f'Bearer {api_key}'},
)
```
## Replacing a key
There's no in-place "rotate" button, and you can't see an existing key's full value again, so rolling a key over means creating a new one and cutting traffic across before removing the old one:
1. On the dashboard, click **Create new API key** and copy the new `sk_*` value into your secret store.
2. Deploy your application with the new key and confirm it's serving traffic.
3. Optionally switch the old key's **Enabled** toggle off first: if nothing breaks within a minute or so, you've confirmed nothing still depends on it.
4. Once the new key is live, **delete** the old key from the dashboard.
If a key is exposed, don't wait to test: delete (or at least disable) the compromised key immediately, then create and deploy a replacement.
## Next
→ [Make your first request](./first-request).
---
## Sign up for an API key
The OneSource REST API is sold as a single subscription billed monthly through Stripe. There's one plan and nothing to choose between: you create an account on the OneSource dashboard, confirm your email, and subscribe. API keys are issued once the subscription is active.
## What you need
- An email address you can receive a confirmation link at.
- A payment card for Stripe Checkout, used when you subscribe.
## Steps
1. **Open the signup page.** Go to the OneSource dashboard at [app.onesource.io](https://app.onesource.io) and choose **Sign Up**.
2. **Create your account.** Enter your name and email (organization is optional), then complete the reCAPTCHA. There's no password to set; OneSource signs you in by email confirmation.
3. **Confirm your email.** OneSource emails you a confirmation link. Opening it confirms the address and takes you to Stripe Checkout; the signup tab you started from signs you in and loads the dashboard.
4. **Complete checkout.** Pay through Stripe. The checkout is also reachable from the dashboard's **Complete Checkout** button. The subscription covers both the REST API and the MCP Server. Stripe charges your card on its normal monthly cycle and emails receipts.
## Get your API key
Create and copy your key from the **API Keys** section of the dashboard. See [Retrieve your API key](./retrieve-api-key).
## Signing back in
There's no password. To return to the dashboard later, go to [app.onesource.io](https://app.onesource.io), enter your email, and OneSource emails you a single-use sign-in link. Click it to land back on the dashboard.
## Managing your subscription
Your subscription lives under **Your Plan** on the dashboard:
- **Account** shows your email, next billing date, and a **Current Cycle Utilization** bar (the share of your monthly request allowance used so far this calendar month).
- **Plan** shows your current plan with its daily and monthly request allowance (see [Usage quotas](./usage-quotas)).
To cancel, open **Your Plan → Plan** and click **Cancel Plan**. The dashboard asks why you're leaving, then schedules the cancellation: your access and full monthly allowance continue until the end of the current billing period, after which you're not charged again. Once a cancellation is scheduled the button becomes **Manage Plan**, which opens the Stripe billing portal where you can update your card. If a renewal payment fails, the dashboard shows the date Stripe will retry.
## Next
→ [Retrieve your API key](./retrieve-api-key) from the dashboard.
---
## Usage quotas
Your plan meters **request volume**, over two windows. Concurrency is not metered, so a
client may issue as many simultaneous calls as its workload needs.
| Window | Resets | Scope |
|---|---|---|
| Per day | 00:00 UTC daily | Per account, shared across every key |
| Per calendar month | 00:00 UTC on the 1st | Per account, shared across every key |
Both windows key on your **license**, not on the individual key, so creating more keys
does not buy more volume. Your active numbers are shown on the OneSource dashboard under
**Your Plan**.
On the API key plan those are **200,000 requests per day** and **2,000,000 per calendar
month**. The daily figure is the monthly figure divided by ten: it exists so a runaway
client cannot spend a whole month's volume in an afternoon and then be stopped until the
1st. A daily wall clears in hours; a monthly wall does not.
:::note Quota windows are UTC, not your billing cycle
The monthly quota resets at 00:00 UTC on the first of the calendar month. Your Stripe
billing date is separate and does not move it.
:::
## Quota headers
Every API-key response carries your position in each **limited** window, so a client can
pace itself from the response it already has instead of polling the dashboard:
| Header | Value |
|---|---|
| `X-Quota-Day-Limit` | Requests allowed in the current UTC day. |
| `X-Quota-Day-Used` | Requests used in the current UTC day, including this one. |
| `X-Quota-Month-Limit` | Requests allowed in the current calendar month. |
| `X-Quota-Month-Used` | Requests used in the current calendar month, including this one. |
```bash
$ curl -i https://api.onesource.io/api/chain/network-info \
-H "Authorization: Bearer $ONESOURCE_API_KEY"
HTTP/2 200
x-quota-day-limit: 200000
x-quota-day-used: 1483
x-quota-month-limit: 2000000
x-quota-month-used: 41902
...
```
A header pair can be absent, and absence never means "blocked":
- **An uncapped window emits nothing**, so treat a missing pair as "this window does not
bound you".
- **If the metering store is briefly unreachable**, the request is served and no headers
are set.
Write your client to use the headers when present and to carry on when they are not.
Calls authenticated with x402 or MPP carry no quota headers. Those flows are settled per
call and are not quota-bound at all.
## What a rejection looks like
Every limit returns HTTP `429` with the flat gateway error shape, distinguished by the
`error` field:
| `error` | Meaning | What to do |
|---|---|---|
| `daily_limit_exceeded` | Your account's daily volume is spent. | Wait for the 00:00 UTC reset, shift the overflow to x402 / MPP, or email support@onesource.io for a higher daily cap. |
| `monthly_limit_exceeded` | Your account's monthly volume is spent. | Route overflow to x402 / MPP until the 1st, or email support@onesource.io for a higher monthly cap. |
| `rate_limited` | You tripped the backend's abuse guard by issuing an extreme burst. | Back off briefly and retry. `Retry-After: 1` is set. |
`rate_limited` is a backend safety valve rather than a plan limit: it guards against a
looping or runaway client saturating the service. Seeing it during normal operation is
worth reporting rather than working around.
A `429` from either volume window still carries the `X-Quota-*` headers, so the response
tells you which window you hit and how large it was.
## Backoff pattern
Retry `429` on the advertised window, and use exponential backoff with jitter for the
transient `502`/`503` you will occasionally see from upstream RPC nodes and the
facilitator:
```ts
async function callWithRetry(url: string, init: RequestInit, attempt = 0): Promise {
const res = await fetch(url, init);
if (res.status === 429) {
// Volume walls (daily_limit_exceeded / monthly_limit_exceeded) will not clear
// on a retry loop; only the transient abuse guard sets Retry-After.
const retryAfter = res.headers.get('retry-after');
if (!retryAfter || attempt >= 5) return res;
await new Promise(r => setTimeout(r, Number(retryAfter) * 1000));
return callWithRetry(url, init, attempt + 1);
}
if (res.status < 500 || attempt >= 5) return res;
const backoff = 2 ** attempt * 500 + Math.random() * 250;
await new Promise(r => setTimeout(r, backoff));
return callWithRetry(url, init, attempt + 1);
}
```
Retrying into a spent volume quota just burns requests against a wall that will not move
until the window resets. Branch on the `error` field before you retry.
See [Error codes](/guides/error-codes) for the full retry catalog.
## Monitoring usage
- **In your client:** track `X-Quota-Day-Used` against `X-Quota-Day-Limit`. The daily
window is the one that trips first and the one you can act on the same day.
- **On the dashboard:** [app.onesource.io](https://app.onesource.io) → **Your Plan →
Account** shows a **Current Cycle Utilization** bar, the share of your account's
monthly allowance used so far in the current **calendar month** (aggregated across all
your keys, not per key). This is the same counter the API enforces against, so the bar
and the headers agree.
Each API response also carries a `meta.request_id` you can pin to log entries on your
side.
## When you need more
- **Spike or overflow traffic:** route bursts to
[pay-per-call with x402 or MPP](../x402-and-mpp/x402-base). No quota, you pay per
request in USDC, and it runs in parallel with your Bearer key.
- **A sustained higher cap:** email support@onesource.io. Daily and monthly caps are
raised on your individual account.
## Free endpoints
A handful of endpoints are public and don't count against your quota:
- `GET /api/pricing`: per-endpoint pricing
- `GET /api/networks`: the networks the API serves
- `GET /openapi.json` and `GET /.well-known/openapi.json`: OpenAPI spec
- `GET /llms.txt`: LLM-optimized index
`GET /health` is also unauthenticated, but it is not one of these. It answers from
the routing layer in front of the API rather than from the API itself, so a `200`
means "the front door is up", not "your next `/api/chain/*` call will succeed". It
stays green during an upstream outage.
:::warning Don't build an API monitor on `/health`
If you want to know whether the API is actually serving, poll a real endpoint. A
free one such as `/api/networks` exercises the full path; a paid one such as
`/api/chain/block-number` additionally proves your credential and quota are good.
:::
---
## First request (direct REST)
The OneSource REST API is plain HTTPS + JSON: any HTTP client works. The same endpoint shape covers all three access paths; only the auth header changes.
## curl
```bash
# Bearer API key (from app.onesource.io/signup)
curl https://api.onesource.io/api/chain/network-info \
-H "Authorization: Bearer $ONESOURCE_API_KEY"
# x402 / MPP / no auth: OneSource will return 402 with payment challenges
curl -v https://api.onesource.io/api/chain/network-info
```
For wallet-paid calls, a custom HTTP client handles the 402 → sign → retry loop. See [x402 on Base](../x402-and-mpp/x402-base) or [MPP on Tempo](../x402-and-mpp/mpp-tempo).
## Node.js (fetch)
```ts
const res = await fetch('https://api.onesource.io/api/chain/live-balance?' + new URLSearchParams({
address: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045',
tokens: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48,0xdAC17F958D2ee523a2206206994597C13D831ec7',
}), {
headers: { Authorization: `Bearer ${process.env.ONESOURCE_API_KEY}` },
});
const { data, error, meta } = await res.json();
if (error) throw new Error(`${error.code}: ${error.message}`);
console.log(`Request ${meta.request_id} cost ${meta.cost_usdc ?? 'free'} USDC`);
```
## Python (httpx)
```python
resp = httpx.get(
'https://api.onesource.io/api/chain/erc20-balance',
params={
'account': '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045',
'token': '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
},
headers={'Authorization': f"Bearer {os.environ['ONESOURCE_API_KEY']}"},
timeout=10.0,
)
resp.raise_for_status()
body = resp.json()
if body['error']:
raise RuntimeError(body['error']['message'])
print(body['data'])
```
## Go
```go
req, _ := http.NewRequest("GET", "https://api.onesource.io/api/chain/tx/"+hash, nil)
req.Header.Set("Authorization", "Bearer "+os.Getenv("ONESOURCE_API_KEY"))
resp, err := http.DefaultClient.Do(req)
if err != nil { return err }
defer resp.Body.Close()
var body struct {
Data json.RawMessage `json:"data"`
Error *APIError `json:"error"`
Meta map[string]any `json:"meta"`
}
if err := json.NewDecoder(resp.Body).Decode(&body); err != nil { return err }
if body.Error != nil { return fmt.Errorf("%s: %s", body.Error.Code, body.Error.Message) }
```
## Choosing a network
Every `/api/chain/*` endpoint accepts an optional `network` query parameter. It defaults to `ethereum` (mainnet); pass `network=sepolia` for the Sepolia testnet or `network=robinhood` for Robinhood Chain.
```bash
# Mainnet (default): the parameter can be omitted
curl "https://api.onesource.io/api/chain/network-info" \
-H "Authorization: Bearer $ONESOURCE_API_KEY"
# Sepolia testnet: add ?network=sepolia
curl "https://api.onesource.io/api/chain/network-info?network=sepolia" \
-H "Authorization: Bearer $ONESOURCE_API_KEY"
# Robinhood Chain: add ?network=robinhood
curl "https://api.onesource.io/api/chain/network-info?network=robinhood" \
-H "Authorization: Bearer $ONESOURCE_API_KEY"
```
For POST endpoints, `network` is still a query parameter, so it goes on the URL (for example `…/api/chain/call?network=sepolia`), not in the JSON body. To confirm which chain a backend is on, call `/api/chain/chain-id`.
| Network | `network` value | EIP-155 chain id |
|---|---|---|
| Ethereum mainnet | `ethereum` (default) | `1` (`0x1`) |
| Sepolia testnet | `sepolia` | `11155111` (`0xaa36a7`) |
| Robinhood Chain | `robinhood` | `4663` (`0x1237`) |
### The same call can mean different things per chain
The endpoints are identical across networks, but the chains are not, and a few of
the differences change how a response should be read:
| Chain fact | Why it matters |
|---|---|
| Block cadence | Ethereum produces a block roughly every 12 seconds; Robinhood Chain does so about every 100 milliseconds. The same block range is a very different time window on each, so size ranges from the reported cadence rather than from a fixed block count. |
| ENS | The ENS registry is deployed on `ethereum` and `sepolia` only. On Robinhood Chain a name lookup fails because of the chain, not because the name is unregistered. |
| Mempool | `ethereum` and `sepolia` have a public pending pool. Robinhood Chain runs a single sequencer, so `/api/chain/pending` reports roughly the next block rather than a queue of unconfirmed transactions. |
| Finality | On Robinhood Chain, the `safe` and `finalized` block tags track settlement on Ethereum and can trail `latest` by a wide margin while the chain is perfectly healthy. |
[`/api/chain/network-info`](/api-reference/chain-network-info) reports all of these
per network as a `chain` object, alongside `head_age_seconds` and `stale`. Call it
first when you are working against a chain for the first time.
## POST endpoints
A few endpoints accept JSON bodies, notably `/api/chain/call` (simulate `eth_call`) and `/api/chain/estimate-gas`:
```bash
curl -X POST https://api.onesource.io/api/chain/call \
-H "Authorization: Bearer $ONESOURCE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
"data": "0x18160ddd"
}'
```
The full request shape for each endpoint is documented in the [API Reference](/api-reference/).
## Next
- [Response envelope](./response-envelope): exact shape of `data`, `error`, `meta`
- [API Reference](/api-reference/): all 25 endpoints
---
## Response envelope
Every `/api/*` response, success or failure, wraps its payload in three fields: `data`, `error`, `meta`.
```json
{
"data": { ... },
"error": null | { "code": , "message": "string" },
"meta": { ... }
}
```
## Success
```json
{
"data": {
"chain_id": "0x1",
"block_number": "0x14abcde",
"gas_price": "0x3b9aca00"
},
"error": null,
"meta": {
"endpoint": "/api/chain/network-info",
"request_id": "00000000abcdef01",
"cost_usdc": "0.001",
"payment_chain": "base",
"payment_token": "USDC"
}
}
```
- `data`: the endpoint's actual response body. Shape varies by endpoint; see the [API Reference](/api-reference/) for each.
- `error`: `null` on success.
- `meta`: per-call telemetry; see below.
## Failure
```json
{
"data": null,
"error": {
"code": 400,
"message": "address param required"
},
"meta": {
"endpoint": "/api/chain/live-balance",
"request_id": "00000000abcdef02"
}
}
```
- `data` is `null`.
- `error.code` is the HTTP status as an integer; branch on it like you would on the status line.
- `error.message` is human-readable English from the handler. Log it, don't parse it.
- `meta.request_id` is logged on the server side; include it in support requests.
Common statuses:
| Status | Typical cause |
|---|---|
| `200` | Success |
| `400` | Missing or malformed parameter |
| `401` | Missing or invalid `Authorization` (returned by the gateway, not the envelope shape; see [Error codes](/guides/error-codes)) |
| `402` | Payment required (x402 / MPP challenge; see [Wallet setup](../x402-and-mpp/wallet-setup)) |
| `403` | Your subscription isn't active for this endpoint (gateway shape) |
| `404` | Unknown route, e.g. a typo in an `/api/chain/*` path (gateway shape). **Not** used for missing on-chain resources; see [Error codes](/guides/error-codes#resource-not-found-doesnt-mean-404) |
| `429` | A daily or monthly request quota is spent, or an extreme burst tripped the abuse guard (gateway shape) |
| `5xx` | Upstream RPC node, facilitator, or unhandled service-side failure |
A `429` means a volume quota was reached; the response carries `X-Quota-*` headers naming which window and how large it was. See [Usage quotas](../api-key/usage-quotas).
## `meta` fields
| Field | Notes |
|---|---|
| `endpoint` | Always present. The matched route, e.g. `/api/chain/network-info`. |
| `request_id` | Always present. Hex identifier; mirrors the server-side log entry. |
| `cost_usdc` | Present when the call was paid (x402 / MPP). The exact USDC amount settled. |
| `payment_chain` | Present on paid calls: `base` for x402, `tempo` for MPP. |
| `payment_token` | Present on paid calls: `USDC` for x402, `USDC.e` or `pathUSD` for MPP. |
Subscription Bearer-key calls don't emit `cost_usdc` / `payment_chain` / `payment_token` because settlement is billed monthly by Stripe, not per-call.
## Patterns
### Branch on HTTP status, not the message
`error.code` mirrors the HTTP status. `error.message` is freeform handler text with no guaranteed wording; never parse it.
```ts
const { data, error } = await res.json();
if (error) {
switch (error.code) {
case 400: throw new ValidationError(error.message);
case 404: return null; // resource not found on-chain
case 402: return handlePaymentChallenge(res);
default: if (error.code >= 500) return retryWithBackoff();
throw new UnknownAPIError(error);
}
}
return data;
```
The gateway uses a different shape (flat `{error, message}`) for auth and routing failures. See [Error codes](/guides/error-codes) for the full pattern that handles both.
### Always log `request_id`
When something looks off, having the `meta.request_id` in your logs lets the team find the corresponding server-side trace in one query. Log it on every call, not only on errors.
### Treat `data` as the only payload
Don't depend on `error` being absent; always read `error` first and fall through to `data` only when `error === null`. The envelope is uniform across success and failure, so you can decode it once and dispatch.
## Full error reference
The two error shapes (envelope and gateway), HTTP status meanings, and recovery patterns are documented in [Guides → Error codes](/guides/error-codes).
---
## Configuration
Reference for everything you can configure on `@one-source/mcp`. See the [install guide](./install) for the basic setup.
## Environment variables
| Variable | Default | Secret | Description |
|---|---|---|---|
| `ONESOURCE_API_KEY` | - | Yes | Bearer key from your [OneSource subscription](../api-key/retrieve-api-key) (`sk_…`). Primary auth mode. |
| `X402_PRIVATE_KEY` | - | Yes | EVM private key (`0x…`) for [wallet-paid x402 access](../x402-and-mpp/x402-base) (USDC on Base). Used when `ONESOURCE_API_KEY` is not set. |
| `MPP_PRIVATE_KEY` | - | Yes | EVM private key (`0x…`) for [wallet-paid MPP access](../x402-and-mpp/mpp-tempo) (USDC.e / pathUSD on Tempo). Used when `ONESOURCE_API_KEY` is not set. If both wallet keys are set, x402 is the initial rail; switch with `1s_payment_mode`. |
| `ONESOURCE_BASE_URL` | `https://api.onesource.io` | No | API backend URL. Change only if directed by OneSource support. |
| `PORT` | `3000` | No | HTTP server port. Only applies in `--http` mode. Used by hosting platforms (Railway, Fly.io). |
| `ONESOURCE_PAYMENT_MODE` | rail default | No | Initial rail + scheme: `x402-exact`, `x402-batch`, `mpp-charge`, or `mpp-session`. Falls back to the enabled rail's own default. Switch in-session with `1s_payment_mode`. |
| `X402_PAYMENT_MODE` | `exact` | No | `batch` opens a payment channel (one deposit funds many calls); `exact` settles per call. Only applies in x402 mode. See [Batch settlement](#batch-settlement-payment-channels). |
| `X402_CHANNEL_DIR` | - (in-memory) | No | Directory to persist batch channel + voucher state across restarts. Recommended whenever `X402_PAYMENT_MODE=batch`. |
| `X402_CHANNEL_SALT` | `0x00…00` | No | 32-byte hex salt selecting which channel to use. Change it to open a fresh channel under the same wallet. |
| `X402_DEPOSIT_MULTIPLIER` | `10` | No | On channel open, deposit `price × this` (minimum 3), funding that many calls before a top-up. Higher means fewer re-deposits but more USDC locked up front. Any unused balance is reclaimable via the `1s_refund` tool or the gateway's idle auto-refund. |
| `X402_BATCH_PROMPT` | `ask` | No | How proactively the agent offers to switch to batch mode: `ask` (confirm with you first), `auto` (switch on its own), or `off` (only when you ask). Only applies in x402 mode. |
| `X402_BATCH_THRESHOLD` | `5` | No | Number of anticipated calls in a session at or above which the agent considers batch mode. Advisory: the agent estimates the count, it is not a hard runtime counter. |
| `X402_RPC_URL` | public Base RPC | No | Custom Base RPC endpoint. Used by batch mode for the on-chain deposit/claim reads. |
| `MPP_PAYMENT_MODE` | `charge` | No | Initial MPP scheme: `charge` (one payment per call) or `session` (voucher channel). Only applies in MPP mode. See [MPP settlement](#mpp-settlement-tempo). |
| `MPP_MAX_DEPOSIT` | `1` | No | `mpp-session`: the channel deposit, in token units (flat, not scaled by call price). Bounds the worst-case balance locked if the server is killed before it settles. Any unused balance is reclaimable via the `1s_refund` tool. Also settable live via `1s_batch_config`. |
| `MPP_RPC_URL` | public Tempo RPC | No | Custom Tempo RPC endpoint. Used by `mpp-session` to open and settle the channel (`mpp-charge` needs no RPC). |
| `ONESOURCE_CONFIG_DIR` | `~/.onesource` | No | Directory holding the server-managed payment config (`batch-config.json`) that `1s_batch_config` reads and writes. |
The only variable you ever have to set yourself is the credential, `ONESOURCE_API_KEY` (or a wallet key: `X402_PRIVATE_KEY` for x402 on Base, or `MPP_PRIVATE_KEY` for MPP on Tempo). Everything else has a working default. The four batch knobs (`X402_PAYMENT_MODE`, `X402_DEPOSIT_MULTIPLIER`, `X402_BATCH_PROMPT`, `X402_BATCH_THRESHOLD`) don't need to be set as env vars at all: your assistant can set and persist them in-session with the [`1s_batch_config`](#configure-batch-behavior-from-your-assistant) tool. For each of those four, a value saved by `1s_batch_config` takes priority over the env var, which in turn beats the built-in default.
### Setting environment variables
:::tip Let the server configure itself
You rarely need to set these by hand. Once the server is installed with any credential, run the [`1s_setup_check`](./tool-reference#setup-and-ops-3) tool: it walks you through every option interactively, applies what it can live (rail, scheme, batch knobs), and hands you a single ready-to-run command for anything that needs a restart, without echoing secrets or editing config files. The manual reference below is for the initial install and for self-hosted (`--http`) deployments.
:::
How you pass env vars depends on your MCP client:
**Claude Code:**
```bash
claude mcp add onesource -e ONESOURCE_API_KEY=sk_… -- npx -y @one-source/mcp@latest
```
**Claude Desktop / Cursor / Windsurf:**
```json
{
"mcpServers": {
"onesource": {
"command": "npx",
"args": ["-y", "@one-source/mcp@latest"],
"env": { "ONESOURCE_API_KEY": "sk_…" }
}
}
}
```
**VS Code:**
```json
{
"servers": {
"onesource": {
"command": "npx",
"args": ["-y", "@one-source/mcp@latest"],
"env": { "ONESOURCE_API_KEY": "sk_…" }
}
}
}
```
**Shell (for `--http` mode or testing):**
```bash
ONESOURCE_API_KEY=sk_… npx -y @one-source/mcp@latest
```
---
## Transport modes
### Stdio (default)
The standard mode used by every MCP client. The server reads from stdin, writes to stdout, and runs for the lifetime of the session.
```bash
ONESOURCE_API_KEY=sk_… npx -y @one-source/mcp@latest
```
No additional flags: this is what every [client-specific guide](./install#client-specific-setup) sets up.
### HTTP (self-hosted)
For remote deployments, shared servers, or clients that support HTTP transport.
```bash
ONESOURCE_API_KEY=sk_… npx -y @one-source/mcp@latest --http
ONESOURCE_API_KEY=sk_… npx -y @one-source/mcp@latest --http --port=8080
```
**Endpoints:**
| Endpoint | Method | Description |
|---|---|---|
| `/mcp` | POST | MCP protocol handler |
| `/health` | GET | Returns server status, version, and tool count |
| `*` | OPTIONS | CORS preflight (allows cross-origin requests) |
**Port priority:** `PORT` env var → `--port` flag → default `3000`.
**Health check:**
```bash
curl http://localhost:3000/health
```
Notes:
- HTTP mode is stateless: each request gets a fresh server instance.
- The server binds to `0.0.0.0` for deployment compatibility.
- CORS is enabled for all origins.
---
## Batch settlement (payment channels)
In x402 mode, the server can pay through a **payment channel** instead of settling every call on-chain: one deposit funds many off-chain signed vouchers, which OneSource redeems in batched on-chain claims. For a burst of calls this amortizes settlement gas. See [x402 on Base → Batch settlement](../x402-and-mpp/x402-base#batch-settlement) for the model, and [Build an x402 batch-settlement client](/guides/x402-batch-settlement-client) for the equivalent standalone client.
Start in batch mode, or flip it mid-session with the `1s_payment_mode` tool:
```bash
X402_PRIVATE_KEY=0x… X402_PAYMENT_MODE=batch X402_CHANNEL_DIR=./channel-storage \
npx -y @one-source/mcp@latest
```
- **Requires the long-lived stdio transport** (the default). The `--http` transport is stateless (each request gets a fresh server instance), so it cannot hold channel state; batch mode there falls back to per-call behavior.
- **Set `X402_CHANNEL_DIR`** to persist the channel across restarts. Without it, channel state lives only in the running process and is lost on exit.
- The first paid call opens the channel with one on-chain deposit (`price × X402_DEPOSIT_MULTIPLIER`, default 10), so a session usually over-funds the channel.
- **Reclaim the unused deposit with the `1s_refund` tool** when you're done making batch calls: it returns the full remaining channel escrow to your wallet on Base right away. Idle channels are also auto-refunded by the gateway after a few hours, so the residual is always recoverable; `1s_refund` just gets it back immediately instead of waiting.
### Configure batch behavior from your assistant
The `1s_batch_config` tool lets your assistant read and change the batch preferences in-session, and persists them to a server-owned file (`batch-config.json` under `ONESOURCE_CONFIG_DIR`, default `~/.onesource`) so they survive restarts without touching the client config.
`1s_batch_config` covers these settings:
| Setting | Values | Default | What it controls |
|---|---|---|---|
| `mode` | `x402-exact` / `x402-batch` / `mpp-charge` / `mpp-session` | rail default | The default rail + scheme the session starts in. Also switched live when that rail is active. |
| `deposit_multiplier` | number ≥ 3 | `10` | x402 channel deposit = call price × this. |
| `mpp_max_deposit` | token amount | `1` | `mpp-session` channel deposit, in tokens (flat, not scaled by call price). |
| `prompt` | `ask` / `auto` / `off` | `ask` | How proactively the agent offers to switch to a channel mode. |
| `threshold` | integer > 0 | `5` | Anticipated call count at or above which a channel mode is worth considering. |
Call it with no arguments to see the current values and the config file path, with a patch (for example `{ "mode": "x402-batch", "deposit_multiplier": 20 }`) to change them, or pass `{ "reset": true }` to restore defaults (which deletes the file and falls back to env vars, then built-in defaults). A saved value takes priority over the matching env var. To switch the live payment scheme for the current session without persisting a default, use `1s_payment_mode` instead.
In x402 mode the agent receives batch guidance in its system prompt at startup, scaled by `prompt` and `threshold`: when it anticipates a burst of calls it can offer (or, on `auto`, perform) the switch to batch mode and remind you to `1s_refund` when finished. `1s_setup_check` reports your current mode, whether batch is available, and both settings.
---
## MPP settlement (Tempo)
With `MPP_PRIVATE_KEY` set, the server pays per call over [MPP on Tempo](../x402-and-mpp/mpp-tempo) instead of x402 on Base. MPP settles in USDC.e or pathUSD, and Tempo gas is materially cheaper than Base. It has two schemes, switchable mid-session with `1s_payment_mode`:
- **`mpp-charge`** (default): one signed Tempo payment per call. Simplest; needs no RPC.
- **`mpp-session`**: a voucher channel. The first call opens an on-chain channel for a flat deposit, `MPP_MAX_DEPOSIT` (default `1` token, not scaled by call price), then subsequent calls are signed off-chain as cumulative vouchers and settled together. Materially cheaper for a burst of calls.
Start in a given mode, or flip it mid-session with `1s_payment_mode`:
```bash
MPP_PRIVATE_KEY=0x… MPP_PAYMENT_MODE=session npx -y @one-source/mcp@latest
```
- The unspent `mpp-session` deposit is settled and reclaimed automatically when the server shuts down cleanly (`SIGINT` / `SIGTERM`), and you can also reclaim it on demand any time with the `1s_refund` tool. `1s_refund` is rail-neutral: it returns the remaining escrow of an open x402 `batch` channel on Base or an `mpp-session` channel on Tempo. A hard kill before either settles leaves the deposit locked on-chain until reclaimed later, so `MPP_MAX_DEPOSIT` bounds the worst case.
- Like x402 batch mode, `mpp-session` needs the long-lived stdio transport to hold channel state; under stateless `--http` it falls back to per-call (`mpp-charge`) behavior.
- x402 and MPP are separate rails with separate wallets (USDC on Base vs USDC.e / pathUSD on Tempo). If both keys are set, x402 is the initial rail, and `1s_payment_mode` switches between all four schemes.
---
## Networks
The OneSource REST API runs against Ethereum mainnet, the Sepolia testnet, and Robinhood Chain. Every chain tool takes an optional `network` argument; it defaults to `ethereum`. Pass `network: "sepolia"` or `network: "robinhood"` on any call to target another chain.
| Network | `network` value | EIP-155 chain id |
|---|---|---|
| Ethereum mainnet | `ethereum` (default) | `1` (`0x1`) |
| Sepolia testnet | `sepolia` | `11155111` (`0xaa36a7`) |
| Robinhood Chain | `robinhood` | `4663` (`0x1237`) |
Robinhood Chain is an Arbitrum Orbit L2 that settles to Ethereum. It differs from
Ethereum in ways that change how a result should be read: roughly 100ms blocks
instead of 12 seconds, no ENS registry, no public mempool, and `safe`/`finalized`
tracking L1 settlement rather than the L2 head. Call `1s_network_info` for the
chain you are on before interpreting a result. See
[Choosing a network](../direct-rest/first-request#choosing-a-network) for the full
table.
[`/api/chain/network-info`](/api-reference/chain-network-info) reports the live network list.
---
## Auth precedence
The server picks an auth mode at startup using this order:
1. `ONESOURCE_API_KEY` → Bearer mode (subscription API key).
2. `X402_PRIVATE_KEY` → wallet-paid mode, [x402 on Base](../x402-and-mpp/x402-base) (USDC).
3. `MPP_PRIVATE_KEY` → wallet-paid mode, [MPP on Tempo](../x402-and-mpp/mpp-tempo) (USDC.e / pathUSD).
4. None set → server starts but every paid tool call returns `Payment required (402)`. `1s_setup_check` reports `Status: Not configured`.
The API key always takes precedence over a wallet key. If both `X402_PRIVATE_KEY` and `MPP_PRIVATE_KEY` are set, x402 is the initial rail; switch to MPP in-session with `1s_payment_mode`.
:::note Wallet-paid modes are for the server you run yourself
Both wallet rails sign payments from a single private key the server holds, so they apply to the local (stdio) or self-hosted server you run. A shared, multi-tenant deployment holds no wallet and authenticates each request with a Bearer API key instead.
:::
---
## Configure Claude Code
[Claude Code](https://claude.com/claude-code) is Anthropic's terminal-based agent. It supports MCP servers via the `claude mcp` subcommand.
## Add the server
```bash
claude mcp add onesource -e ONESOURCE_API_KEY=sk_… -- npx -y @one-source/mcp@latest
```
This registers a stdio MCP server named `onesource`. Claude Code stores the config in your user-level `~/.claude.json` (or per-project `.claude/settings.json` if run from a project directory).
## Verify
In a Claude Code session:
```
> Call 1s_setup_check.
```
Expected: a tool-use block whose "Current configuration" reports the **active auth method** as `API key` (with the first 6 characters of your key), the server version, and that the service is reachable.
## Common scopes
- **User-scoped** (default with `claude mcp add`): the server is available in every Claude Code session you start.
- **Project-scoped**: prefix with `--scope project` to write to `.claude/settings.json` in the current directory. Useful if different projects need different API keys.
```bash
claude mcp add onesource --scope project -e ONESOURCE_API_KEY=sk_… -- npx -y @one-source/mcp@latest
```
## Remove or re-add
```bash
claude mcp remove onesource
claude mcp list # see what's registered
```
## Windows note
If `npx` fails to launch on your Windows configuration, wrap the command with `cmd /c`:
```bash
claude mcp add onesource -e ONESOURCE_API_KEY=sk_… -- cmd /c npx -y @one-source/mcp@latest
```
## Troubleshooting
- **"MCP server onesource already exists"**: run `claude mcp remove onesource` first, then re-add.
- **403 / wrong key**: a stale `ONESOURCE_API_KEY` in your shell rc file (`.zshrc`, `.bashrc`) can shadow the value Claude Code passed. Run `echo $ONESOURCE_API_KEY` outside Claude Code to check, then unset it in your shell or rotate the key in the MCP config.
- **`npx` slow to start**: first call downloads the package. Subsequent calls hit npm's local cache and start fast.
- **`npx` / `command not found`**: confirm Node.js is on PATH (`node --version`). On Windows, re-run the Node.js installer and make sure "Add to PATH" is checked, or restart your terminal after install.
## Next
- [Tool reference](./tool-reference)
- Same setup for [Claude Desktop](./configure-claude-desktop), [Cursor](./configure-cursor), [Windsurf](./configure-windsurf)
---
## Configure Claude Desktop
[Claude Desktop](https://claude.ai/download) reads MCP servers from a per-user JSON config file.
## Locate the config file
| OS | Path |
|---|---|
| macOS | `~/Library/Application Support/Claude/claude_desktop_config.json` |
| Windows | `%APPDATA%\Claude\claude_desktop_config.json` |
| Linux | `~/.config/Claude/claude_desktop_config.json` |
If the file doesn't exist, create it.
## Add the server
```json
{
"mcpServers": {
"onesource": {
"command": "npx",
"args": ["-y", "@one-source/mcp@latest"],
"env": { "ONESOURCE_API_KEY": "sk_…" }
}
}
}
```
If `mcpServers` already exists, add `"onesource"` as a new key inside it.
**Windows note:** if `npx` doesn't launch directly from JSON on your machine, swap the command form:
```json
"command": "cmd",
"args": ["/c", "npx", "-y", "@one-source/mcp@latest"],
```
## Restart Claude Desktop
Fully quit the app (Cmd-Q / right-click tray → Quit) and reopen. MCP servers are loaded on launch.
**What these fields mean:**
- `command`: the program to run (`npx` fetches and runs npm packages).
- `args`: arguments to that program (`-y` auto-confirms the install).
- `env`: environment variables passed through to the server process.
## Verify
Start a new chat and ask: *"Call `1s_setup_check`."* You should see a tool-use card whose "Current configuration" reports the **active auth method** as `API key` (with the first 6 characters of your key).
If you don't see the tool offered:
- Confirm `claude_desktop_config.json` is valid JSON (use a linter: a stray comma will silently break **all** servers, not just `onesource`).
- Check **Settings → Developer → MCP Logs** for boot errors.
- Fully quit Claude Desktop and reopen; closing the window isn't enough. macOS: Cmd+Q. Windows: right-click the tray icon → Quit.
## Troubleshooting
### Server not showing up
- **Bad JSON.** Validate the file at [jsonlint.com](https://jsonlint.com/).
- **Didn't fully restart.** Use Cmd+Q (macOS) or Quit-from-tray (Windows).
- **Node.js missing.** Run `node --version` in a terminal. If missing, install from [nodejs.org](https://nodejs.org/) (LTS).
### "Command not found" / "npx not found"
Node.js isn't on the system PATH. Restart your computer after installing Node.js. On Windows, re-run the installer with "Add to PATH" checked.
### Tool calls fail
- **Network.** The server has to reach `api.onesource.io`.
- **Smoke-test directly.** In a terminal: `ONESOURCE_API_KEY=sk_… npx -y @one-source/mcp@latest`. If it boots without errors, the package itself is healthy; the issue is in the Claude config. Ctrl+C to stop.
## Next
- [Tool reference](./tool-reference)
- Other clients: [Claude Code](./configure-claude-code), [Cursor](./configure-cursor), [Windsurf](./configure-windsurf)
---
## Configure Cursor
[Cursor](https://cursor.com) (0.45 or later) supports MCP servers through its native settings UI.
## Add the server (UI)
1. **Cursor → Settings → MCP**.
2. Click **+ Add new MCP server**.
3. Fill in:
- **Name**: `onesource`
- **Type**: `command`
- **Command**: `npx -y @one-source/mcp@latest`
- **Environment variables**: `ONESOURCE_API_KEY=sk_…`
4. Click **Save**.
## Add the server (config file)
Alternatively, edit `~/.cursor/mcp.json` for a global config that applies to every project, or `.cursor/mcp.json` in a project root for a per-project config (create if absent):
```json
{
"mcpServers": {
"onesource": {
"command": "npx",
"args": ["-y", "@one-source/mcp@latest"],
"env": { "ONESOURCE_API_KEY": "sk_…" }
}
}
}
```
## Verify
Open Composer or Chat and ask: *"Call `1s_setup_check`."* The tool card's "Current configuration" should report the **active auth method** as `API key` (with the first 6 characters of your key).
## Troubleshooting
- **Cursor doesn't see the server**: hit the refresh button on the MCP settings page or restart the app. New env vars require a restart.
- **403 / wrong key**: see [Claude Code](./configure-claude-code#troubleshooting); same shadowing pattern applies.
- **Some `onesource` tools missing in Composer**: Cursor caps active tools across all MCP servers at ~40. If you have multiple MCPs configured, disable the ones you aren't using on the MCP settings page.
- **Invalid JSON**: a stray comma or bracket in `mcp.json` silently prevents the server from loading. Validate with a linter.
## Next
- [Tool reference](./tool-reference)
- Other clients: [Claude Code](./configure-claude-code), [Claude Desktop](./configure-claude-desktop), [Windsurf](./configure-windsurf)
---
## Configure VS Code
VS Code reaches MCP servers through GitHub Copilot: they appear as callable tools in Copilot Chat when you're in **Agent** mode.
## Prerequisites
- VS Code with the **GitHub Copilot** extension installed.
- Node.js 18+.
## Add the server
Create or edit `.vscode/mcp.json` in your workspace root:
```json
{
"servers": {
"onesource": {
"command": "npx",
"args": ["-y", "@one-source/mcp@latest"],
"env": { "ONESOURCE_API_KEY": "sk_…" }
}
}
}
```
:::caution
VS Code uses `"servers"` as the top-level key. Claude Desktop, Cursor, and Windsurf all use `"mcpServers"`; copying a config across clients without changing this key will silently fail.
:::
You can also reach this file from the Command Palette: **Ctrl+Shift+P** (Cmd+Shift+P on macOS) → **MCP: Add Server**.
## Workspace vs user scope
- **Workspace** (the example above): `.vscode/mcp.json` in your workspace root. The server is only available in that workspace.
- **User**: Command Palette → **MCP: Open User Configuration**. The server is available across every workspace.
## Verify
Open Copilot Chat, switch to **Agent** mode, and prompt: *"Call `1s_setup_check`."* You should see a tool card whose "Current configuration" reports the **active auth method** as `API key` (with the first 6 characters of your key).
## With x402 (wallet-paid)
Swap `ONESOURCE_API_KEY` for an Ethereum private key:
```json
{
"servers": {
"onesource": {
"command": "npx",
"args": ["-y", "@one-source/mcp@latest"],
"env": { "X402_PRIVATE_KEY": "0x…" }
}
}
}
```
For MPP on Tempo instead, set `MPP_PRIVATE_KEY` in place of `X402_PRIVATE_KEY`. See [x402 on Base](../x402-and-mpp/x402-base) and [MPP on Tempo](../x402-and-mpp/mpp-tempo) for funding details.
## Next
- [Tool reference](./tool-reference)
- Other clients: [Claude Code](./configure-claude-code), [Claude Desktop](./configure-claude-desktop), [Cursor](./configure-cursor), [Windsurf](./configure-windsurf)
---
## Configure Windsurf
[Windsurf](https://windsurf.com) reads MCP servers from a per-user JSON config file.
## Locate the config file
| OS | Path |
|---|---|
| macOS / Linux | `~/.codeium/windsurf/mcp_config.json` |
| Windows | `%USERPROFILE%\.codeium\windsurf\mcp_config.json` |
You can also reach the file from inside Windsurf via the MCPs icon in the Cascade panel → **Configure**.
## Add the server
Create or edit the config file:
```json
{
"mcpServers": {
"onesource": {
"command": "npx",
"args": ["-y", "@one-source/mcp@latest"],
"env": { "ONESOURCE_API_KEY": "sk_…" }
}
}
}
```
## Restart Cascade
In Windsurf, open the Cascade panel and click the refresh icon next to the MCP servers list. New servers and changed env vars require this refresh (or a full Windsurf restart).
## Verify
Open Cascade and prompt: *"Call `1s_setup_check`."* Expected: a tool card whose "Current configuration" reports the **active auth method** as `API key` (with the first 6 characters of your key).
## Per-tool toggling
Windsurf lets you enable or disable individual tools within an MCP server from the Cascade MCP panel: useful if you want, say, only the blockchain data tools and not `1s_report_bug`.
## Next
- [Tool reference](./tool-reference)
- Other clients: [Claude Code](./configure-claude-code), [Claude Desktop](./configure-claude-desktop), [Cursor](./configure-cursor)
---
## Docs MCP
Eight tools let an assistant **read about** OneSource: search the docs, list endpoints, look up parameters and pricing, find endpoints by use case. They are free, read-only, and need no credential.
[`@one-source/mcp`](./install) already includes them, so if you have the main server installed there is nothing else to add. They answer "how do I use the OneSource REST API?" while its chain tools answer "what's the current ETH balance of vitalik.eth?"
**`@one-source/docs-mcp`** ships the same eight on their own, for when you want documentation help and nothing else: no auth, no payment, no live data, no wallet.
| Package | What it does | Auth |
|---|---|---|
| [`@one-source/mcp`](./install) | Calls live OneSource endpoints, and searches the docs | API key, x402, or MPP (the documentation tools stay free) |
| [`@one-source/docs-mcp`](https://www.npmjs.com/package/@one-source/docs-mcp) | Searches OneSource docs offline | None |
## Installing the docs server on its own
Reach for this when the assistant should read about OneSource without being able to call the chain or spend anything.
### Hosted endpoint (zero setup)
The docs MCP is also hosted as a stateless HTTP endpoint. Point any MCP client that supports HTTP transport at:
```
https://docs.onesource.io/api/mcp
```
No install, no auth. Best path for clients that support remote MCP servers.
### Local install
**Claude Code:**
```bash
claude mcp add onesource-docs -- npx -y @one-source/docs-mcp
```
**Claude Desktop / Cursor / Windsurf:**
```json
{
"mcpServers": {
"onesource-docs": {
"command": "npx",
"args": ["-y", "@one-source/docs-mcp"]
}
}
}
```
**VS Code:**
```json
{
"servers": {
"onesource-docs": {
"command": "npx",
"args": ["-y", "@one-source/docs-mcp"]
}
}
}
```
**Any MCP client (stdio):** `npx -y @one-source/docs-mcp`.
## Tools
| Tool | Purpose |
|---|---|
| `1s_search_docs` | Keyword search across all OneSource docs |
| `1s_get_api_overview` | Operation counts, tags, networks, sample endpoints |
| `1s_list_endpoints` | List endpoints (optional tag filter): method, path, price, summary |
| `1s_get_endpoint_reference` | Full reference for one endpoint: parameters, request body, example response, curl |
| `1s_search_use_cases` | Find endpoints by natural-language description |
| `1s_list_networks` | Supported networks |
| `1s_get_payment_info` | x402 / MPP protocols, prices, pay-to addresses |
| `1s_get_authentication_guide` | Bearer / x402 / MPP auth: code examples |
The data is bundled from the committed OpenAPI snapshot and the Markdown sources of this site. Publishing is gated on both bundled copies being current, and there are no network calls at runtime.
## Registering it alongside the main server
`@one-source/mcp` carries these eight tools already, so most setups need only that one entry. Registering both is still valid, and they do not conflict, but your assistant then sees each documentation tool twice:
```json
{
"mcpServers": {
"onesource": {
"command": "npx",
"args": ["-y", "@one-source/mcp@latest"],
"env": { "ONESOURCE_API_KEY": "sk_…" }
}
}
}
```
## Also see
- [Tool reference](./tool-reference): every tool the main server exposes, documentation tools included.
- [AI editor setup guide](/guides/ai-editor-setup): uses a generic `mcpdoc`-based docs server reading `llms.txt`. Lighter-weight; trades the structured tools above for plain keyword search.
---
## Install the MCP server
The OneSource REST API ships with an official MCP (Model Context Protocol) server. Any MCP-compatible AI assistant (Claude Code, Claude Desktop, Cursor, Windsurf) can call OneSource endpoints with natural language. The server runs locally via `npx`, authenticates with your API key, and exposes tools for balances, NFTs, transactions, events, contracts, and chain utilities, plus eight free documentation tools that answer questions about the API before any credential is configured.
- **Package:** [`@one-source/mcp`](https://www.npmjs.com/package/@one-source/mcp)
- **Source:** https://github.com/blockparty-global/1s-mcp
## Prerequisites
- Node.js 18+
- A credential: either a OneSource API key from your [subscription](../api-key/retrieve-api-key) (keys start with `sk_`), or a wallet private key for pay-per-call access via [x402 on Base](../x402-and-mpp/x402-base) or [MPP on Tempo](../x402-and-mpp/mpp-tempo).
## Client-specific setup
- [Claude Code](./configure-claude-code)
- [Claude Desktop](./configure-claude-desktop)
- [Cursor](./configure-cursor)
- [Windsurf](./configure-windsurf)
- [VS Code (GitHub Copilot)](./configure-vscode)
## Any MCP client (generic stdio)
For any MCP-compatible client not listed above, the server runs over stdio by default:
```bash
ONESOURCE_API_KEY=sk_… npx -y @one-source/mcp@latest
```
Or, as a JSON configuration block:
```json
{
"command": "npx",
"args": ["-y", "@one-source/mcp@latest"],
"env": { "ONESOURCE_API_KEY": "sk_…" }
}
```
The server communicates over stdin/stdout. Diagnostics go to stderr; most clients capture them separately.
## Self-hosted HTTP transport
If you want to run the MCP server as a long-lived HTTP service (multiple agents share a single instance, or you want to expose it over your network):
```bash
ONESOURCE_API_KEY=sk_… npx -y @one-source/mcp@latest --http --port=3000
```
Point MCP clients at `http://localhost:3000/mcp`.
**Endpoints:**
| Endpoint | Method | Description |
|---|---|---|
| `/mcp` | POST | MCP protocol handler |
| `/health` | GET | Returns server status, version, and tool count |
| `*` | OPTIONS | CORS preflight (allows cross-origin requests) |
**Health check:**
```bash
curl http://localhost:3000/health
```
**Port priority:** `PORT` env var (used by hosting platforms like Railway and Fly.io) → `--port` flag → default `3000`.
## Verify
After your assistant restarts, ask it: *"Call `1s_setup_check`."* On a healthy install its "Current configuration" reports your **active auth method** as `API key` (with the first 6 characters of your key) and confirms the server version and that the service is reachable. If it shows the auth method as *none* (blockchain tools locked), double-check the env var and reload the client.
## Next
- [Configure-by-client guides](./configure-claude-code)
- [Configuration reference](./configuration): full env-var and transport-mode reference
- [Tool reference](./tool-reference): what each tool does
---
## MCP tool reference
`@one-source/mcp` exposes 97 tools (49 of them `1s_std_*`, including `1s_std_decision_plan`, `1s_std_decision_stake`, `1s_std_decision_hold`, `1s_std_decision_sbill`, and `1s_std_decision_bid`), all prefixed with `1s_` so they sort together in your client. Live-chain tools end in `_live`; chain utilities don't. Each chain tool maps 1:1 to a OneSource REST API endpoint and inherits its pricing; see the [API Reference](/api-reference/) for full parameter and response schemas. The eight `1s_ds_*` tools work the same way against [Deepstate Market Data](/deepstate/) instead, and the `1s_std_*` tools against [The Standard Reserve](/standard/).
Running the server over HTTP exposes 95 of them: `1s_payment_mode` and `1s_refund` change process-level payment state, which is not safe to share between callers, so they are stdio-only.
## Live chain data (12)
| Tool | Endpoint | What it returns |
|---|---|---|
| `1s_multi_balance_live` | `/api/chain/live-balance` | ETH plus up to 20 caller-supplied ERC20 balances in one batched call |
| `1s_erc20_balance_live` | `/api/chain/erc20-balance` | Single ERC20 balance with name/symbol/decimals |
| `1s_erc1155_balance_live` | `/api/chain/erc1155-balance` | ERC1155 token balance for an account |
| `1s_nft_owner_live` | `/api/chain/nft-owner` | NFT owner via `ownerOf` |
| `1s_nft_metadata_live` | `/api/chain/nft-metadata` | NFT metadata with IPFS / Arweave / `data:` URI resolution |
| `1s_erc721_tokens_live` | `/api/chain/erc721-tokens` | Token metadata batch for an ERC721 contract |
| `1s_erc20_transfers_live` | `/api/chain/erc20-transfers` | ERC20 Transfer logs |
| `1s_events_live` | `/api/chain/events` | Event logs via `eth_getLogs` |
| `1s_total_supply_live` | `/api/chain/total-supply` | ERC20 total supply |
| `1s_allowance_live` | `/api/chain/allowance` | ERC20 allowance check |
| `1s_tx_details_live` | `/api/chain/tx/{hash}` | Transaction + receipt |
| `1s_contract_info_live` | `/api/chain/contract/{address}` | Name/symbol/decimals + ERC-standard detection (ERC-165) |
### The token list on `1s_multi_balance_live`
`tokens` takes at most 20 ERC20 contract addresses. Past 20 the call is rejected rather than
truncated, so split a longer list across several calls.
The tool reads the contracts you name. It does not discover what a wallet holds and does not
total the balances into a portfolio value, so a token you leave out of `tokens` is absent from
the response rather than reported as zero. An error on one token comes back against that token,
leaving the rest of the batch intact.
### Paging the log tools
`1s_erc20_transfers_live` and `1s_events_live` can exceed the MCP response limit (100,000 characters) in far fewer blocks than you'd expect, especially on a fast chain or a busy contract. When that happens you still get a usable result rather than an error:
| Field | Meaning |
|---|---|
| `truncated` | `true` when the page was cut short |
| `returned` / `total` | Entries in this page, and entries the call actually found |
| `next_from_block` | First block **not** included; pass it back as `from_block` to continue |
Pages are cut on block boundaries, so walking `next_from_block` skips nothing and repeats nothing. Do that rather than retrying the same window with a smaller range: the first call already fetched and billed for the full result, so an overlapping retry pays twice for data you were already given.
One case can't be resumed: a single block whose logs exceed the limit on their own. You get what fits plus `partial_block` and deliberately no cursor, because `/api/chain/events` has no log-index offset to resume from, and a cursor there would promise gapless paging it can't deliver.
This paging is a property of the MCP server. Calling `/api/chain/events` over REST returns the full result set with no cap of its own, though the upstream node's 20,000-log limit still applies: see [Batching and cost optimization](../../guides/batching-and-cost-optimization#6-batch-event-log-queries-by-topic--block-range).
## Chain utilities (13)
| Tool | Endpoint | What it returns |
|---|---|---|
| `1s_network_info` | `/api/chain/network-info` | Chain ID, latest block, gas price (batched) |
| `1s_chain_id` | `/api/chain/chain-id` | EIP-155 chain ID |
| `1s_block_number` | `/api/chain/block-number` | Latest block number |
| `1s_block_by_number` | `/api/chain/block/{number}` | Block header + transaction hashes |
| `1s_pending_block` | `/api/chain/pending` | Pending block from the mempool |
| `1s_contract_code` | `/api/chain/code/{address}` | Contract bytecode |
| `1s_nonce` | `/api/chain/nonce/{address}` | Transaction count for an address |
| `1s_storage_read` | `/api/chain/storage` | Read an arbitrary storage slot |
| `1s_tx_receipt` | `/api/chain/receipt/{hash}` | Transaction receipt |
| `1s_simulate_call` | `/api/chain/call` | Simulate `eth_call` (read-only contract call) |
| `1s_estimate_gas` | `/api/chain/estimate-gas` | Gas estimate for a transaction |
| `1s_ens_resolve` | `/api/chain/ens/{input}` | Forward ENS resolution |
| `1s_proxy_detect` | `/api/chain/proxy/{address}` | Upgradeable-proxy implementation detection (EIP-1967, UUPS, Transparent) |
## Deepstate market data (8)
Deepstate is an on-chain order book protocol on Robinhood Chain. These tools read its order books, trades, candles, stats, and analytics; none take a `network` parameter, since Deepstate data is always Robinhood Chain. See [Deepstate Market Data](/deepstate/) for pricing, response shape, and full endpoint detail.
| Tool | Endpoint | What it returns |
|---|---|---|
| `1s_ds_markets` | `/deepstate/v1/markets` | The configured Deepstate markets, with token layout and pool/router addresses |
| `1s_ds_book` | `/deepstate/v1/book/{book}` | Order-book snapshot: bids descending, asks ascending |
| `1s_ds_trades` | `/deepstate/v1/trades/{book}` | Trade tape, newest first |
| `1s_ds_candles` | `/deepstate/v1/candles/{book}` | OHLCV candles |
| `1s_ds_stats` | `/deepstate/v1/stats/{book}` | Rolling 24h/7d/30d volume and price change |
| `1s_ds_makers` | `/deepstate/v1/analytics/makers` | Per-maker time-at-top, resting notional, fills, and maker rewards earned (token varies by market; see `1s_ds_markets`) |
| `1s_ds_cost_to_quote` | `/deepstate/v1/analytics/cost-to-quote` | Gas spent resting and cancelling orders |
| `1s_ds_depth_history` | `/deepstate/v1/analytics/depth-history` | Depth heatmap: resting size by price level over time |
## The Standard Reserve (48)
The Standard Reserve ("STANDARD") is an on-chain central-bank protocol on Robinhood Chain. These tools read its charters, branches, auctions, epochs, exit economics, backing, and participants; none take a `network` parameter, since Standard Reserve data is always Robinhood Chain. See [The Standard Reserve](/standard/) for the full protocol writeup, pricing, and [How to read a TSR response](/standard/reading-a-response) for what `basis`, `serving_state`, and `_raw` mean below. `1s_std_addresses` is free; every other tool here is paid the same way as the matching REST route.
Eight of these, `1s_std_decision_branch`, `1s_std_decision_exit`, `1s_std_decision_charter`, `1s_std_decision_plan`, `1s_std_decision_stake`, `1s_std_decision_hold`, `1s_std_decision_sbill`, and `1s_std_decision_bid`, are composite tools that bundle several of the individual routes below into one call. [Deciding with the data](/standard/deciding-with-the-data) maps common questions (exit, branch cost, policy, staking, holding, one S-Bill, a license bid, backing, participants) to the tool you actually want.
| Tool | Endpoint | What it returns |
|---|---|---|
| `1s_std_addresses` | `/v1/addresses` | The verified address registry: check this before trusting or paying any other Standard Reserve contract (free) |
| `1s_std_charter` | `/v1/charters/{id}` | One charter: holder, branch count, status |
| `1s_std_exit_quote` | `/v1/charters/{id}/exit-quote` | Pro-rata exit quote for n of a charter's open branches, with `realizable_eth` and an `at_block` what-if |
| `1s_std_exit_fee_curve` | `/v1/exit/fee-curve` | How the exit fee would move at larger or smaller withdrawal sizes than the current one |
| `1s_std_exit_fee_forecast` | `/v1/exit/fee-forecast` | Current-pace projection of the system-wide exit fee assuming no further exits |
| `1s_std_exit_pressure` | `/v1/exit-pressure/current` | Current exit-pressure snapshot and resolution-fee rate |
| `1s_std_branches_summary` | `/v1/branches/summary` | Branch count, yield per branch/day, license price in branch-days |
| `1s_std_branches_doi` | `/v1/branches/doi` | Days of Issuance: what the license price buys in issuance terms |
| `1s_std_license_cost` | `/v1/branches/license-cost` | Today's license price converted to an estimated ETH cost, compared against the charter auction |
| `1s_std_license_headroom` | `/v1/charters/{id}/license-headroom` | How many more licenses this charter can still buy today, with a live quote for each |
| `1s_std_branch_auction_live` | (live) | Raw, head-fresh state of the Branch license auction |
| `1s_std_auctions_current` | `/v1/auctions/current` | Current Dutch-auction quotes for both the license and charter auctions, with phase/sold-out/units fields |
| `1s_std_auction_days` | `/v1/auctions/days` | Per-day auction history: open, floor, close, sold vs. offered, sellout time |
| `1s_std_license_bids` | `/v1/auctions/license/bids` | The open-bid book for the license auction, read at one block: bidder, count, max unit price, the contract's `fillable` answer, a `blocking` list per bid, stale or prunable flags, and a derived effective crossing price. Optional `charter_id`, `bidder`, `fillable_only`, `sort` |
| `1s_std_auction_sales` | `/v1/auctions/sales` | Recent individual auction sales |
| `1s_std_epochs` | `/v1/epochs` | Epoch history: net flow, signal, regime, multiplier, issuance |
| `1s_std_policy_current` | `/v1/policy/current` | The latest epoch, live-estimated before it settles |
| `1s_std_policy_outlook` | `/v1/policy/outlook` | Current-sign-holds projection of the multiplier and issuance rate |
| `1s_std_issuance_runway` | `/v1/issuance/runway` | Cumulative issuance vs. budget and a same-rate projected end date |
| `1s_std_governance_changes` | `/v1/governance/changes` | Param-change history, queued changes, switches, guardian pause state |
| `1s_std_protocol_flows` | `/v1/flows/protocol` | Protocol money movements, newest first: migrations, forwarded taxes, recycled fees, team claims, POL buybacks, fees and pairings, incentive sends, and outflows from the reserve vault Safe. Filter with `kind`, page with `cursor` |
| `1s_std_backing` | `/v1/backing/current`, `/history` | Vault ETH and reserve holdings valued per circulating STANDARD |
| `1s_std_buyback_readiness` | `/v1/buybacks/readiness` | Whether a buyback tick could fire right now: capacity, cooldown, TWAP deviation |
| `1s_std_tax_schedule` | `/v1/tax/schedule` | Current launch buy/sell tax bps and the decay schedule |
| `1s_std_supply` | `/v1/supply/current` | STANDARD supply ledger: circulating, minted, burned |
| `1s_std_vaults` | `/v1/vaults/current` | Expansion and Contraction vault balances |
| `1s_std_pool` | `/v1/pool` | Latest ETH/STANDARD Uniswap v4 pool state |
| `1s_std_candles` | `/v1/pool/candles` | OHLC price candles for the ETH/STANDARD pool |
| `1s_std_flow_hourly` | `/v1/flow/hourly` | Hourly ETH flow into and out of the pool |
| `1s_std_flow_charters` | `/v1/flow/charters` | Pool buys and sells grouped by wallets that held a Charter at each swap's exact block/log position; ambiguous transactions are excluded |
| `1s_std_holders_concentration` | `/v1/holders/concentration` | Branch-per-charter distribution, top owners, HHI concentration score, cohort split |
| `1s_std_wallet` | `/v1/wallets/{address}` | One wallet's full position: charters, pending owed, dormancy, license headroom |
| `1s_std_dormancy` | `/v1/dormancy` | Wallets past their reportable dormancy window |
| `1s_std_dormancy_bounties` | `/v1/dormancy/bounties` | Same, ranked by estimated bounty |
| `1s_std_decision_branch` | `/v1/decisions/branch` | Everything for "should I buy a branch or license right now," bundled |
| `1s_std_decision_exit` | `/v1/decisions/exit` | Everything for "should I exit this charter's branches right now," bundled, plus what the net STANDARD would earn staked as an S-Bill for one term (`parts.sbills_alternative`) |
| `1s_std_decision_charter` | `/v1/decisions/charter` | Everything for "should I buy into a new charter right now," bundled, including the charter auction's price path for the rest of the day (`charter.expected_prices`) |
| `1s_std_decision_plan` | `/v1/decisions/plan` | Simulates keep/selective/aggressive Branch-expansion strategies over a horizon you choose, seeded from live license price, pool price, issuance rate, and branch counts. Each strategy also prices staking its released STANDARD as an S-Bill instead of selling it (`stake_instead_of_sale`) |
| `1s_std_decision_stake` | `/v1/decisions/stake` | The inputs for staking STANDARD into an S-Bill, in one call: the live quote for `amount`, current S-Bills state, optional `wallet` cap headroom, and a derived 1h/6h/24h rate projection that assumes no other deposits |
| `1s_std_decision_sbill` | `/v1/decisions/sbill` | The figures for one S-Bill by `bill_id`. Active bill: the exit payout now (measured), the premium an early exit gives up, and a derived exit fee for each coming day. Matured bill: the redeem payout (measured) and what rolling would lock (derived; only the bill's owner can roll, and nothing rolls automatically) |
| `1s_std_decision_hold` | `/v1/decisions/hold` | The inputs for holding STANDARD, in one call: policy outlook, issuance runway, pending governance changes, tax schedule, buyback readiness, and the reportable dormancy count. Takes no arguments |
| `1s_std_decision_bid` | `/v1/decisions/bid` | The inputs for a standing license bid at a max price, in one call: when the price reaches it (derived), the bids and licenses ahead and the supply left (derived), with `charter_id` that charter's pending, caps, and any bid a new one would replace (measured), and the measured fill history. A price crossing is not a fill, since `fill()` is a separate transaction anyone may send. Requires `max_unit_price` |
| `1s_std_genesis_live` | (live) | Head-fresh stats on the genesis Dutch mint |
| `1s_std_events` | `/v1/events` | Raw decoded protocol events, newest first |
| `1s_std_sbills_current` | `/v1/sbills/current` | Current S-Bills staking terms: rate per term and simple APR at the live term length, drift state, principal locked, caps, pause/close flags, and the early-exit fee window inputs. The contract has no automatic roll; the owner must call roll after maturity |
| `1s_std_sbills_rate_history` | `/v1/sbills/rate-history` | How the S-Bill rate has moved, with the term length in force at each point |
| `1s_std_sbills_quote` | `/v1/sbills/quote` | Live quote for staking a given amount of STANDARD into an S-Bill: rate locked, premium, rate after, and which caps would block it. Optional `wallet` adds that wallet's open principal and cap headroom |
| `1s_std_sbills_bills` | `/v1/sbills/bills`, `/v1/sbills/bills/{id}` | One S-Bill or a filtered list; the single-bill form adds live exit and redeem quotes. The contract has no automatic roll; the owner must call roll after maturity |
| `1s_std_sbills_events` | `/v1/sbills/events` | Decoded S-Bills activity feed: deposits, early exits, redemptions, rolls, settlements, rate changes |
## Documentation (8)
These read the bundled OneSource documentation corpus. They are free, read-only, and need no credential, so they answer "how do I use this API?" while the chain tools are still waiting on a key or a wallet.
| Tool | Purpose |
|---|---|
| `1s_search_docs` | Keyword search across the OneSource documentation |
| `1s_get_api_overview` | What the REST API covers: operation count, tags, networks, payment protocols |
| `1s_list_endpoints` | Every REST endpoint with method, path, price, and summary (optional tag filter) |
| `1s_get_endpoint_reference` | Full reference for one endpoint: parameters, request body, example response, payment, curl |
| `1s_search_use_cases` | Find endpoints by natural-language description |
| `1s_list_networks` | Networks the API routes, read from the spec |
| `1s_get_payment_info` | x402 / MPP protocols, prices, pay-to addresses, globally or per endpoint |
| `1s_get_authentication_guide` | Bearer / x402 / MPP auth, with code examples and when to use which |
For documentation help without the chain tools, the same eight ship standalone as [`@one-source/docs-mcp`](./docs-mcp).
## Payments (2)
These two tools only do something in wallet-paid mode (`X402_PRIVATE_KEY` or `MPP_PRIVATE_KEY` set). With an API key, calls are covered by your plan, so they report that there is nothing to switch or refund.
| Tool | Purpose |
|---|---|
| `1s_payment_mode` | View or switch the rail + scheme the session pays with: `x402-exact` / `x402-batch` ([x402 on Base](../x402-and-mpp/x402-base), USDC) or `mpp-charge` / `mpp-session` ([MPP on Tempo](../x402-and-mpp/mpp-tempo), USDC.e / pathUSD). `*-exact` / `*-charge` settle per call; `*-batch` / `*-session` open a channel where one deposit funds many calls. Call with no arguments to see the current mode. |
| `1s_refund` | Reclaim the unused channel deposit back to your wallet, on demand. Rail-neutral: it settles and returns the full remaining escrow of an open x402 `batch` channel (on Base) or an `mpp-session` voucher channel (on Tempo). For x402 this beats waiting for the gateway's automatic idle refund; for MPP it's the manual reclaim path alongside the automatic settle on clean shutdown. |
## Setup and ops (3)
These tools need no authentication, so they work before you've configured a key.
| Tool | Purpose |
|---|---|
| `1s_setup_check` | Interactive setup and health check. **Call this first** in any session: it asks whether you want to just review your current config or set up / change it, then for a change it walks you through choosing an auth method (API key) or payment rail (x402 on Base / MPP on Tempo) and every related option one decision at a time. It applies what it can live (via `1s_payment_mode` / `1s_batch_config`) and hands you a single ready-to-run command for anything needing a restart (such as a wallet key), never asking you to paste a secret into the chat or hand-edit config files. It also confirms the credential is being read, the service is reachable, the server version (with an update hint if a newer one is published), and, in a wallet-paid mode, the wallet address and current x402 batch or MPP session state. |
| `1s_batch_config` | View or change the payment preferences (autonomy, "burst" threshold, x402 deposit multiplier, MPP session deposit cap, default rail + mode) from your assistant and persist them across restarts. No config-file editing or env vars required. Wallet-paid mode only: under API-key auth the server tells the assistant not to call it, since there are no payment rails to configure. See [Configuration → Configure batch behavior from your assistant](./configuration#configure-batch-behavior-from-your-assistant). |
| `1s_report_bug` | File a bug report from inside your assistant. Captures recent tool calls and the request context. |
## Example prompts
The assistant picks the right tool automatically:
- *"What's the USDC balance of vitalik.eth?"* → `1s_ens_resolve` + `1s_erc20_balance_live`
- *"Show the last 10 ERC20 transfers out of 0xabc…"* → `1s_erc20_transfers_live`
- *"Resolve the metadata for Bored Ape #4521."* → `1s_nft_metadata_live`
- *"Decode the receipt for transaction 0xdef… and tell me which events fired."* → `1s_tx_receipt`
- *"Is 0x123… a proxy contract? If so, what's the implementation address?"* → `1s_proxy_detect`
## Network
Ethereum mainnet (default), the Sepolia testnet, and Robinhood Chain. Every chain tool takes an optional `network` parameter; it defaults to `ethereum`. Pass `network: "sepolia"` or `network: "robinhood"` to target another chain. Robinhood Chain is an Arbitrum Orbit L2 with no ENS registry and no public mempool, so call `1s_network_info` before interpreting a result there. See [Configuration → Networks](./configuration#networks) for details.
The eight `1s_ds_*` Deepstate tools always operate on Robinhood Chain and take no `network` parameter at all.
## Pricing
Per-call cost for the chain tools is published in the response `meta.cost_usdc` field and on each operation's [API Reference](/api-reference/) page under `x-payment-info`. Bearer-key subscribers pay nothing per call (your monthly quota covers it); wallet-paid users (x402 or MPP) pay $0.001 to $0.02 per call, depending on the tool.
The `1s_ds_*` Deepstate tools are priced flat instead: $0.005 per call, except the three analytics tools (`1s_ds_makers`, `1s_ds_cost_to_quote`, `1s_ds_depth_history`) at $0.02. See [Deepstate Market Data → Pricing](/deepstate/#pricing).
The `1s_std_*` Standard Reserve tools are priced flat at $0.02 per call, except `1s_std_addresses` (free) and the eight `1s_std_decision_*` tools (`branch`, `exit`, `charter`, `plan`, `stake`, `hold`, `sbill`, and `bid`), which are $0.05 to reflect the several routes each one bundles or the simulation each one runs. The [OpenAPI spec](https://api.onesource.io/standard/openapi.yaml) is the source of truth for exact current pricing; it can't drift out of sync with what's actually charged the way a hardcoded number on this page could.
---
## MPP on Tempo
**MPP** (Multi-Party Payments) is an IETF-aligned HTTP payment protocol that uses standard `WWW-Authenticate` / `Authorization` headers ([RFC 7235](https://datatracker.ietf.org/doc/html/rfc7235)) instead of x402's custom `Payment-Required` flow. The headers survive proxies, work with `curl`, and don't conflict with regular HTTP auth schemes.
OneSource supports MPP on the [Tempo](https://tempo.xyz) network, and accepts **both USDC.e and pathUSD** as settlement currencies. It offers two payment modes:
- **Single payment**: one charge settled per call. The default; simplest to integrate.
- **Sessions**: open a channel once, pay many calls under it, close to settle in a single transaction. Materially cheaper for high call volumes.
Which currency you can pay in depends on your client: see [Currencies and client support](#currencies-and-client-support).
## Single payment (one charge per call)
Each call settles on its own:
1. Your client calls a OneSource endpoint without any `Authorization` header.
2. OneSource returns `HTTP 402` with one or more `WWW-Authenticate: Payment` headers; each carries the price, `payTo`, network, and currency.
3. Your client picks a challenge and constructs an MPP credential: a signed payload covering the price + recipient + nonce.
4. Your client retries the call with `Authorization: Payment `.
5. OneSource verifies the credential, settles on Tempo, and returns the response plus a `Payment-Receipt` header.
Same wallet model as x402; different network and protocol header format.
The response is released only after the transfer confirms on Tempo. If the transfer was broadcast but settlement didn't complete, you get `503` `settlement_unconfirmed` with the transaction hash instead of the data. Don't pay again for that request; see [Settlement errors](/guides/error-codes#settlement-errors-after-a-paid-call).
## Currencies and client support
OneSource accepts two currencies on mainnet, and emits a challenge for **both** on every single-payment 402 (USDC.e first, then pathUSD):
| Currency | Contract | Notes |
|---|---|---|
| **USDC.e** | `0x20c000000000000000000000b9537d11c60e8b50` | The default everywhere. Standard ERC20 stablecoin on Tempo. |
| **pathUSD** | `0x20c0000000000000000000000000000000000000` | Native Tempo stablecoin. See client support below. |
OneSource emits both challenges; which one gets paid is client behavior:
- A client that takes the **first** challenge without iterating pays in USDC.e. This is why OneSource lists USDC.e first.
- Paying in **pathUSD** requires a client that iterates the challenge list and signs from an ECDSA-generated wallet keypair; the [mpp-go SDK](#custom-client-mpp-go) does both.
- Your client's own documentation is the authority on which currencies it speaks.
Sessions are **USDC.e only** regardless of client: see [Sessions](#sessions).
## Using MPP-aware clients
MPP is a direct-HTTP protocol: you reach it by pointing an MPP-capable client at a OneSource REST endpoint, and it negotiates the 402 for you. Any of the clients below works.
:::note The OneSource MCP Server speaks MPP too
The [`@one-source/mcp`](../mcp/install) runtime server pays with MPP on Tempo when you give it an `MPP_PRIVATE_KEY` (USDC.e / pathUSD), alongside x402 on Base via `X402_PRIVATE_KEY`. It offers `mpp-charge` (per-call) and `mpp-session` (voucher channel) modes, switchable with `1s_payment_mode`; see [MCP → MPP settlement](../mcp/configuration#mpp-settlement-tempo). The direct-HTTP clients below are the way to pay with MPP from anything other than the MCP server.
:::
### AgentCash
[AgentCash](https://agentcash.dev) speaks MPP alongside x402: point it at a OneSource endpoint and it picks the right protocol from the 402 challenge. Nothing to configure. It takes the first challenge offered, so against OneSource it pays in **USDC.e**; AgentCash's own docs are the authority on its currency support.
### Tempo CLI
The official [Tempo wallet CLI](https://github.com/tempoxyz/wallet) makes a paid request with `tempo request`:
```bash
tempo request https://api.onesource.io/api/chain/network-info
```
It handles the 402 → credential → retry cycle automatically and prints the response; there's no currency flag to set. The Tempo CLI's default wallet pays in USDC.e: its access keys are scoped to USDC.e spending, so a pathUSD challenge fails against them. To pay in pathUSD, use the mpp-go SDK below.
Sessions use the same tool: `tempo wallet sessions list` / `tempo wallet sessions close ` (a session opens automatically on the first request to a host).
### Custom client (`mpp-go`)
The [`github.com/tempoxyz/mpp-go`](https://github.com/tempoxyz/mpp-go) library exposes the credential primitives directly and pays in **pathUSD as well as USDC.e** against OneSource. It supports any of Tempo's stablecoins, but only when signing from an **ECDSA-generated wallet keypair**. See the mpp-go README: the `charge-basic` and `session-client` examples are the quickest way in.
## Sessions
The second payment mode: open a session once, pay many calls under one signed envelope, then close to settle on-chain. For workloads above ~50 calls/min this is materially cheaper than settling every call.
How it works:
- The 402 advertises a **session** challenge alongside the per-call charge challenges. Your client opens a channel, then satisfies subsequent calls with vouchers against it instead of paying each one individually.
- Closing the session settles the net total in one on-chain transaction. A session that is never closed still settles: OneSource settles it for you.
**Sessions are USDC.e only.** Unlike single payments, OneSource emits just one session challenge, for USDC.e. A client can select a session challenge differently from its per-call path, so offering a second (pathUSD) session challenge risks a close building a settle transaction in the wrong currency. Single-currency emission removes the ambiguity.
## When MPP beats x402
- **Lower per-call cost.** Tempo gas is materially cheaper than Base; settlement fees can be an order of magnitude lower.
- **Proxy / CDN friendly.** RFC 7235 headers pass through Cloudflare, AWS API Gateway, and other middleboxes that strip nonstandard headers.
- **Session model.** If your agent makes hundreds of calls/minute, per-call settlement burns fees; sessions amortize.
## When to stick with x402
- You already have a Base USDC balance and prefer not to bridge.
- Your existing tooling speaks x402 only.
- You want the on-chain settlement to land on a high-traffic L2.
The gateway accepts both; pick whichever fits your wallet and your existing integrations.
## Next
- [x402 on Base](./x402-base)
- [Wallet setup](./wallet-setup): if you haven't funded your wallet yet
- [Tempo docs](https://docs.tempo.xyz): MPP protocol reference, CLI, and wallet setup
---
## Wallet setup
Pay-per-call access uses one of two protocols:
- **[x402 on Base mainnet](./x402-base)**: pay each call in USDC on Base. The standard agent-payment protocol.
- **[MPP on Tempo](./mpp-tempo)**: pay each call in USDC.e (or pathUSD) on the Tempo network. Lower per-call gas costs.
Both require a wallet you control with a funded balance in the relevant currency.
## Pick a wallet flavor
| Protocol | Network | Currency | Wallet needs |
|---|---|---|---|
| x402 | Base mainnet (chain ID 8453) | USDC (`0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913`) | Ethereum-style key (secp256k1) with a USDC balance on Base |
| MPP | Tempo mainnet | USDC.e (`0x20c000000000000000000000b9537d11c60e8b50`) or pathUSD (`0x20c0000000000000000000000000000000000000`) | Tempo-style key with USDC.e or pathUSD balance |
If you already use an agent wallet (e.g., [AgentCash](https://agentcash.dev)), it likely supports both protocols; skip to the protocol pages.
## Fund a fresh wallet
### x402 / Base
**1. Get a private key.** You need a 64-char hex string starting with `0x`, 66 chars total.
- **Export from MetaMask**: Account menu (three dots) → **Account details** → **Show private key**.
- **Export from Coinbase Wallet**: Settings → Developer settings → **Export private key**.
- **Generate a fresh one**: `echo "0x$(openssl rand -hex 32)"`. Fund this address before using it.
:::warning Use a dedicated wallet
Don't use the private key from a wallet that holds significant funds. Create or designate a wallet specifically for OneSource payments and keep only a small USDC balance on it (a few dollars).
:::
**2. Send USDC on Base to its address.** Sources:
- Bridge from Ethereum mainnet via [Base Bridge](https://bridge.base.org).
- Withdraw USDC directly to Base from an exchange that supports Base (Coinbase, Kraken, etc.).
- Swap directly on Base via Uniswap, Aerodrome, or your DEX of choice.
**3. Budget.** A few dollars of USDC handles thousands of API calls (per-call prices range from $0.001 to $0.010).
:::warning Base network only
USDC must be on **Base** (chain ID 8453), not Ethereum mainnet. Sending USDC on the wrong network to the same address won't work; the funds will sit on the wrong chain. If this happens, use [Base Bridge](https://bridge.base.org) to move them across.
:::
### MPP / Tempo
1. Generate a Tempo-compatible key.
2. Obtain USDC.e or pathUSD on Tempo. See [Tempo's docs](https://tempo.xyz) for bridge and on-ramp options.
3. Mainnet RPC: `https://rpc.tempo.xyz`.
## What the OneSource REST API does
When you call an endpoint without a Bearer key, the service returns `HTTP 402 Payment Required` with two headers offered in parallel:
- `Payment-Required`: x402 v2 challenge (price, payTo, chain, asset).
- `WWW-Authenticate: Payment`: RFC 7235-style MPP challenge (price, payTo, network, asset).
Your client picks whichever protocol it supports, signs the proof, and resends the request with the `Payment-Signature` (x402) or `Authorization: Payment` (MPP) header. The service verifies, settles on-chain, and returns the response, along with a receipt header for your records.
## Security best practices
Use a wallet dedicated to OneSource payments rather than one holding real funds, and keep only a few dollars on it, topping up when it drains.
Keep the key out of source control. An env var or a `.env` listed in `.gitignore` is fine locally; in production put it in a secrets manager (AWS Secrets Manager, HashiCorp Vault, 1Password CLI) rather than a plain env var. If a key was ever shared, logged, or pasted somewhere unexpected, generate a new one and move the balance across.
## Troubleshooting
| Problem | Fix |
|---|---|
| `Payment required (402)` keeps coming back | x402 isn't configured; your client isn't signing. Set `X402_PRIVATE_KEY` (or supply an account to `@x402/fetch`). |
| Payment fails / times out | Wallet has no USDC on Base. Check the balance on [BaseScan](https://basescan.org). |
| `x402 setup failed` / "invalid key" in logs | Key must start with `0x` and be 66 characters (`0x` + 64 hex). |
| USDC went to the wrong network | Bridge it to Base via [bridge.base.org](https://bridge.base.org). |
| Tools work but call returns no payment receipt | A successful paid response always carries `Payment-Response`, because the data is released only after settlement confirms. If it's missing, the call was probably authorized by an API key instead (in the MCP server, `ONESOURCE_API_KEY` takes priority over a wallet), or your HTTP client isn't exposing the header. |
## Next
- [x402 on Base](./x402-base)
- [MPP on Tempo](./mpp-tempo)
---
## x402 on Base
[x402](https://www.x402.org) is the Coinbase-originated standard for HTTP-native micropayments. OneSource supports x402 on Base mainnet: every paid endpoint accepts a USDC-on-Base payment signature in place of an API key.
## How a call works
1. Your client calls a OneSource endpoint without any `Authorization` header.
2. OneSource returns `HTTP 402` with a `Payment-Required` header containing the price ($0.001 to $0.02 USDC), the `payTo` address, and the asset (USDC on Base chain ID 8453).
3. Your client signs an EIP-712 `TransferWithAuthorization` payload over the price + nonce + deadline.
4. Your client retries the call with the signed payload in a `Payment-Signature` header.
5. OneSource verifies the signature with the x402 facilitator, runs the call, settles the transfer on Base, and returns the API response plus a `Payment-Response` header (transaction hash + settled amount).
The response is released only after settlement confirms, so a successful paid response always carries the transaction hash. You aren't charged for a call that fails upstream. If settlement fails or can't be confirmed, you get an error instead of the data; see [Settlement errors](/guides/error-codes#settlement-errors-after-a-paid-call). One of them, `settlement_unconfirmed`, means your payment may have gone through: don't pay again for that request.
## Using `@x402/fetch`
[`@x402/fetch`](https://www.npmjs.com/package/@x402/fetch) wraps `fetch` to handle the 402 → sign → retry loop automatically. OneSource speaks x402 **v2**, so use the v2 client: register the EVM `exact` scheme on an `x402Client`, then wrap `fetch` with it.
```ts
const account = privateKeyToAccount(process.env.X402_PRIVATE_KEY as `0x${string}`);
const client = new x402Client();
registerExactEvmScheme(client, { signer: account });
const paidFetch = wrapFetchWithPayment(fetch, client);
const res = await paidFetch('https://api.onesource.io/api/chain/network-info');
const { data, error } = await res.json();
```
You call it like normal `fetch`; the wrapper pays for every paid endpoint it hits.
:::caution Use the v2 client
The challenge is carried in the **`Payment-Required` response header**, not the response body: the body is only `{"error":"Payment required","meta":{…}}`. The v1 `x402-fetch` package reads `accepts` from the body, so it throws `TypeError: Cannot read properties of undefined (reading 'map')` before it ever signs. Install `@x402/fetch` and `@x402/evm` at `^2`.
:::
## Using AgentCash
[AgentCash](https://agentcash.dev) is an agent wallet and MCP server that speaks both x402 and MPP. Set it up with:
```bash
npx agentcash@latest install
```
It generates a local wallet on first run; fund it with USDC on Base. Once AgentCash is wired into your assistant (see [its setup docs](https://agentcash.dev)), ask the assistant to `fetch https://api.onesource.io/api/chain/network-info` and it signs the x402 payment automatically.
## Using the OneSource MCP Server with x402
Set `X402_PRIVATE_KEY` instead of `ONESOURCE_API_KEY`:
```bash
X402_PRIVATE_KEY=0x… npx -y @one-source/mcp@latest
```
Or in your MCP client config:
```json
{
"mcpServers": {
"onesource": {
"command": "npx",
"args": ["-y", "@one-source/mcp@latest"],
"env": { "X402_PRIVATE_KEY": "0x…" }
}
}
}
```
The OneSource MCP Server picks up the key and signs payments automatically on every tool call.
To batch many calls through one channel instead of paying per call, add `X402_PAYMENT_MODE=batch` (and a persistent `X402_CHANNEL_DIR`): see [Batch settlement](#batch-settlement) below.
## Pricing
Per-call USDC prices for `/api/chain/*` routes are published in two places:
- The `x-payment-info.price.amount` extension on every [API Reference](/api-reference/) page.
- The response `meta.cost_usdc` field on every successful call.
[Deepstate market data](/deepstate/) (`/deepstate/v1/*`) is priced flat instead ($0.005 per call, $0.02 for the analytics routes) and doesn't carry `meta.cost_usdc` on a successful call, only in a `402` challenge.
Examples:
| Endpoint | Price (USDC) |
|---|---|
| `/api/chain/block-number` | 0.001 |
| `/api/chain/network-info` | 0.001 |
| `/api/chain/live-balance` | 0.003 |
| `/api/chain/call` | 0.005 |
| `/api/chain/nft-metadata` | 0.008 |
| `/api/chain/pending` | 0.010 |
| `/deepstate/v1/markets` | 0.005 |
| `/deepstate/v1/analytics/makers` | 0.02 |
## Verify the payment
The `Payment-Response` header on a successful call carries the on-chain transaction hash. You can verify settlement on https://basescan.org with that hash.
## Receipts
OneSource also returns settlement metadata on the body's `meta` object: `payment_chain`, `payment_token`, `cost_usdc`, `request_id`. Log these for audit / billing reconciliation.
## Batch settlement
x402 has a session-like mode that mirrors [MPP sessions](./mpp-tempo#sessions), but stays on Base/USDC, so you don't bridge to Tempo. Instead of settling every call on-chain, you open a payment channel with a single deposit, satisfy many calls with off-chain signed claims against it, and OneSource settles the cumulative total in batched on-chain claims. For high call volumes this amortizes settlement gas the same way MPP sessions do.
OneSource advertises a `batch-settlement` scheme alongside the standard `exact` scheme in the `402` for every `/api/chain/*` route. OneSource is the channel authorizer (self-managed), claims accrued usage on a short interval, and refunds any unused deposit after a withdraw delay (~1 day on mainnet).
### Using it
Batch needs a client that holds channel state across calls: the deposit, the signed vouchers, and the running cumulative total. Two supported paths:
- **The OneSource MCP Server (no code).** Set `X402_PAYMENT_MODE=batch` (and a persistent `X402_CHANNEL_DIR`) when you run [`@one-source/mcp`](../mcp/install) in x402 mode, or flip it in-session with the `1s_payment_mode` tool. The `1s_refund` tool reclaims any unused deposit on demand when you're done. See [MCP configuration → Batch settlement](../mcp/configuration#batch-settlement-payment-channels). Works in the default stdio (long-lived) transport, not the stateless `--http` mode.
- **A custom client.** Build directly on the `@x402/evm` SDK. [Build an x402 batch-settlement client](/guides/x402-batch-settlement-client) is a complete, runnable example you can copy.
:::note Exact-only clients keep working
The 402 advertises both schemes on every route, and what happens next is client behavior: a client that implements only the **exact** scheme ignores the batch challenge and pays per call; nothing fails, it just doesn't batch. The two paths above batch against OneSource out of the box; for any other wallet, its own documentation is the authority on whether it implements batch-settlement. Protocol details: the [x402 batch-settlement spec](https://docs.x402.org/schemes/batch-settlement).
:::
## Next
- [MPP on Tempo](./mpp-tempo): alternative protocol with lower gas costs
- [Wallet setup](./wallet-setup): if you haven't funded your wallet yet
---
## Guides
Practical guides for common OneSource REST API workflows.
## Available
- **[Batching and cost optimization](./batching-and-cost-optimization)**: patterns that cut API spend 5-20x: batched balance calls, endpoint-tier awareness, client-side caching, and amortized settlement (MPP sessions or x402 batch settlement) for high-volume workloads.
- **[Error codes](./error-codes)**: the two error shapes (envelope from OneSource, flat from the gateway), what each HTTP status means, and a recovery pattern that handles both. Includes a normalizer and a retry/backoff sketch.
- **[Migrating from x402 / MPP to an API key](./migrating-to-api-key)**: moving production traffic from wallet-paid x402 or MPP calls to a Bearer API key from app.onesource.io (or running both in parallel). Generalized across our discovery channels: AgenticMarket / CDP Bazaar, AgentCash, x402scan, mppScan, MCP Registry, Glama, npm.
- **[AI editor setup](./ai-editor-setup)**: wire the OneSource docs into Claude Code, Claude Desktop, Cursor, VS Code, or Windsurf as an MCP server so your assistant can answer questions about the API. Distinct from [`@one-source/mcp`](/getting-started/mcp/install), which lets the assistant *call* the API.
## Reference
The [API Reference](/api-reference/) lists every endpoint with parameters, example responses, and per-call pricing.
---
## AI editor setup
Give your AI coding assistant knowledge of the OneSource **documentation** so it can answer questions and generate accurate calls without you copy-pasting from these pages.
:::note Already running the main server?
[`@one-source/mcp`](/getting-started/mcp/install) includes the documentation tools, so it covers this guide on its own. The options below are for wiring in the docs without the chain tools.
:::
There are three ways to do it, in rough order of preference.
## 1. Recommended: the OneSource docs MCP
[`@one-source/docs-mcp`](/getting-started/mcp/docs-mcp) gives your assistant structured tools, search docs, list endpoints, look up parameters, pricing, and auth, which retrieve far more precisely than plain text search. Two ways to run it:
- **Hosted, zero install**: point any HTTP-capable MCP client at `https://docs.onesource.io/api/mcp`.
- **Local**: `npx -y @one-source/docs-mcp` (Node).
Per-editor configuration (Claude Code, Claude Desktop, Cursor, Windsurf, VS Code) lives on the **[Docs MCP page](/getting-started/mcp/docs-mcp)**; start there.
## 2. Already using `mcpdoc`?
If you've standardized on LangChain's [`mcpdoc`](https://github.com/langchain-ai/mcpdoc) across your documentation sources, add OneSource as one more entry rather than running a second kind of server. It serves keyword search over `llms.txt` (no structured tools) and needs Python's [`uv`](https://docs.astral.sh/uv/): `curl -LsSf https://astral.sh/uv/install.sh | sh`.
**Claude Code:**
```bash
claude mcp add-json onesource-docs '{"type":"stdio","command":"uvx","args":["--from","mcpdoc","mcpdoc","--urls","OneSourceDocs:https://docs.onesource.io/llms.txt","--follow-redirects"]}'
```
**Cursor / Windsurf / Claude Desktop**: add to your editor's MCP config file (VS Code uses a `servers` key instead of `mcpServers`):
```json
{
"mcpServers": {
"onesource-docs": {
"command": "uvx",
"args": ["--from", "mcpdoc", "mcpdoc", "--urls", "OneSourceDocs:https://docs.onesource.io/llms.txt", "--follow-redirects"]
}
}
}
```
## 3. No MCP support: use the raw files
If your tool can't speak MCP, point it at (or paste in) the doc bundles directly:
- **[llms.txt](https://docs.onesource.io/llms.txt)**: index of every page (lightweight, for discovery).
- **[llms-full.txt](https://docs.onesource.io/llms-full.txt)**: the full docs concatenated into one file (for context-window ingestion).
---
## Batching and cost optimization
Per-call pricing rewards efficient request patterns. A few small habits cut spend 5-20x for the same answer.
## 1. Use `live-balance` for batched ERC20 lookups
`/api/chain/live-balance` runs ETH balance plus multiple ERC20 balances in one call (server-side multicall under the hood). One $0.003 request beats N $0.003 requests:
```bash
# Cheap: one call for ETH + 3 tokens = $0.003
curl "https://api.onesource.io/api/chain/live-balance?address=0xd8da...&tokens=0xA0b8...,0xdAC1...,0x6B17..." \
-H "Authorization: Bearer $ONESOURCE_API_KEY"
# Expensive: four calls = $0.012, same data
curl "https://api.onesource.io/api/chain/erc20-balance?account=...&token=0xA0b8..." # $0.003
curl "https://api.onesource.io/api/chain/erc20-balance?account=...&token=0xdAC1..." # $0.003
curl "https://api.onesource.io/api/chain/erc20-balance?account=...&token=0x6B17..." # $0.003
# + a separate ETH balance call
```
The `tokens` parameter accepts a comma-separated list of at most 20 contract addresses. Past 20 the call is rejected rather than truncated, so split a longer list across several calls. The endpoint reads the contracts you name: it does not discover a wallet's holdings, so a token you leave out is absent from the response rather than reported as zero.
The two endpoints differ in response shape. The batched call returns each balance as a raw hex string with `symbol` and `decimals` but no `name`, while `erc20-balance` returns a decimal string and includes `name`. Switching from N single calls to one batched call means parsing the hex (`parseInt(balance, 16)` or `BigInt(balance)`) and sourcing the token name elsewhere if you need it.
## 2. Use `network-info` instead of separate calls
`/api/chain/network-info` returns chain ID + latest block + current gas price in one $0.001 call. Don't call `chain-id` and `block-number` separately when you need the bundle, and don't reach for `estimate-gas` to get the chain-tip gas price (that endpoint estimates gas for a *specific* transaction body, not the tip).
| What you want | One call | Two calls |
|---|---|---|
| Chain ID + block + gas price | `network-info` = **$0.001** | `chain-id` + `block-number` = **$0.002** (and still no gas price) |
## 3. Pick the cheapest endpoint that answers your question
Endpoints are priced by complexity. Always check the [API Reference](/api-reference/) for cost before wiring:
| Tier | Endpoints | Use when |
|---|---|---|
| **$0.001** | `block-number`, `chain-id`, `network-info` | Tip / chain ID lookups |
| **$0.003** | `live-balance`, `block`, `erc20-balance`, `erc1155-balance`, `nft-owner`, `code`, `nonce`, `allowance`, `total-supply` | Single-state reads |
| **$0.004** | `estimate-gas` | Gas estimate for a specific transaction body |
| **$0.005** | `call`, `events`, `erc20-transfers`, `contract`, `receipt`, `storage`, `ens`, `proxy` | Decoded reads, log queries |
| **$0.008** | `tx`, `nft-metadata`, `erc721-tokens` | Heavy reads, metadata resolution |
| **$0.010** | `pending` | Mempool reads |
A workload that calls `chain/tx` ($0.008) when `chain/receipt` ($0.005) would have answered the question is paying 60% more for the same outcome.
## 4. Cache deterministic results client-side
Some endpoint results are immutable. Cache them aggressively:
- **Block headers** (`/api/chain/block/{number}`) for any block ≥ 64 blocks below tip: immutable after finality.
- **Transactions and receipts** (`/api/chain/tx/{hash}`, `/api/chain/receipt/{hash}`): immutable after confirmation.
- **Contract bytecode** (`/api/chain/code/{address}`): immutable for non-upgradeable contracts; safe to cache for proxies between upgrade events.
- **NFT metadata** (`/api/chain/nft-metadata`): off-chain metadata via IPFS / Arweave is content-addressed; cache by `tokenURI` rather than re-fetching every call.
- **ENS resolution** (`/api/chain/ens/{name}`): cache for an hour or two; ENS records change rarely.
For an analytics workload, even a 1-minute TTL on `block` lookups removes most of the repeat calls.
## 5. Don't poll the tip if you don't need to
If your app needs "is this transaction confirmed?", poll the **receipt** by hash, not the block tip. One $0.005 call replaces dozens of $0.001 tip checks plus per-block scans.
**Watch the response shape, not the HTTP status.** `chain/receipt` returns `HTTP 200` whether the tx is confirmed or not; for an unknown hash you get `{"data": {"result": null}, ...}`. Polling on status `404` will loop forever. The correct check:
```ts
const { data } = await res.json();
if (data.result === null) return 'pending'; // not yet mined or unknown hash
return { status: 'confirmed', receipt: data.result };
```
(Same pattern with `chain/tx`, which returns `{transaction, receipt}` and uses `data.transaction === null` for the not-yet-mined case, but at $0.008 you're paying 60% more for the same answer if all you needed was "is it confirmed?".)
## 6. Batch event-log queries by topic + block range
`/api/chain/events` paginates `eth_getLogs` results. A single call with a wide `to_block`-`from_block` range and the right topic filters covers thousands of events for $0.005, much cheaper than N narrow queries. A 500-block window on a busy contract can easily return 10,000+ logs for a single $0.005 call.
The upstream node caps any single query at **20,000 log results**. Overshoot and you get a `502` envelope error whose `message` includes the safe sub-range the node would have served: use it to drive adaptive pagination:
```json
{
"data": null,
"error": {
"code": 502,
"message": "getLogs failed: RPC error -32602: query exceeds max results 20000, retry with the range 21675072-21675860"
},
"meta": { "endpoint": "/api/chain/events", "request_id": "..." }
}
```
Parse the suggested range out of the message and retry with that window, then continue from the next block. Add address or topic filters to keep result counts under the cap when you can.
Sizing the window in blocks only works if you know the chain's cadence. The same 500 blocks is about 100 minutes on Ethereum and about 50 seconds on Robinhood Chain, so a range tuned on mainnet under-samples badly elsewhere. Read `avg_block_time_ms` from [`/api/chain/network-info`](/api-reference/chain-network-info) and convert from the time window you want.
Going through the MCP server rather than REST adds a 100,000-character response cap, which a wide window will hit first. It pages instead of failing: see [Paging the log tools](../getting-started/mcp/tool-reference#paging-the-log-tools).
## 7. For wallet-paid workloads, watch settlement fees too
x402 and MPP per-call USDC charges *include* on-chain settlement costs. At very low per-call prices ($0.001) the gas can be a non-trivial fraction of the spend. If you're running thousands of calls/minute, amortize the settlement so one deposit/envelope covers N calls:
- **On Tempo (MPP):** [sessions](/getting-started/x402-and-mpp/mpp-tempo#sessions): open a channel once, pay many calls under it.
- **On Base (x402):** [batch settlement](/getting-started/x402-and-mpp/x402-base#batch-settlement): the same model without leaving Base. Use the [OneSource MCP Server's batch mode](/getting-started/mcp/configuration#batch-settlement-payment-channels) (no code) or [build a batch-settlement client](/guides/x402-batch-settlement-client).
## Quick checklist
- [ ] Replaced N ERC20 lookups with a single `live-balance` call.
- [ ] Used `network-info` for chain-state checks instead of three separate calls.
- [ ] Cached immutable results (finalized blocks, confirmed receipts, NFT metadata) client-side.
- [ ] Polling receipts by hash, not the tip.
- [ ] Picked the cheapest endpoint that answers each question (check `meta.cost_usdc` to confirm).
- [ ] For wallet-paid high-volume workloads: amortized settlement, MPP sessions on Tempo or x402 batch settlement on Base, instead of paying per call.
---
## Error codes
This guide covers `/api/chain/*` responses. [Deepstate market data](/deepstate/) (`/deepstate/v1/*`) only ever emits the flat gateway shape below, never the envelope shape; see [Deepstate Market Data → Errors](/deepstate/#errors).
OneSource can emit three error shapes depending on where the failure originates. Your client needs to handle all three:
- **Envelope errors**: from the OneSource service itself (parameter validation, on-chain lookups, upstream RPC issues). Carries `{error: {code, message}, meta}`.
- **Gateway errors**: from the API gateway in front of OneSource (auth, payment, routing). Flat `{error, message}`.
- **402 + MPP RFC 7807 errors**: payment-protocol-specific shapes the gateway forwards verbatim. Different again.
The HTTP status is the only thing all three share. Branch on it when in doubt; use the body to read details.
## Envelope errors (from the OneSource service)
```json
{
"error": {
"code": 400,
"message": "address param required"
},
"meta": { "endpoint": "/api/chain/live-balance", "request_id": "abcdef0123456789" }
}
```
- `error.code` is the **HTTP status as an integer** (`400`, `402`, `500`, `502`), mirroring the status line. It is *not* a stable string identifier.
- `error.message` is human-readable English produced by the handler. The wording is not a stable contract; don't parse it.
- `meta.request_id` mirrors the server-side trace. Always log it.
- `data` is **omitted** on error (not present as `null`).
Branch on the HTTP status (or equivalently `error.code`), and use `error.message` for diagnostics.
### What you'll see by status
| HTTP | When | Example `error.message` |
|---|---|---|
| `400` | Parameter missing, malformed, or wrong type. | `"address param required"`, `"invalid number \"notanumber\": expected decimal or 0x-prefixed hex"`, `"to and data fields required"`, `"invalid JSON body"` |
| `404` | Path doesn't match any route on OneSource (the gateway forwarded an unknown path). Unregistered `/api/chain/*` paths never get this far: the gateway catches them and returns the flat shape below. | `"404 page not found"` |
| `500` | Unhandled internal failure. | Captured handler message. |
| `502` | Upstream RPC node returned an error; the message passes the upstream verbatim. | `"ownerOf failed: RPC error 3: execution reverted: ERC721: owner query for nonexistent token"`, `"getLogs failed: RPC error -32602: query exceeds max results 20000, retry with the range ..."` |
The `400` bucket is by far the most common; pre-validate parameters client-side against the [API Reference](/api-reference/) before sending.
### "Resource not found" doesn't mean `404`
OneSource does **not** return `404` for missing on-chain resources. The semantics vary by endpoint, and you generally need to look at the response body:
| Endpoint | Unknown / missing resource returns |
|---|---|
| `/api/chain/block/{n}` (past tip or future) | `200` + `{"data": {"result": null}, ...}` |
| `/api/chain/tx/{hash}` (unknown hash) | `200` + `{"data": {"transaction": null, "receipt": null}, ...}` |
| `/api/chain/receipt/{hash}` (unknown / unmined) | `200` + `{"data": {"result": null}, ...}` |
| `/api/chain/nft-owner` (nonexistent `token_id`) | `502` envelope error; the RPC revert (`"execution reverted: ERC721: owner query for nonexistent token"`) passes through |
| `/api/chain/ens/{name}` (no resolver) | `200` + `{"data": {"name": "...", "error": "no resolver set for this name"}, ...}`: inline `data.error` string, NOT the envelope `error` field |
Polling on HTTP `404` to detect "tx pending" or "NFT doesn't exist" will fail. The reliable patterns:
```ts
// "Is this tx confirmed?": receipt by hash
const { data } = await res.json();
if (data?.result === null) return 'pending';
return { status: 'confirmed', receipt: data.result };
// "Does this NFT exist?": catch the 502 revert
if (!res.ok && body?.error?.code === 502 &&
body.error.message.includes('owner query for nonexistent token')) {
return 'does-not-exist';
}
```
## Gateway errors (auth, payment, routing)
Returned by the gateway *before* the request reaches OneSource, so there's no `meta` block. Flat shape:
```json
{ "error": "invalid_token", "message": "Invalid or expired API key" }
```
`error` is a lowercase snake_case identifier. Branch on it the same way you would on an HTTP status.
| `error` | HTTP | Cause | Recovery |
|---|---|---|---|
| `invalid_token` | 401 | `Authorization: Bearer sk_…` was present but the key isn't recognized or has been revoked. | Re-copy the key from [app.onesource.io](https://app.onesource.io) → API Keys. Rotate if compromised. |
| `api_key_required` | 401 | The endpoint requires a Bearer key but x402/MPP isn't accepted, *or* both x402 and MPP are disabled. An anonymous call to a paid endpoint returns `402` with a payment challenge instead (see the next section). | Add `Authorization: Bearer sk_…`. |
| `plan_required` | 403 | Your API key doesn't have an active subscription yet. | Finish subscribing at [app.onesource.io](https://app.onesource.io), or call the endpoint with x402 / MPP instead. |
| `daily_limit_exceeded` | 429 | Your account's daily request quota is spent. | Wait for the 00:00 UTC reset, move overflow to x402 / MPP, or email support@onesource.io for a higher cap. See [Usage quotas](/getting-started/api-key/usage-quotas). |
| `monthly_limit_exceeded` | 429 | Your account's monthly request quota is spent. | Route overflow to x402 / MPP until the calendar month rolls over, or email support@onesource.io for a higher cap. |
| `rate_limited` | 429 | An extreme burst tripped the backend's abuse guard. This is not a plan limit. | Back off and retry; `Retry-After: 1` is set. If you see it in normal operation, email support@onesource.io. |
| `auth_unavailable` | 503 | The gateway couldn't reach the validation service. Transient. | Retry with backoff. |
| `invalid_payment` | 400 | x402 / MPP payment header was present but malformed. | Re-sign with the current challenge. |
| `verification_failed` | 502 | Payment header was well-formed but the facilitator couldn't verify it. | Re-sign and retry; if persistent, the facilitator is degraded. |
| `upstream_error` | 502 | Gateway reached OneSource but OneSource returned an unrecoverable error. | Retry with backoff. |
| `not_found` | 404 | Either no upstream is configured for this host, or the path is an unregistered `/api/chain/*` route. The gateway knows the complete `/api/chain/*` route set, so it can identify a typo there as a bad path. | Check the hostname, and check the path against the [API Reference](/api-reference/). |
Unknown paths **outside** `/api/chain/*` are not enumerated by the gateway, so they come back as `api_key_required` (401) rather than `not_found`. A 401 on a path you believe is real is worth re-checking for a typo before assuming it's an auth problem.
### Settlement errors (after a paid call)
A paid call settles after OneSource has answered. The gateway holds the response, settles your payment, and releases the data only once settlement confirms. If the call itself fails upstream, you aren't charged. If settlement fails, you get one of these errors instead of the data, in the flat shape above, and the `Payment-Response` receipt header carries the same code:
| `error` | HTTP | What happened | Recovery |
|---|---|---|---|
| `settlement_failed` | 402 | The payment was rejected before anything moved. The response carries a fresh `Payment-Required` challenge. | Pay again with a new authorization. |
| `settlement_unavailable` | 503 | Settlement is temporarily unavailable, and your payment was not charged. | Retry the request. `Retry-After` is set. |
| `settlement_unconfirmed` | 503 | OneSource couldn't confirm whether your payment went through. It may have. | Don't create a new payment for this request. Resending the **same** payment authorization is safe. If your client can't do that, email support@onesource.io with your wallet address and the time of the request. |
| `settlement_pending` | 402 | The transfer was broadcast but hasn't confirmed yet. No new challenge is attached. | Check the transaction in the `Payment-Response` receipt before paying again. |
| `authorization_used` | 402 | The payment authorization you sent was already used, most likely by an earlier request whose reply you didn't get. No new challenge is attached. | Don't send a new payment for this request. Email support@onesource.io with the authorization nonce. |
MPP single payments follow the same rule. A clean rejection comes back as RFC 7807 problem+json (see below). If the transfer was broadcast but settlement didn't complete, you get `503` `settlement_unconfirmed` with a `transaction` field holding the Tempo transaction hash: check whether it confirmed, and if it did, email support@onesource.io with the hash.
`settlement_unconfirmed` is the one error where a blind retry can cost you money. Payment-aware clients such as `@x402/fetch` and MPP fetchers sign a new payment on every attempt, so retrying the call pays again. Check whether the payment went through before doing anything else.
## Payment-required (402) and MPP RFC 7807
### 402 from an anonymous call
An unauthenticated call to a paid endpoint comes back with `HTTP 402` and a third hybrid shape: `error` is a string (like gateway flat shape) but a `meta` block is present too:
```json
{
"error": "Payment required",
"meta": {
"cost_usdc": "0.001",
"endpoint": "/api/chain/network-info",
"network": "eip155:8453"
}
}
```
The response also carries the protocol-specific challenge headers:
- `Payment-Required: `: x402 challenge (price in USDC on Base, recipient address, expiry).
- `WWW-Authenticate: Payment ...` (one or more): MPP challenges (Tempo network, both one-shot `charge` and `session` flavors).
A single 402 advertises every supported protocol; clients pick the one they speak and ignore the rest. Sign and retry; payment-aware HTTP clients ([`@x402/fetch`](https://www.npmjs.com/package/@x402/fetch) v2 for x402, MPP-aware fetchers for Tempo) handle this loop for you. See [x402 on Base](/getting-started/x402-and-mpp/x402-base) or [MPP on Tempo](/getting-started/x402-and-mpp/mpp-tempo).
### MPP credential errors (RFC 7807)
A malformed MPP `Authorization: Payment ...` credential comes back from the MPP SDK in **RFC 7807 problem+json** form, neither envelope nor gateway-flat:
```json
{
"type": "https://mpp.dev/errors/malformed-credential",
"title": "Malformed Credential",
"status": 400,
"detail": "mpp: invalid credential encoding: json decode: invalid character ..."
}
```
Branch on `type` (a stable URL) when handling MPP-specific failures.
The gateway also exposes [quota headers](/getting-started/api-key/usage-quotas#quota-headers) (`X-Quota-Day-Limit`, `X-Quota-Day-Used`, `X-Quota-Month-Limit`, `X-Quota-Month-Used`) on every API-key response, including on a `429`.
## Anatomy of recovery logic
A client that recovers cleanly branches on HTTP status. Normalize the three body shapes up front so the rest of the logic stays simple:
```ts
function parseError(status: number, body: any): { code: number | string; message: string } | null {
// RFC 7807 (MPP credential errors): { type, title, detail, status }
if (body?.type && body?.title) {
return { code: body.type, message: body.detail ?? body.title };
}
if (!body?.error) return null;
// Envelope shape: { error: { code: , message }, meta }
if (typeof body.error === 'object') return body.error;
// Gateway flat shape: { error: "", message }
// Also handles the 402 hybrid: { error: "Payment required", meta }
return { code: body.error, message: body.message ?? '' };
}
async function callSkills(url: string, init: RequestInit, attempt = 0): Promise {
const res = await fetch(url, init);
const body = await res.json();
const err = parseError(res.status, body);
if (!err) return body.data;
// Retryable: transient gateway/upstream failures + 5xx from OneSource.
const retryable =
err.code === 'auth_unavailable' ||
err.code === 'upstream_error' ||
err.code === 'verification_failed' ||
(typeof err.code === 'number' && err.code >= 500);
if (retryable && attempt < 3) {
await new Promise(r => setTimeout(r, 2 ** attempt * 500 + Math.random() * 250));
return callSkills(url, init, attempt + 1);
}
throw new APIError(err, res.status);
}
```
Note: don't branch on `err.code === 404` to detect "resource not found": OneSource returns `200` + `data.*: null` for missing on-chain resources. See [the "Resource not found" section](#resource-not-found-doesnt-mean-404) above.
A few notes on what this sketch deliberately doesn't do:
- **No `429` branch.** Quota rejections need handling that differs by cause, which the flat retryable test above can't express: `rate_limited` clears in a second and sets `Retry-After`, while `daily_limit_exceeded` and `monthly_limit_exceeded` will not clear on any retry loop. See [Usage quotas](/getting-started/api-key/usage-quotas#what-a-rejection-looks-like) for a `429`-aware version.
- **No retry on `settlement_unconfirmed`.** It's a string code, so the numeric `>= 500` test above skips it, and that's deliberate: retrying through a payment-aware client pays again. See [Settlement errors](#settlement-errors-after-a-paid-call).
- **No 402 branch.** Payment-aware HTTP clients (`@x402/fetch`, MPP fetchers) handle the `402 → sign → retry` loop for you. If you're writing one of those clients yourself, branch on `402` and read the `Payment-Required` / `WWW-Authenticate` headers.
## Always log `request_id`
`meta.request_id` (envelope shape only; gateway errors don't carry one) is the single most useful field for support. It mirrors the server-side trace; including it in your error log lets the team find the corresponding entry in one query.
## When in doubt
Email support@onesource.io with:
1. The endpoint and parameters.
2. The full response body and HTTP status.
3. `meta.request_id` if the failure came from OneSource (envelope shape).
4. The approximate time of the failure.
If you're using the [OneSource MCP Server](/getting-started/mcp/install), the `1s_report_bug` tool files the same details for you.
---
## Migrating from x402 / MPP to an API key
The OneSource REST API is discoverable through a number of channels (AgenticMarket / CDP Bazaar, AgentCash, x402scan, mppScan, the MCP Registry, Glama, npm) and any agent that found you there can call the API by signing per-request payments in USDC. That suits low-volume and experimental traffic.
For production-scale workloads a direct Bearer API key is usually cheaper and simpler: predictable monthly billing through Stripe, a bundled request allowance, no per-call wallet settlement. This guide covers what changes (and what doesn't) when you migrate.
## What stays the same
- **Base URL.** Still `https://api.onesource.io`. No DNS change.
- **Endpoints.** Identical paths, parameters, response shapes. All endpoints in the [API Reference](/api-reference/) work as before.
- **Response envelope.** Same `{ data, error, meta }` shape on success and failure.
The wire format is *exactly* the same. Only the auth header changes.
## What changes
### Auth
| Before (x402 or MPP) | After (Bearer API key) |
|---|---|
| Sign each call (USDC on Base for x402, or USDC.e / pathUSD on Tempo for MPP); client handles `402 → sign → retry` | `Authorization: Bearer sk_…` once, every call |
| Wallet private key in your agent's env | API key from the OneSource dashboard |
| Per-call USDC settlement on Base or Tempo | Monthly billing through Stripe |
### Pricing model
Per-call charges range $0.001 to $0.010 USDC depending on endpoint. The Bearer-key subscription bundles that into a single per-month fee covering a request allowance. There is one plan, so there's nothing to size or choose at signup. For sustained traffic it is usually materially cheaper than per-call. For low volume, x402 or MPP may still be more economical; see the [cost comparison](#cost-comparison) below.
### Limits
| | x402 / MPP | Bearer API key |
|---|---|---|
| Concurrency | Unlimited; your wallet balance is the only bound | Unlimited |
| Request volume | Unmetered, you pay per call | Fixed daily and monthly quota, account-wide (shared across all your keys) |
What a key changes is how volume is paid for: a fixed monthly bill in place of per-call settlement, against a daily and monthly quota reported on every response via `X-Quota-*` headers. See [Usage quotas](/getting-started/api-key/usage-quotas).
If that volume isn't enough, email support@onesource.io (daily and monthly caps can be raised on an individual account) or route the overflow to x402 / MPP, which has no quota.
### Discovery
The OneSource REST API stays listed on its existing discovery channels (AgenticMarket / CDP Bazaar, AgentCash, x402scan, mppScan, the MCP Registry, Glama, npm) regardless of which auth path your own traffic uses. The service still emits x402 challenges in parallel with MPP and accepts Bearer keys, so third-party agents that find OneSource through any of those channels keep working without code changes.
## Migration path
### 1. Sign up for an API key
Follow [Sign up for an API key](/getting-started/api-key/subscribe) at [app.onesource.io/signup](https://app.onesource.io/signup): create your account, confirm your email, and subscribe. Then create a key under **API Keys** and copy its `sk_…` value from the creation dialog right away, it's shown only once.
### 2. Read the key into your app
[Retrieve your API key](/getting-started/api-key/retrieve-api-key) covers dashboard, env-var, and `.env` patterns. Standard practice: store as `ONESOURCE_API_KEY` in your hosting platform's secret store; read once at startup; cache in memory.
### 3. Swap auth in your client
If you were using `@x402/fetch`:
```diff
- import { x402Client, wrapFetchWithPayment } from '@x402/fetch';
- import { registerExactEvmScheme } from '@x402/evm/exact/client';
- import { privateKeyToAccount } from 'viem/accounts';
-
- const account = privateKeyToAccount(process.env.X402_PRIVATE_KEY);
- const client = new x402Client();
- registerExactEvmScheme(client, { signer: account });
- const apiFetch = wrapFetchWithPayment(fetch, client);
+ const apiFetch = (url: string, init: RequestInit = {}) =>
+ fetch(url, {
+ ...init,
+ headers: { ...init.headers, Authorization: `Bearer ${process.env.ONESOURCE_API_KEY}` },
+ });
const res = await apiFetch('https://api.onesource.io/api/chain/network-info');
```
If you were using AgentCash MCP or an MPP-aware fetcher:
```diff
{
"mcpServers": {
"agentcash": { "command": "npx", "args": ["-y", "agentcash-mcp"] }
+ ,
+ "onesource": {
+ "command": "npx",
+ "args": ["-y", "@one-source/mcp@latest"],
+ "env": { "ONESOURCE_API_KEY": "sk_…" }
+ }
}
}
```
The OneSource MCP Server gives the agent direct, key-authenticated access; AgentCash and other wallet-paid clients can stay for non-OneSource x402 endpoints.
### 4. Update error handling
With Bearer-key auth the failure surface shifts from x402 / MPP payment errors to gateway auth errors plus envelope errors from the service. The gateway returns a flat `{error, message}` shape for auth/routing problems (`invalid_token`, `api_key_required`, `plan_required`) while the OneSource service still returns the canonical `{data, error, meta}` envelope for parameter validation and on-chain failures. See [Error codes](/guides/error-codes) for the full catalog and a normalizer that handles both shapes.
If your retry logic only handles the `402 → sign → retry` loop, replace it with a 5xx backoff path plus a `429` branch. Read the `X-Quota-Day-Used` / `X-Quota-Day-Limit` headers on the success path and slow down before you hit the wall, rather than discovering it as a rejection.
### 5. Smoke test
Run the unified verify call from [First request](/getting-started/api-key/first-request) once with the new key. Compare to your existing x402 / MPP logs: `data` should be byte-equal for the same input.
## Cost comparison
The choice comes down to cost per call.
Wallet-paid calls cost $0.001 to $0.010 each depending on the endpoint. Across the 25
paid endpoints the mean is about **$0.0043**, though your figure depends entirely on your
mix: the three cheapest endpoints (`block-number`, `chain-id`, `network-info`) are
$0.001, while `erc721-tokens`, `nft-metadata`, and `tx/` are $0.008 and `pending` is
$0.010. Live per-endpoint prices are at
[`/api/pricing`](https://api.onesource.io/api/pricing) and on each operation's
[API Reference](/api-reference/) page.
Monthly x402 / MPP spend at a few volumes, before settlement gas:
| Calls / month | All cheapest ($0.001) | Mean mix ($0.0043) | All dearest ($0.010) |
|---|---|---|---|
| 5,000 | $5 | $22 | $50 |
| 25,000 | $25 | $108 | $250 |
| 250,000 | $250 | $1,080 | $2,500 |
| 2,000,000 | $2,000 | $8,640 | $20,000 |
The plan wins as soon as your wallet-paid spend would exceed the plan's monthly price.
Compare the relevant row above against the price shown at
[app.onesource.io](https://app.onesource.io). Settlement gas pushes the crossover a
little further in the plan's favor (Base for x402, materially less on Tempo for MPP, less
again if you use [batch settlement](/guides/x402-batch-settlement-client)), but it is a
small correction next to the per-call charge.
For most endpoint mixes that crossover lands in the low thousands of calls per month.
Past it the plan's advantage compounds quickly: a workload doing 250,000 calls a month of
mixed endpoints is comparing roughly a thousand dollars of wallet spend against one flat
monthly fee.
Two things that arithmetic doesn't capture:
- **The plan is bounded and the wallet is not.** The plan stops at its monthly and daily
quota (see [Usage quotas](/getting-started/api-key/usage-quotas)).
Above the cap you are contacting support or
sending overflow to x402 anyway, so at very high volume the honest answer is "both",
not "the plan".
- **Some reasons to migrate aren't about price at all.** A key means no wallet or private
key in your runtime, an invoiced monthly charge your finance team can process, and a
procurement path. Those hold whatever the arithmetic says, and they are the things
per-call rails structurally cannot offer.
Below the crossover, wallet-paid is the better choice: cheaper, no volume
ceiling, and no signup at all.
## Running both in parallel
Nothing forces you to pick one. The OneSource service accepts Bearer keys, x402 signatures, and MPP credentials in parallel; they coexist on the same endpoints. Common dual-mode patterns:
- **API key as primary, wallet-paid as overflow.** Your default client uses the Bearer key; when `X-Quota-Day-Used` approaches `X-Quota-Day-Limit` (or you get a `429` carrying `daily_limit_exceeded` / `monthly_limit_exceeded`), fall back to an x402- or MPP-signed call for that request. You buy the steady-state volume and pay per call only for the tail above it.
- **API key for production paths, wallet-paid for experiments.** Your prod service uses the Bearer key; ad-hoc scripts or one-off agents pay per call.
## Keeping your discovery footprint visible
If you actively rely on discovery (agents finding OneSource through AgenticMarket / CDP Bazaar, AgentCash, x402scan, mppScan, the MCP Registry, Glama, or npm) those listings remain live and unaffected by which auth path your own traffic uses. Even if you switch your *own* calls to Bearer-key auth, third-party agents discovering you through any wallet-paid channel will continue to pay-per-call via x402 or MPP without disruption.
---
## Build an x402 batch-settlement client
OneSource advertises a `batch-settlement` scheme on every `/api/chain/*` route (see
[x402 on Base → Batch settlement](/getting-started/x402-and-mpp/x402-base#batch-settlement)
for how it works and when it pays off). A client that implements only the **exact**
scheme ignores the batch challenge and pays per call. To actually batch, you need a
client that implements the scheme: open a channel with one deposit, sign an off-chain
voucher per call, and let OneSource settle the cumulative total in a single on-chain claim.
This guide is that client, end to end: the reference implementation we use to validate
the feature against mainnet, roughly 60 lines of TypeScript on top of the official
`@x402` SDK. Copy it and point it at your wallet.
:::note Prefer not to write code?
If you just want batch from an AI assistant, the
[OneSource MCP Server's batch mode](/getting-started/mcp/configuration#batch-settlement-payment-channels)
does it with one env var (`X402_PAYMENT_MODE=batch`): no client code. This guide is for
building a **custom** client or wiring batch into your own app.
Either way, batch only earns its keep for a **burst of calls from one long-lived
process**: the channel state (deposit, cumulative voucher) has to persist across calls.
For a handful of one-off requests, plain per-call [x402](/getting-started/x402-and-mpp/x402-base)
is simpler and cheaper. See [Batching and cost optimization](/guides/batching-and-cost-optimization)
to decide.
:::
## Prerequisites
- **Node.js ≥ 24**.
- **A wallet funded with USDC on Base** (mainnet asset `0x833589…`). The first call
deposits `price × depositMultiplier` into the channel escrow, so fund a little above
one call's price. Per-call prices are on each
[API Reference](/api-reference/) page.
- The wallet's **private key**. Treat it like any secret: load it from the environment,
never commit it.
## Project setup
Create a directory with three files.
**`package.json`**
```json
{
"name": "onesource-x402-batch-client",
"private": true,
"type": "module",
"scripts": {
"start": "tsx index.ts"
},
"dependencies": {
"@x402/evm": "2.14.0",
"@x402/fetch": "2.14.0",
"dotenv": "^16.4.7",
"viem": "^2.48.11"
},
"devDependencies": {
"tsx": "^4.21.0",
"typescript": "^5.7.3"
}
}
```
The batch scheme lives in `@x402/evm` (the scoped SDK family).
**`tsconfig.json`**
```json
{
"compilerOptions": {
"target": "ES2020",
"module": "ES2020",
"moduleResolution": "bundler",
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true,
"resolveJsonModule": true,
"types": ["node"]
},
"include": ["index.ts"]
}
```
**`.env`**
```bash
EVM_PRIVATE_KEY=0x… # funded Base wallet (the payer)
RESOURCE_SERVER_URL=https://api.onesource.io
ENDPOINT_PATH=/api/chain/block-number # any priced /api/chain/* route
NUMBER_OF_REQUESTS=3
DEPOSIT_MULTIPLIER=5 # deposit = price × this on channel open
STORAGE_DIR=./channel-storage # persist channel state across runs
# CHANNEL_SALT=0x… # bump for a fresh channel (see below)
# REFUND_AFTER_REQUESTS=true # reclaim unused deposit at the end
```
Then `npm install`.
## The client
**`index.ts`**
```ts
config();
const privateKey = process.env.EVM_PRIVATE_KEY?.trim() as `0x${string}`;
if (!privateKey) throw new Error('EVM_PRIVATE_KEY is required');
const baseURL = process.env.RESOURCE_SERVER_URL ?? 'https://api.onesource.io';
const endpointPath = process.env.ENDPOINT_PATH ?? '/api/chain/block-number';
const url = `${baseURL}${endpointPath}`;
const storageDir = process.env.STORAGE_DIR;
const channelSalt = (process.env.CHANNEL_SALT ?? `0x${'0'.repeat(64)}`) as `0x${string}`;
const numberOfRequests = Number(process.env.NUMBER_OF_REQUESTS ?? '3');
const depositMultiplier = Number(process.env.DEPOSIT_MULTIPLIER ?? '5');
const refundAtEnd = process.env.REFUND_AFTER_REQUESTS === 'true';
async function main(): Promise {
const account = privateKeyToAccount(privateKey);
// The public client lets the scheme read chain state and submit the deposit.
// Pass a URL to http() to use your own RPC instead of the public default.
const publicClient = createPublicClient({ chain: base, transport: http() });
const signer = toClientEvmSigner(account, publicClient);
const scheme = new BatchSettlementEvmScheme(signer, {
depositPolicy: { depositMultiplier },
salt: channelSalt,
// Persist channel + voucher state to disk so a later run reuses the same
// channel instead of opening (and funding) a new one.
...(storageDir ? { storage: new FileClientChannelStorage({ directory: storageDir }) } : {}),
});
const client = new x402Client();
client.register('eip155:*', scheme);
const fetchWithPayment = wrapFetchWithPayment(fetch, client);
const httpClient = new x402HTTPClient(client);
console.log(`payer ${signer.address} → ${url}\n`);
for (let i = 0; i < numberOfRequests; i++) {
const res = await fetchWithPayment(url, { method: 'GET' });
const result = await httpClient.processResponse(res);
if (result.kind === 'success') {
console.log(`Request ${i + 1}:`, result.body);
// settleResponse.transaction is the on-chain claim hash on the call that
// triggers a settlement; off-chain voucher-only calls have it empty.
console.log(' settle:', JSON.stringify(result.settleResponse));
} else {
console.log(`Request ${i + 1} - ${result.kind}:`, JSON.stringify(result));
}
}
if (refundAtEnd) {
console.log('\nRequesting refund of the unused channel balance…');
console.log(JSON.stringify(await scheme.refund(url), null, 2));
}
}
main().catch((err) => {
console.error(err?.response?.data?.error ?? err);
process.exit(1);
});
```
Run it:
```bash
npm start
```
## What happens on each run
1. **First call: channel open.** The scheme reads the `402`, sees the
`batch-settlement` challenge, and deposits `price × DEPOSIT_MULTIPLIER` USDC into the
escrow contract on Base (one on-chain transaction). It signs a voucher for this call's
amount and retries; OneSource serves the response.
2. **Subsequent calls: off-chain vouchers.** Each call signs a new voucher for the
*cumulative* amount and sends it in the payment header. No on-chain transaction:
`settleResponse.transaction` is empty for these.
3. **Settlement: one claim.** OneSource (the channel authorizer) redeems accrued vouchers
in a single on-chain claim on a short interval, transferring the cumulative total from
escrow to the receiver.
So N calls cost **one deposit + one claim** in gas instead of N settlements. With the
example's 3 calls you'll see the deposit tx on call 1, empty settle on calls 2 and 3,
and the cumulative total claimed in one transaction. Verify any hash on
[basescan.org](https://basescan.org).
## Channel lifecycle
- **Persistent storage.** `STORAGE_DIR` keeps the channel record on disk. A later run
with the **same wallet + same `CHANNEL_SALT`** reuses the open channel: no second
deposit. This is why batch only makes sense in a long-lived process or across runs that
share that directory; a fresh process with no stored state opens a new channel every
time (worst of both worlds).
- **A fresh channel.** Change `CHANNEL_SALT` to any new 32-byte hex value to open a
separate channel under the same wallet (useful when the previous one is drained or
pending withdrawal).
- **The unused deposit.** Because you deposit `price × multiplier`, a residual stays
locked in escrow after your calls. Set `REFUND_AFTER_REQUESTS=true` to call
`scheme.refund(url)` and reclaim it, but note the on-chain **withdraw delay** (~1 day
on mainnet) before the funds are releasable. Lower `DEPOSIT_MULTIPLIER` to shrink the
residual, at the cost of re-depositing sooner if the burst runs long.
:::warning Switching away mid-channel
Any unclaimed-but-deposited balance is locked until the withdraw delay elapses. Don't
open a large channel for a workload you might abandon; size the deposit to the burst.
:::
## Caveats
- This custom client and the
[OneSource MCP Server's batch mode](/getting-started/mcp/configuration#batch-settlement-payment-channels)
both batch against OneSource out of the box. An exact-only wallet pointed at the
same endpoints silently pays per call; that's fine, just not batched. Your wallet's
own documentation is the authority on whether it implements batch-settlement.
- The scheme is published in TypeScript and Go. This guide is the TypeScript path; the Go
SDK exposes the equivalent batch-settlement client.
- For the protocol-level contract (authorizer model, voucher format, withdraw mechanics),
see the [x402 batch-settlement spec](https://docs.x402.org/schemes/batch-settlement).
## Next
- [x402 on Base](/getting-started/x402-and-mpp/x402-base): the per-call scheme and the conceptual batch overview
- [Batching and cost optimization](/guides/batching-and-cost-optimization): when batching beats other savings
- [MPP on Tempo](/getting-started/x402-and-mpp/mpp-tempo): the same amortization model on Tempo
---
## Overview
# OneSource REST API & MCP Server
A REST and MCP interface to live EVM data: balances, NFTs, transactions, events, contracts, ENS, and chain utilities, plus [Deepstate](/deepstate/) protocol market data and [The Standard Reserve](/standard/) on Robinhood Chain. Serves Ethereum mainnet by default for chain data, plus the Sepolia testnet and Robinhood Chain via the `network` parameter. Designed for AI agents and apps that need fresh, on-chain answers without running their own node.
- **Base URL:** `https://api.onesource.io`
- **MCP server:** [`@one-source/mcp`](https://www.npmjs.com/package/@one-source/mcp) on npm
- **OpenAPI:** [`/openapi.json`](https://api.onesource.io/openapi.json)
- **AI-optimized docs:** [`/llms-full.txt`](pathname:///llms-full.txt)
## Three ways to access
| Access path | How you authenticate | Best for |
|---|---|---|
| **API key** | `Authorization: Bearer sk_…` | Production apps, predictable monthly billing via Stripe |
| **MCP server** | `ONESOURCE_API_KEY=sk_…` (or an x402 / MPP wallet) | AI assistants: Claude Code, Claude Desktop, Cursor, Windsurf |
| **x402 / MPP** | USDC on Base (x402) or USDC.e on Tempo (MPP) | Pay-per-call from agents, no signup required |
See [Getting Started](/getting-started/) to pick a path.
## Response envelope
Every `/api/*` response is wrapped:
```json
{ "data": { ... }, "error": null, "meta": { "endpoint": "/api/chain/network-info", "request_id": "…" } }
```
On failure, `data` is `null` and `error` is `{ code, message }`. Standard HTTP status codes apply (`200`, `400`, `401`, `403`, `404`, `429`).
[Deepstate market data](/deepstate/) (`/deepstate/v1/*`) does not use this envelope; see its own [response shape](/deepstate/#response-shape).
## What's covered
- **Chain RPC:** `eth_call`, gas estimate, code, nonce, storage, receipts, blocks, pending block, network info
- **Balances:** ETH + ERC20 (batched), ERC1155, ERC20 allowances and total supply
- **NFTs:** ownership, metadata with IPFS/Arweave resolution, ERC721 token enumeration
- **Logs:** `eth_getLogs`, ERC20 transfer history
- **ENS:** forward resolution
- **Identity:** proxy detection, contract introspection
- **Deepstate market data:** order books, trades, candles, stats, maker analytics, gas cost-to-quote, and depth history for the Deepstate protocol on Robinhood Chain
- **The Standard Reserve:** charters, branches, auctions, epochs, issuance, exit pressure, vaults, and a free verified-address registry for the Standard Reserve protocol on Robinhood Chain
- **Free utilities:** pricing, health, OpenAPI, sitemap, llms.txt
Full endpoint catalog: [API Reference](/api-reference/). Deepstate's own reference: [Deepstate Market Data](/deepstate/). Standard Reserve's own reference: [The Standard Reserve](/standard/).
---
## Charters, branches, and licenses
## Charter: the membership token
A charter is a soulbound (non-transferable, it can't be sold or moved once minted) ERC-721 token. It's the position that lets its holder open branches. The charter supply comes from two sources: the genesis mint, and the charter auction (below), which sells new charters directly on an ongoing basis. A charter earns nothing on its own; it's only productive once it has one or more open branches.
## Branch: the thing that earns
A branch is a license opened under a charter that accrues STANDARD issuance continuously for as long as it stays open. A single charter can open more than one branch. [Days of issuance](./deciding-with-the-data#what-does-a-branch-cost-right-now) restates a license's price in terms of how many days of that branch's own issuance it would take to earn the price back, deliberately not framed as a yield, APY, or payback period, since this API never states a return figure.
## License: what you burn to open a branch
A license is purchased through the **license auction** and paid for in STANDARD, which is burned on purchase (100% of it: licenses are one of the token's three burn paths, alongside buybacks and the resolution fee).
**Protocol v1.1 (live 2026-09-21) replaced the license auction contract outright** and changed its cadence: per the team's own announcement, each "day" window is now 12 hours rather than 24, so two auction windows run per calendar day, each offering its own batch of licenses; a charter can still buy at most 3 branches in any rolling 24-hour period regardless of how many windows that spans. `GET /v1/addresses` reflects the new contract address; the retired one is kept in the registry so its historical auction days stay attributable. The cadence, per-charter cap, and half-life below come from the team's announcement, not yet an independently confirmed read of the new contract's own bytecode (it isn't Sourcify-verified either). The day-window mechanics themselves (day roll, opening-price rule, floor-price recompute) are assumed unchanged from the retired contract, since the announcement describes it as a parameter migration rather than a behavioral rewrite, but that assumption hasn't been fork-replayed against the new deployment yet.
- **Supply per window**: the number of licenses on offer per auction window is a chain-read constant (`licenses_per_day` or equivalent), not fixed at 1 or unlimited; `GET /v1/branches/summary` and `GET /v1/auctions/current` report it live.
- **Intra-window decay**: within a window, the price decays on an exponential half-life, the same *excess-over-floor* shape confirmed for the trading tax hook (see [Trading tax hook](./trading-tax-hook)): `price(t) = floor + (start − floor) · 0.5^(t / half_life)`, truncated, not rounded, and not a whole-price decay. This shape was measured directly against three real day-0 purchases and matched a fork replay to the wei on the retired contract (`basis: inferred_from_bytecode`); under v1.1 the half-life itself is a faster 2 hours per the team's announcement, read live off the new contract rather than hardcoded, but not yet independently re-confirmed by a fork replay the way the original value was.
- **Day roll**: when a new day's first sale-triggering call lands, the contract "rolls." The old day's unsold offer disappears (it doesn't carry over), and a new day's ladder starts. The new day's **opening price** is `max(start_multiplier × yesterday's last sale price, today's floor price)` if a sale happened the prior day, or `start_multiplier × today's floor price` if it didn't. The **floor price** itself is recomputed from the live `base_rate`, multiplier, and total-branches count at the moment of the roll, which is why the floor can move day to day even without a governance change. Both rules are `basis: inferred_from_bytecode`, pinned by nine wei-exact fork replays, and pending confirmation against real mainnet day-roll behavior beyond what's already been observed live.
- **When the next day opens**: `GET /v1/auctions/current` (`1s_std_auctions_current`), `GET /v1/branches/auction/live`, and `GET /v1/branches/doi` (`1s_std_branches_doi`) each carry a `day_clock` object: `current_day`, `day_started_at`, `next_day_starts_at`, `seconds_to_next_day`, and `auction_day_seconds`, all `basis: "derived"`. These come from `auctionAnchor() + n × auction_day_seconds`, not from the day-roll event, since that event only fires on the first sale of a new day and so can't tell you a boundary before it happens. `auction_day_seconds` is read live from the contract, not assumed to be 24 hours: under v1.1 it's 12 hours, so `current_day` now increments twice per calendar day. A charter auction's anchor is only set once that auction's `start()` call runs, not at deploy, so a charter auction that hasn't started yet reports every `day_clock` field as `null` with a sibling `day_clock_reason` explaining why.
## Charter auction: the other way in
Buying a license under an *existing* charter is one path to a branch. The **charter auction** is the other: it sells new charters directly, on its own Dutch-auction schedule, and its own price (converted to ETH) doesn't always match what a license would cost converted to ETH on the same day. `GET /v1/branches/license-cost` computes both prices in ETH terms (including buy tax and pool price impact) and names the cheaper of the two (`cheapest_path`) so you don't have to eyeball it. `GET /v1/auctions/days?kind=license` and `?kind=charter` give recent per-day history (open/floor/close price and sell-out timing) for judging whether today's number looks typical for either auction; license days also carry purchase aggregates (transaction count, distinct charters, average price, total paid, and a purchase-size distribution), which are null for charter days.
## Where to look
| Question | Route | MCP tool |
|---|---|---|
| What does a license cost today, in ETH? | `GET /v1/branches/license-cost` | `1s_std_license_cost` |
| How many more licenses can this specific charter still buy today? | `GET /v1/charters/{id}/license-headroom` | `1s_std_license_headroom` |
| What's a branch worth in days-of-issuance terms? | `GET /v1/branches/doi` | `1s_std_branches_doi` |
| Recent auction days, prices, and sell-out speed | `GET /v1/auctions/days` | `1s_std_auction_days` |
| Standing license bids: who bid, at what max, and whether each can fill | `GET /v1/auctions/license/bids` | `1s_std_license_bids` |
| Inputs for a standing bid at a max price, in one call | `GET /v1/decisions/bid` | `1s_std_decision_bid` |
| One call bundling the branch-buy decision | `GET /v1/decisions/branch` | `1s_std_decision_branch` |
See [Deciding with the data](./deciding-with-the-data#what-does-a-branch-cost-right-now) for the full branch-cost decision walkthrough, and [What goes into a standing license bid?](./deciding-with-the-data#what-goes-into-a-standing-license-bid) for how standing bids work.
---
## Contracts and addresses
The Standard Reserve deployed 12 contracts on Robinhood Chain (chain id `4663`) at launch, plus it shares the chain's existing Uniswap v4 `PoolManager` singleton rather than deploying its own. **Protocol v1.1 (live 2026-09-21)** added two more (`incentivesVault`, `polBuyback`) and replaced `licenseAuction` outright with a new deployment, kept in the registry as `licenseAuctionLegacy` so its auction history stays attributable to the contract that actually ran it. A later addition, `sbills` (S-Bills fixed-term staking, deployed 2026-10-01; see [S-Bills: fixed-term staking](./s-bills)), is not in the `AddressRegistry` at all, the only one of the 16 that isn't. **`GET /v1/addresses` is the live source of truth**, it's free, requires no API key or payment, and exists specifically so you can check an address before sending it funds or trusting data attributed to it. Don't hardcode addresses from this page or any other document into anything that moves money; read them from that endpoint at call time.
## The 16 roles, plus the shared pool singleton
| Role | What it does |
|---|---|
| `registry` | Address book the other contracts resolve each other's roles through |
| `centralBank` | Issuance authority: reads pool net flow, sets the multiplier, routes fees |
| `standard` | The `$STANDARD` ERC-20 token itself |
| `charterNFT` | The soulbound charter ERC-721 |
| `taxHook` | Uniswap v4 hook applying the launch-tax decay on swaps (see [Trading tax hook](./trading-tax-hook)) |
| `polManager` | Manages protocol-owned liquidity in the ETH/STANDARD pool, including the TWAP-deviation gate |
| `feeSplitter` | Routes protocol fee flow across destinations |
| `contractionVault` | Holds ETH for automated buybacks during contraction |
| `expansionVault` | Holds ETH for reserve-asset accumulation during expansion. Since 2026-10-02 the registry points it at a multisig Safe (2 of 4 signers); see [Reserves, vaults, POL, and buybacks](./reserves-vaults-and-buybacks#the-two-vaults) |
| `licenseAuction` | Runs the branch-license Dutch auction. Protocol v1.1 replacement, active from block 69222157; see [Charters, branches, and licenses](./charters-branches-licenses) for the cadence change |
| `licenseAuctionLegacy` | The retired license auction this replaced. Kept in the registry, not scanned for new activity, so auction days sold before the switch stay attributable to the contract that actually ran them |
| `charterAuction` | Runs the charter Dutch auction |
| `genesisMinter` | Handled the founding-charter genesis mint |
| `incentivesVault` | Protocol v1.1: holds STANDARD bought back by `polBuyback`, for future distribution (see [Reserves, vaults, POL, and buybacks](./reserves-vaults-and-buybacks)) |
| `polBuyback` | Protocol v1.1: executes the bid-side buybacks that fund `incentivesVault`. Its buyback events are decoded and served as `pol_buyback` rows in `GET /v1/flows/protocol` |
| `poolManager` (shared) | Uniswap v4's own singleton contract; the ETH/STANDARD pool is one `PoolId` inside it, not a separate deployed contract |
| `sbills` | Fixed-term STANDARD staking (S-Bills). Not in the `AddressRegistry`; OneSource hard-codes the address and checks it against the registry and token addresses embedded in the contract's own bytecode (see [S-Bills: fixed-term staking](./s-bills#not-in-the-address-registry)) |
Every address was sourced from the team's own published site bundle, their live app's configuration, not an explorer name match, since name matching on an explorer is exactly the copycat vector this registry exists to defend against. Each was then cross-checked as present on-chain, with non-zero bytecode, at its stated deployment block. `taxHook` deployed as its own separate contract, later than `centralBank`/`standard`/`charterNFT` (which share one deploy block). This differs from the rehearsal cluster's arrangement, where the Central Bank contract itself doubled as the pool's hook; don't assume this deployment's pattern carries over to any other TSR-shaped deployment. The three Protocol v1.1 additions (`licenseAuction`'s new deployment, `incentivesVault`, `polBuyback`) were sourced and cross-checked the same way; `licenseAuctionLegacy` keeps its original launch-era source, unchanged by the migration. `sbills` was sourced from the team's stake page bundle and verified on-chain the same way, but it isn't a registry role at all: there's no registry entry to cross-check it against, only the bytecode-embedded addresses described below.
## Verification status: read this honestly
**2 of the 16 contracts have verified source: `standard` (the `$STANDARD` token) and `taxHook`.** Both verified on Sourcify (`exact_match`, 2026-09-16), compiled with `solc 0.8.26`. The other 14, including `registry` and `sbills`, remain unverified on Sourcify and Blockscout. That means:
- The **addresses and deploy blocks** are trustworthy for all 16, cross-checked against live bytecode presence at each block, not just copied from an announcement.
- For `standard` and `taxHook`, the **mechanics** described elsewhere in this section (the tax decay formula, exact-output tax grossing, the epoch net-flow window, the supply/burn/retirement model) are read directly from verified Solidity, the strongest claim this documentation makes.
- `registry` (the `AddressRegistry`) is a special case: it hasn't verified on Sourcify itself, but its full source ships inside both verified bundles above (all three contracts were compiled together), so its ABI and behavior are known from source even though the registry's own bytecode carries no Sourcify record of its own. Treat it as source-known, not source-verified: a caller checking Sourcify directly for the registry's address will still see no match.
- For the other 12 registry-listed contracts (`centralBank`, `charterNFT`, `polManager`, `feeSplitter`, `contractionVault`, `expansionVault`, `licenseAuction`, `licenseAuctionLegacy`, `charterAuction`, `genesisMinter`, `incentivesVault`, `polBuyback`), the **mechanics** (the multiplier step rule, the day-roll pricing rule, and similar) are still not read from a verified Solidity file. They're established either by `measured` on-chain calls and event logs at real blocks, or by `inferred_from_bytecode`, running the actual deployed bytecode against controlled inputs on a forked copy of the chain, which is strong evidence (it's the real code executing) but only pins the cases actually exercised, not a full audited account of every code path. `incentivesVault` and `polBuyback` are newer still: their behavior is currently established only by the team's own announcement, not yet by a fork replay against either contract's deployed bytecode.
- `sbills` has no verified source and, unlike every other row here, no Sourcify-adjacent ABI to fall back on: its full interface was recovered from bytecode (no verified source exists anywhere for it), cross-checked against the 4-byte selectors and event topics the bytecode actually contains, and against a handful of parameter names recovered from the team's own published site bundle. Treat its function and event names as recovered, not source-confirmed, even where this documentation states them plainly.
- Several event names attached to the POL Manager contract are `inferred` rather than hash-confirmed: a candidate name matches the contract's behavior and call pattern, but no keccak-256 hash of a candidate signature matched the observed topic. Treat those field names as working labels, not confirmed identifiers.
- Every API field built from an unverified contract's mechanics is labeled `basis: inferred_from_bytecode` (or the matching `_basis` sibling) so it's never confused with a source-verified read. See [How to read a TSR response](./reading-a-response#how-sure-the-api-is-basis).
`GET /v1/addresses` reports a `registry_status` of `placeholder`, `partial`, or `verified` precisely so a caller doesn't have to track this by reading documentation. Anything not listed, or listed below `verified`, should be treated as unconfirmed regardless of what any other source (including this page) claims.
## Explorer
Robinhood Chain's block explorer is at `robinhoodchain.blockscout.com`; it is unreachable from outside networks. Sourcify (`sourcify.dev`) is the verification source this page relies on instead. Re-check both directly rather than assuming this page's verification status stays current, since a verification landing after this page was written wouldn't be reflected here.
## Where to look
| Question | Route | MCP tool |
|---|---|---|
| Is this address actually a Standard Reserve contract? | `GET /v1/addresses` | `1s_std_addresses` |
| What genesis-mint metrics does the reserve report? | `GET /v1/genesis/live`, `/v1/genesis/mints` | `1s_std_genesis_live` |
---
## Deciding with the data
The rest of this section is organized by route. This page is organized by question: pick the decision you're facing, and it names the routes and MCP tools that answer it. See [How to read a TSR response](./reading-a-response) first for what `basis`, `serving_state`, and `assumptions[]` mean below.
## Should I exit now?
Exiting retires a charter's open branches for a pro-rata share of the reserve, minus a resolution fee that scales with system-wide exit pressure (how much has already left recently relative to what's held). The fee moves with everyone else's behavior, not just yours, so "now vs. later" is a real question.
| Route | MCP tool | What it answers |
|---|---|---|
| `GET /v1/charters/{id}/exit-quote` | `1s_std_exit_quote` | Your actual quote: gross, fee, net, and `realizable_eth` (net STANDARD sold into the pool, estimated) |
| `GET /v1/exit/fee-curve` | `1s_std_exit_fee_curve` | How the fee rate would move at larger or smaller withdrawal sizes than yours |
| `GET /v1/exit/fee-forecast` | `1s_std_exit_fee_forecast` | A current-pace projection of the fee rate assuming no one else exits between now and then |
| `GET /v1/tax/schedule` | `1s_std_tax_schedule` | The sell-tax bps applied to `realizable_eth`, falling during the launch decay window, so timing can matter here independently of the exit fee. The decay is `basis: inferred_from_bytecode`: the *excess over the floor* rate halves every 4 minutes and truncates, it isn't a whole-rate decay, and this is pending confirmation at the first real epoch settlement (~2026-09-18 01:00Z) |
| `GET /v1/decisions/exit` | `1s_std_decision_exit` | All of the above in one call, plus pool state and pending governance changes to fees or resolution, bundled for this one decision |
`realizable_eth` and the fee-curve ladder are estimates with `assumptions[]`: a single-range constant-liquidity approximation of the pool swap. Re-quote right before you transact rather than caching either.
`GET /v1/decisions/exit` also carries `parts.sbills_alternative`: what the exit's net STANDARD (`exit_quote.net_raw`, after the exit fee) would earn staked for one S-Bills term at today's quote. It reports the rate paid, the premium, the term, and the exit fee that bill would pay if it left early; `summary.sbills_alternative_*` repeats the headline numbers. It is `basis: derived` and assumes the STANDARD is staked immediately at today's quote as the only deposit. The part is skipped, with a reason, when the exit has no positive net amount. The two options sit side by side as inputs, and the route doesn't say which is better.
The fee-forecast route also names *when* the fee changes, not just where it's headed: `expiry_schedule` lists each window day still counted in the fee, when it drops out, and the resulting rate, and `next_fee_change_at` picks out the first one that actually moves the number. It's null (with `incomplete_days` naming the gap) if a day in the window can't currently be computed.
## What does a branch cost right now?
There are two ways to add a branch: buy today's Dutch-auction license, or buy into the charter auction directly. They aren't always the same price once you convert both to ETH.
| Route | MCP tool | What it answers |
|---|---|---|
| `GET /v1/branches/license-cost` | `1s_std_license_cost` | Today's license price converted to an estimated ETH cost (buy tax + pool price impact included), compared against the charter auction's own ETH price, with `cheapest_path` naming the cheaper route |
| `GET /v1/charters/{id}/license-headroom` | `1s_std_license_headroom` | How many more licenses *this* charter can still buy today, with a live quote for each remaining slot |
| `GET /v1/auctions/days?kind=license` or `kind=charter` | `1s_std_auction_days` | Recent days' open/floor/close prices and how fast each sold out, for judging whether today's price is typical. For `kind=license`, each day also carries purchase aggregates: transaction count, distinct charters, count-weighted average price, total paid, and a purchase-size distribution (null for `kind=charter`) |
| `GET /v1/decisions/branch` | `1s_std_decision_branch` | The above plus days-of-issuance and any pending governance change to the licenses-per-day rate, bundled |
**Buy today's license or wait for tomorrow's?** `GET /v1/decisions/branch` (`1s_std_decision_branch`) carries `summary.next_day_starts_at` and `summary.seconds_to_next_day`, lifted from the same `day_clock` object described in [Charters, branches, and licenses](./charters-branches-licenses#license-what-you-burn-to-open-a-branch). Both are `null` if the license auction hasn't started yet or the boundary can't be computed. There's no built-in "wait and save" recommendation: the next day's opening price resets off *that* day's own floor and last sale rather than continuing the current decay, so knowing the countdown tells you when the price resets, not which side of it is cheaper. Pair it with `GET /v1/auctions/days?kind=license` to see whether recent day-opens have landed above or below where today's price already sits.
"Days of issuance" (`GET /v1/branches/doi`, `1s_std_branches_doi`) is how this API expresses what a license's STANDARD price buys you in issuance terms; it's not a payback calculation, and there's no ROI or APY framing anywhere in this API. Say "days of issuance" or "issuance in ETH terms," not "yield."
**Planning a multi-day expansion, rather than a single license buy?** `GET /v1/decisions/plan` (`1s_std_decision_plan`, $0.05 like the other composite routes) seeds a keep/selective/aggressive strategy simulation from this API's own live routes (license price, pool price, issuance rate, branch count) and runs it forward over a horizon you choose. It's explicitly a simulation for comparing strategies, not a forecast: the response never names a winning strategy, and every seeded assumption is reported with a `source` (`seeded_live`, `user_supplied`, or `default`) so you can tell what it held constant. Each strategy also carries `stake_instead_of_sale`: the STANDARD it would release at the end of the horizon, priced as one S-Bill deposited then instead of sold. `quote_now` is the contract's own quote for that amount today (measured). `at_horizon_end` projects the rate paid and premium at the moment of the stake from the contract's drift rule and price-impact curve (derived, computed locally, and within about 1e-12 relative of simulated on-chain quotes when no parameters or deposits change). It assumes the stake is the only deposit and the rate parameters don't change. The block is `{available: false, reason}` when a strategy releases nothing or a read failed. It sits beside the three strategies rather than ranking against them, so the plan still never names a winner.
The next day's opening license price (`GET /v1/auctions/current`, `/v1/auctions/days`) is also `basis: inferred_from_bytecode`: it's `max(start_multiplier × yesterday's last sale, today's floor price)` when a sale happened the prior day, or `start_multiplier × today's floor price` when it didn't, pending confirmation at the first real epoch settlement (~2026-09-18 01:00Z).
**What will the charter auction's price be later today?** `GET /v1/auctions/current` carries `charter.expected_prices` (and `GET /v1/decisions/charter` repeats it as `summary.charter_expected_prices`): the charter price at +15m, +30m, +1h, +4h, and `end_of_day`, the last second before the next day opens. It is `basis: derived`, decaying from the measured `currentPrice()` with the charter's own `dayHalfLife` and `dayFloorPrice`, and it matches simulated on-chain prices within about 11 wei. It assumes no charter is bought first and that the half-life, floor, and bank regime stay as they are. A row that would land in the next auction day is `null` with a `reason`, since the next day's opening price isn't projected. In contraction regime every row is `null`: the curve was verified only outside contraction, where `currentPrice()` doubles.
## What will policy do next?
The Central Bank's issuance rate moves with a multiplier that steps up or down based on the sign of net ETH flow across consecutive epochs (a fixed multi-day settlement window). Both the step rule and what counts as "the prior epoch's flow" for this purpose were established by running the real contract bytecode against controlled inputs, since no verified source exists for it (`basis: inferred_from_bytecode`; see [How to read a TSR response](./reading-a-response#how-sure-the-api-is-basis)). Two details worth knowing before you read a projection: a zero net-flow signal *holds* the multiplier rather than cutting or raising it, and no cut can fire at the very first epoch settlement no matter what the signal says. Both are pending confirmation against the contracts' live behavior at the first real settlement (~2026-09-18 01:00Z).
| Route | MCP tool | What it answers |
|---|---|---|
| `GET /v1/policy/outlook` | `1s_std_policy_outlook` | A current-sign-holds projection: what the multiplier and issuance rate would be if the current epoch's net-flow sign doesn't change before close |
| `GET /v1/issuance/runway` | `1s_std_issuance_runway` | Cumulative issuance against the total budget, current stream rate, and a same-rate projection of when the budget runs out |
| `GET /v1/governance/changes` | `1s_std_governance_changes` | Param-change history, anything queued but not yet applied, one-way switch states (including the address registry's `execution_permissionless`), and guardian pause state |
| `GET /v1/policy/current` | `1s_std_policy_current` | The latest settled (or, before first settlement, live-estimated) epoch: signal, regime, multiplier, issuance |
`multiplier_if_sign_holds_raw` and `issuance_if_sign_holds_raw` are projections, not predictions: nothing stops the sign from flipping before the epoch closes, and the response says so in `assumptions[]`.
## Is the token backed, and is the buyback mechanism working?
STANDARD is backed by ETH held in two vaults (expansion and contraction) plus any configured reserve assets, with an automated buyback that burns tokens when conditions allow it.
| Route | MCP tool | What it answers |
|---|---|---|
| `GET /v1/backing/current` | `1s_std_backing` | Vault ETH, reserve holdings, and ETH backing per circulating STANDARD, both including and excluding protocol-owned liquidity |
| `GET /v1/backing/history` | (same tool, ranged) | The same ratio over time, so a single snapshot isn't read as the whole trend |
| `GET /v1/buybacks/readiness` | `1s_std_buyback_readiness` | Whether a buyback tick could fire right now: capacity, cooldown, TWAP deviation, and recent executed ticks |
| `GET /v1/decisions/charter` | `1s_std_decision_charter` | Backing and holder concentration bundled with the charter-buy decision, since both speak to how established the reserve is |
Protocol-owned liquidity (POL, the paired liquidity the STANDARD/ETH pool itself needs to function) is reported separately from free reserve backing. It isn't a claim you can redeem against circulating supply, so `backing/current` reports both an excluding-POL and an including-POL figure rather than picking one. Collected trading taxes sitting in the hook or the FeeSplitter, awaiting the permissionless `forwardTaxes()` push (`hook_eth_balance_raw`/`fee_splitter_eth_balance_raw` on `/v1/buybacks/readiness`), aren't counted in either backing figure either: they're revenue in transit, not reserve holdings. See [Reserves, vaults, POL, and buybacks](./reserves-vaults-and-buybacks#hook-held-taxes-are-not-vault-backing).
## What goes into staking or holding?
`GET /v1/decisions/stake?amount=&wallet=` (`1s_std_decision_stake`, $0.05) bundles the live S-Bills quote for `amount` STANDARD (whole tokens, not wei) with the current S-Bills state. Pass `wallet` and it adds that wallet's open principal and its headroom under the per-wallet cap; the same optional `wallet` is also accepted by `GET /v1/sbills/quote`, which then reports `exceeds_wallet_cap`, `wallet_open_principal_raw`, and `wallet_cap_headroom_raw` in `checks`.
The response also carries a top-level `waiting` block: a `basis: derived` projection of the per-term rate 1h, 6h, and 24h from now, plus the time the rate reaches its ceiling. It holds only under the assumption stated in `waiting.assumption`, that nobody else deposits and the admin doesn't change rate parameters. The inputs it starts from (current rate, drift per second, ceiling, last update) are measured from the contract and listed under `waiting.inputs`. Each point's `rate` is the drifted rate. `quote_rate_paid` is this amount's quote at that rate, computed with the contract's price-impact curve (the rate decays toward the floor as the amount grows). Against simulated on-chain quotes it lands within about 5e-11 relative at 1 STANDARD, 7e-13 at 50, and 6e-16 from 123,456 up. It reports what the rate does under that assumption and nothing about timing a deposit.
`GET /v1/decisions/hold` (`1s_std_decision_hold`, $0.05, no parameters) bundles the policy outlook, issuance runway, the latest pending governance changes, the tax schedule, buyback readiness, and the dormancy count. The dormancy part carries the number of reportable wallets (capped at 500, with `count_is_lower_bound`), not the wallet list. Both are null, with `constants_missing: true`, until the dormancy window has been read from chain. To get the wallets, page `GET /v1/dormancy?reportable=true`.
Both routes report each underlying read under `parts..status` and return inputs only.
| Route | MCP tool | What it answers |
|---|---|---|
| `GET /v1/decisions/stake` | `1s_std_decision_stake` | The S-Bills quote, live S-Bills state, optional wallet cap headroom, and the derived rate projection, bundled |
| `GET /v1/decisions/sbill` | `1s_std_decision_sbill` | One bill's record and live state, with the exit-fee schedule (active) or the redeem and roll figures (matured) |
| `GET /v1/decisions/hold` | `1s_std_decision_hold` | Policy outlook, issuance runway, pending governance, tax schedule, buyback readiness, and dormancy count, bundled |
**What should I do with one S-Bill I already hold?** `GET /v1/decisions/sbill?bill_id=` (`1s_std_decision_sbill`, $0.05) returns the bill's record and the live S-Bills state, plus a block that depends on the bill's `state`. Each block is always an object, with `applicable: false` and a `reason` when it doesn't apply.
- **Active bill:** `exit_schedule.exit_now` is measured from the contract's own `quoteExit`: the fee, the payout, and the premium vested so far. An early exit pays principal minus the fee whenever it happens; the premium and any bonus never go to a leaver (`premium_given_up`). `fee_by_day` is derived: the fee the bill would pay if it exited on each coming day until older exits leave the 7-day window or the bill matures. It assumes no other exits, deposits, maturities, or settlements. `lowest_projected_fee` names the earliest cheapest day and the saving against today, and `projection_check` compares today's projected fee with `quoteExit` on every response.
- **Matured bill:** `maturity.redeem_quote` is measured from `quoteRedeem`. `maturity.roll` is derived: the new principal (the redeem payout), plus the rate and premium from a fresh quote at roll time. Only the bill's owner can roll. There is no auto-roll, so a matured bill earns nothing until its owner redeems or rolls it.
- **Exited or redeemed bill:** the record only.
An unknown `bill_id` returns 404 and a malformed one returns 400.
## What goes into a standing license bid?
A standing bid is an order on a charter to buy licenses at or below a maximum unit price. One bid per charter; only the charter NFT's owner can place or cancel it, for 1 to 3 licenses. Nothing is escrowed when it is placed. A fill is paid from the charter's pending STANDARD on the Central Bank, at the license price current at fill time (not the bid's max), and it is all-or-nothing: a bid for 3 either buys 3 or buys none. A fill shares the daily license supply with direct buys. Two caps apply per charter: 3 licenses per 86400-second window and 10 branches in total. `fill()` is a separate transaction that any address may send, so the price reaching a bid's max does not by itself fill the bid.
`GET /v1/auctions/license/bids` (`1s_std_license_bids`, $0.02) returns the open-bid book, read from the contract at one pinned block. Each bid carries the contract's own `fillable` answer, the checks behind it, and a `blocking` list naming every failing check: price above max, daily supply used up, charter window cap, charter branch cap, insufficient pending, owner changed, charter burned. A stale bid (its bidder no longer owns the charter) is flagged `stale` and `prunable`. `effective_crossing_price_raw` is `basis: derived`: the lower of the bid's max and its charter's pending STANDARD divided by its count. Pending keeps accruing, so a bid held back by pending can become fillable sooner than that figure implies. Bidder addresses are included, since they are public chain state. Filters are `charter_id`, `bidder`, and `fillable_only`; `sort` is `max_desc` (default) or `charter_id`.
`GET /v1/decisions/bid?max_unit_price=&count=&charter_id=` (`1s_std_decision_bid`, $0.05) bundles the book header, the auction state, and the derived blocks below. `max_unit_price` is in whole STANDARD tokens; `count` and `charter_id` are optional.
- **`crossing`** (derived): when the license price reaches the max, today or on the next auction day, down to the second. It assumes the curve doesn't change first. `crossing.next_day` is the same question for the next day's curve, since a bid at or below the price still waits for the daily reset when today's supply is gone.
- **`queue`** (derived): the other open bids that would fill before the price reaches the max, and the supply left after them. Supply left is clamped at 0, and `licenses_ahead_beyond_supply` reports the overflow. `at_effective_crossing` gives the queue at the price a short-pending charter can actually pay.
- **`this_charter`** (measured, with `charter_id`): pending STANDARD, the window and branch caps, and any existing bid the new one would replace. That bid is left out of the queue. The window cap is checked twice: now, and when the price reaches the max. `window_cap_covers_count_at_crossing` and `window_cap_covers_count_at_next_day_crossing` are `basis: derived` and count the charter's full allowance from `cap_window_resets_at` onward. Window resets fall on auction-day rolls, so a charter that has used its allowance today can usually still fill on the next day's curve. `effective_crossing_day` (`today` or `next_day`) gives the day whose curve reaches the charter's effective price, and `queue.at_effective_crossing` is counted on that day.
- **`fill_execution`**: states that a crossing is not a fill, plus measured `fills_to_date`, `distinct_fillers`, and `last_fill_at` from the events index. If nobody is sending fills, `last_fill_at` goes stale.
Each part reports its own `status`, and the route states no verdict.
| Route | MCP tool | What it answers |
|---|---|---|
| `GET /v1/auctions/license/bids` | `1s_std_license_bids` | Every standing bid, with fillable and blocking reasons, stale flags, and effective crossing price |
| `GET /v1/decisions/bid` | `1s_std_decision_bid` | When the price reaches a max, who is ahead in the book, what a charter can pay, and how fills happen, bundled |
## Who else is here?
Ownership concentration and dormancy both matter for judging how decentralized (and how "use it or lose it") the reserve actually is.
| Route | MCP tool | What it answers |
|---|---|---|
| `GET /v1/holders/concentration` | `1s_std_holders_concentration` | Branch-per-charter distribution, top owners, an HHI concentration score, and a genesis-vs-auction cohort split |
| `GET /v1/wallets/{address}` | `1s_std_wallet` | One wallet's full position: charters, pending owed, dormancy deadline, and license headroom per charter |
| `GET /v1/flow/charters` | `1s_std_flow_charters` | Pool buys and sells by wallets that held at least one Charter when each swap occurred; each swap is counted once and ambiguous multi-swap transactions are excluded |
| `GET /v1/dormancy/bounties` | `1s_std_dormancy_bounties` | Wallets already past their reportable-dormancy window, ranked by estimated bounty |
`reportable_at_block` on the dormancy routes is itself an estimate (built from a measured or, failing that, a hardcoded block-time assumption), not a live chain read. See the route's own description in the [OpenAPI spec](https://api.onesource.io/standard/openapi.yaml) before building an automated `reportDormant()` caller against it.
## Composite routes: one call per decision
`GET /v1/decisions/branch`, `/v1/decisions/exit`, and `/v1/decisions/charter` (MCP: `1s_std_decision_branch`, `1s_std_decision_exit`, `1s_std_decision_charter`) each bundle the routes above into the single call the matching decision actually needs, with a `summary` carrying the handful of numbers you'd look at first and `parts..status` reporting each underlying route's own success or failure independently. Reach for a composite first; drop down to the individual routes above only when you need a field the composite's `summary` doesn't surface, or a parameter (like a custom `branches` count) the composite doesn't forward.
`GET /v1/decisions/plan` (`1s_std_decision_plan`) is a fourth composite with a different shape: instead of bundling several current-state routes into one snapshot, it seeds a forward simulation from them. See "What does a branch cost right now?" above for what it answers.
`GET /v1/decisions/stake` and `GET /v1/decisions/hold` (`1s_std_decision_stake`, `1s_std_decision_hold`) are the fifth and sixth. Stake bundles current-state reads and adds one derived projection; hold is a plain bundle. See "What goes into staking or holding?" above. `GET /v1/decisions/sbill` (`1s_std_decision_sbill`) is the seventh: it takes one bill and returns exit or roll figures depending on the bill's state. See "What should I do with one S-Bill I already hold?" above. `GET /v1/decisions/bid` (`1s_std_decision_bid`) is the eighth: it models a standing license bid at a max price. See "What goes into a standing license bid?" above.
---
## Exits and the resolution fee curve
Exiting retires one or more of a charter's open branches for a pro-rata share of the reserve, paid out as STANDARD you then realize as ETH by selling into the pool. It costs a **resolution fee**, taken as a percentage of the withdrawal before the remainder is realized.
## Exit pressure: the input to the fee
Exit pressure gauges how much withdrawal demand the reserve is absorbing right now, relative to what it's currently holding. The mainnet formula matches the Central Bank's own `previewResolutionFeeWad` view to within 5e-17 relative error at every point on the withdrawal-size ladder (`formula_version: mainnet-exit-2026-10-02`, `basis: derived`):
```
denom = max(bank_total_pending_live, PRESSURE_DENOMINATOR_FLOOR)
pressure = min(withdrawn_7d / denom / PRESSURE_SATURATION, 1)
```
`withdrawn_7d` is the STANDARD actually withdrawn over the **last 7 protocol days, including today**. Protocol days are 86400-second buckets counted from the Central Bank's start time, so the window moves forward one whole day at a time: at each day boundary the oldest day drops out at once. `bank_total_pending_live` is the reserve's current pending (held, not-yet-withdrawn) balance, floored at a fixed constant so the ratio doesn't blow up when the pending balance is small. The result is normalized to run from 0 (no pressure) to 1 (fully saturated).
This is the formula the deployed contract computes; the whitepaper's own draft, `P_exit = W / max(D+W, min_reserve)`, is not what it runs. `GET /v1/exit-pressure/history` rows carry their own `formula` field precisely so a caller can tell which formula produced a given row; `rows_without_formula` in the response counts rows that predate that field, so they aren't silently misread as validated against it.
## The fee: pressure-based and quadratic
```
fee = FEE_MIN + (FEE_MAX − FEE_MIN) · pressure²
```
The fee rises with the **square** of pressure, not linearly: it stays close to the floor for most of the pressure range and accelerates only as pressure approaches saturation. `FEE_MIN` and `FEE_MAX` are chain-read constants (`GET /v1/constants`), and the fee moves with *everyone's* recent withdrawal behavior, not just yours. The same withdrawal quoted at low system-wide pressure and at high pressure can come back with very different fees.
## What each route actually gives you
- `GET /v1/charters/{id}/exit-quote` (`1s_std_exit_quote`), your specific quote right now: gross, fee, net, and `realizable_eth` (an *estimate* of net ETH from selling into the pool, `estimate: true`, carrying `assumptions[]`).
- `GET /v1/exit/fee-curve` (`1s_std_exit_fee_curve`), the fee rate at six points along a withdrawal-size ladder (1%, 5%, 10%, 25%, 50%, 100% of the reserve's pending balance), so you can see how the fee would move at a larger or smaller size than yours.
- `GET /v1/exit/fee-forecast` (`1s_std_exit_fee_forecast`), a **no-further-exits** projection: how the system-wide fee would drift down if nobody else exits between now and a future point. Labeled as a projection, never a prediction. It also carries an `expiry_schedule`: the exact window days still counted in `withdrawn_7d`, the instant each one ages out, and the fee rate that results, plus `next_fee_change_at` for the first expiry that actually moves the rate. If a day in the window is missing or unusable, `incomplete_days` names it and the schedule fields come back null, leaving only the coarser day-offset `forecast`.
- `GET /v1/exit-pressure/current` and `/history` (`1s_std_exit_pressure`), the raw pressure snapshot and its trend over time.
The fee rate an exit would pay **right now** comes from the contract itself: `previewResolutionFeeWad`, read live or from the snapshot's stored preview points (`basis: measured`). That covers the fee in `exit-quote`, the live fields in `fee-curve`, `fee_rate_wad` in `exit-pressure/current`, and day 0 of `fee-forecast`. These are served whatever `exit_formula_version` says. The formula's own figures sit beside them in fields with `formula` in the name, such as `fee_rate_formula_wad`. Those, like every forward-looking point in `fee-forecast`, are null unless the formula is validated.
`realizable_eth` is an estimate: a single-range constant-liquidity approximation of the Uniswap v4 swap (it doesn't model the trade crossing into an adjacent initialized tick), plus whichever side of the trade the sell tax applies to. Re-quote right before transacting instead of caching it. See [How to read a TSR response](./reading-a-response#estimates-carry-assumptions) for what `assumptions[]` covers on estimate fields generally.
## Where to look
| Question | Route | MCP tool |
|---|---|---|
| What would I actually net if I exit now? | `GET /v1/charters/{id}/exit-quote` | `1s_std_exit_quote` |
| How does the fee change at other withdrawal sizes? | `GET /v1/exit/fee-curve` | `1s_std_exit_fee_curve` |
| Where is the fee headed if nobody else exits? | `GET /v1/exit/fee-forecast` | `1s_std_exit_fee_forecast` |
| Everything above, bundled into one call, plus what the net STANDARD would earn staked as an S-Bill | `GET /v1/decisions/exit` | `1s_std_decision_exit` |
See [Deciding with the data](./deciding-with-the-data#should-i-exit-now) for the "should I exit now" decision walkthrough, including the launch-tax interaction on `realizable_eth`.
---
## Governance switches and risks
## Owner-controlled parameters
Several of the mechanics described elsewhere in this section are owner-adjustable, not fixed at deploy time. Confirmed owner-gated setters include the tax hook's override flag and rate (see [Trading tax hook](./trading-tax-hook#the-override-flag)), and the POL Manager's TWAP-deviation bound and burn-vs-transfer setting for collected fees (see [Reserves, vaults, POL, and buybacks](./reserves-vaults-and-buybacks#protocol-owned-liquidity-pol)). `GET /v1/governance/changes` is the single route that surfaces this category of risk across the whole protocol: param-change history, anything **queued but not yet applied**, one-way switch states, and guardian pause state, all in one call.
## One-way switches
Some parameters can only move in one direction once set. One example is whichever flag disables the launch-tax schedule outright rather than letting it be re-enabled. `GET /v1/governance/changes` reports the state of these switches explicitly, since a one-way change that already happened is a materially different risk from a two-way parameter that could be flipped back.
## The address registry: who's allowed to point where
An `AddressRegistry` contract is the book every other Standard Reserve contract resolves the others' roles through (`centralBank`, `taxHook`, `feeSplitter`, `contractionVault`, `expansionVault`, `polManager`, `genesisMinter`, `charterAuction`, `licenseAuction`, plus the `guardian`, `executor`, and `netFlowSource` roles), keyed by name rather than by position. Two mechanisms govern how much that book can change after the fact:
- **`lock(key)` is per-key and one-way.** The owner can freeze any individual key's binding permanently; migration for that role then requires deploying a new contract entirely, not just re-pointing the registry. `TAX_HOOK` is the only key that is locked; every other key remains re-pointable by the owner.
- **`executionPermissionless` is a single one-way switch for the whole registry**, flipped by `openExecution()` and never reversible. It governs who may crank a buyback tick or a bid-side POL buyback: while it reads `false`, only the registry's `executor` address may do either; once opened, anyone can. It does not affect ExpansionVault reserve purchases, which stay owner-only regardless. `executionPermissionless` reads `false`, so buyback ticks and POL buybacks are restricted to the named `executor`. This gating was confirmed against the pre-v1.1 POL Manager; whether the new `polBuyback` contract (see [Contracts and addresses](./contracts-and-addresses)) is gated by the same registry switch, or has its own separate permission logic, hasn't been independently confirmed.
`GET /v1/governance/changes` surfaces both of these under a `registry` object: `execution_permissionless` (with `basis: measured`), the per-key `locked` map, and the current `executor` and `guardian` addresses. `GET /v1/buybacks/readiness` reports the same `executionPermissionless` read as its `permissionless` field, with `permissionless_source` naming exactly which registry call produced it.
## Guardian pause
A guardian role can pause protocol operation. `GET /v1/governance/changes` reports whether that pause is currently active. A caller building anything automated against this API (an exit bot, a license-buying script, anything that reacts to a quote) should check this before assuming a route's absence of an error means the protocol is operating normally. A paused protocol may still answer read routes while write paths are blocked at the contract level.
## What's confirmed vs. genuinely unresolved
This is the honest-accounting section: several mechanics load-bearing enough to affect a real financial decision are not fully pinned down, and presenting them as settled would be worse than saying so plainly.
- **The policy multiplier's first-settlement exemption.** The rule (no cut fires at the bank's very first epoch settlement, regardless of signal) is pinned by fork execution of the real bytecode. *Why* the exemption exists in the contract's design is not visible from that method: the relevant code branch's body wasn't recoverable by static decompilation. Plausible explanations (e.g. requiring a full prior epoch before judging) are not confirmed.
- **No mainnet epoch had settled as of the most recent bytecode analysis behind this documentation.** Every policy-multiplier and step-rule conclusion above was validated by running the deployed contract against chosen inputs on a fork, which is the strongest evidence available without verified source. It hasn't yet been checked against a real, naturally-occurring `EpochSettled` event. Treat `GET /v1/policy/outlook`'s projections as resting on a not-yet-confirmed rule until the first live settlement.
- **The 70/15/15 regime-based ETH split** described in the whitepaper is unverified against the deployed Central Bank contract. Its internal fee-routing logic isn't available to read, verified or otherwise.
- **The contraction vault's rate-limit path** (a supply-controller cap on how fast a contraction tick can burn) has an address configured to the zero address on mainnet. That limiting path has never actually been exercised, so its behavior if a supply controller were ever set is untested.
- **Several POL Manager event and function names are inferred, not hash-confirmed.** See [Contracts and addresses](./contracts-and-addresses#verification-status-read-this-honestly). A descriptive name attached to an unconfirmed selector is a best guess at intent, not a confirmed account of what the function does.
- **Which side pays the underlying Uniswap LP fee** on a swap (separate from the tax hook's own tax) is still not established. The *rate* is known and reported live (`lp_fee_bps` on `GET /v1/pool`, cached from `tsr.constants:pool_lp_fee_ppm`, currently a static 1%), see [Reserves, vaults, POL, and buybacks](./reserves-vaults-and-buybacks#the-pools-lp-fee), but which party actually bears that fee on a given swap is called out as unresolved in `docs/tsr/BYTECODE_SEMANTICS_2026-09-15.md` in `sre-services` and hasn't been investigated further since. Don't read the rate being known as the payer question also being settled.
None of the above is flagged to suggest the protocol is broken. It's flagged because "inferred from the deployed bytecode by a careful third party" and "confirmed by the protocol's own verified source" are different strengths of claim, and this API's `basis` field exists precisely so a caller can tell which one they're looking at rather than have it presented as uniform certainty.
## Where to look
| Question | Route | MCP tool |
|---|---|---|
| What governance changes have happened, are queued, or are one-way? | `GET /v1/governance/changes` | `1s_std_governance_changes` |
| Is the guardian pause active right now? | `GET /v1/governance/changes` | `1s_std_governance_changes` |
| Is buyback/POL execution open to the public yet, and who's the executor/guardian? | `GET /v1/governance/changes`, `GET /v1/buybacks/readiness` | `1s_std_governance_changes`, `1s_std_buyback_readiness` |
---
## The Standard Reserve
The Standard Reserve ("STANDARD", [standardreserve.xyz](https://standardreserve.xyz)) is an on-chain central-bank protocol on Robinhood Chain (chain id `4663`). It runs charters (soulbound, meaning non-transferable, ERC-721 tokens that represent a member's standing), branches, a license auction and a charter auction, epochs with a policy multiplier and net-flow signal, issuance, an exit fee, dormancy tracking, two vaults, protocol-owned liquidity, and a tax hook. OneSource indexes the deployed contracts and serves the results as a read-only REST API.
- **Base URL:** `https://api.onesource.io/standard/v1/...`
- **OpenAPI spec:** [`/standard/openapi.yaml`](https://api.onesource.io/standard/openapi.yaml) (free, no auth)
- **Network:** Robinhood Chain only. These routes take no `network` parameter.
Every number the API returns comes from reading the deployed contracts, not from the whitepaper. The whitepaper's own constants are redacted; the contracts are the source of truth, and this API reports what they expose.
## Check an address before you pay anyone
Over 100 lookalike contracts impersonate Standard Reserve components. `GET /v1/addresses` is free and requires no payment or API key for exactly this reason: check an address before you send it money or trust data from it, not after.
```bash
curl https://api.onesource.io/standard/v1/addresses
```
The response always answers with `HTTP 200`, even before OneSource has finished verifying every address, and includes a `registry_status` of `placeholder`, `partial`, or `verified` so you know how far verification has progressed. Treat anything not listed, or listed with a status below `verified`, as unconfirmed.
Also free: `GET /v1/genesis/live` and `GET /v1/genesis/mints` (head-fresh genesis mint metrics), `GET /v1/health`, `GET /v1/ready`, and the OpenAPI document itself.
## Everything else is paid
Every other route is pay-per-call with x402 (USDC on Base) or covered by an API key subscription. There are no public pricing tiers: the API key subscription is the primary way to use the paid routes, and x402 covers per-call pricing. Exact prices aren't listed here; the response body below and the [OpenAPI document](https://api.onesource.io/standard/openapi.yaml) are the source of truth, so this page can't drift out of sync with what's actually charged.
A paid call without a valid `Authorization` header or payment signature gets back an `HTTP 402` with a JSON body in the x402 v2 shape: a `resource` naming the URL and route, and an `accepts` array of ways to pay it. Each entry gives a `scheme` (`exact`), a `network` (`eip155:8453` for Base), the `asset` (the USDC contract), a `maxAmountRequired` in USDC base units, and the `payTo` address; the body also carries `payment_rails` and `api_key_subscription` hints pointing at the alternative. Sign and retry against one of the `accepts` entries, or subscribe with an API key instead. See [x402 on Base](/getting-started/x402-and-mpp/x402-base) and [MPP on Tempo](/getting-started/x402-and-mpp/mpp-tempo) for the full client setup, or [Getting Started](/getting-started/) to pick an access path.
```bash
curl https://api.onesource.io/standard/v1/policy/current
# HTTP/1.1 402 Payment Required
# {
# "x402Version": 2,
# "resource": { "url": "...", "description": "..." },
# "accepts": [
# { "scheme": "exact", "network": "eip155:8453", "asset": "...", "maxAmountRequired": "...", "payTo": "..." }
# ],
# "payment_rails": "...",
# "api_key_subscription": "..."
# }
```
## Reading a response: basis, as_of_block, serving_state
Every served number carries a small set of fields that tell you how much to trust it: `basis` (`measured`, `derived`, `constant`, or `inferred_from_bytecode`), `as_of_block`, and `serving_state` (`fresh` / `lagging` / `withheld`). See [How to read a TSR response](./reading-a-response) for the full rundown, including the null-vs-zero rule, `formula_version` gates, and how `_raw` fields are scaled.
Not sure which route answers your actual question? [Deciding with the data](./deciding-with-the-data) maps five common decisions (exit, branch cost, policy, backing, participants) to the routes and MCP tools that answer them.
## What the API covers
Mechanics OneSource indexes and serves as measured or derived data:
- **Charters**: soulbound ERC-721 membership tokens, keyset-paginated (`before`/`after`/`order` on `charter_id`, with `next_before`/`next_after`/`total_charters`; an unrecognized query parameter is a `400`, not a silent no-op), and an exit quote per charter (`realizable_eth` estimates the ETH you'd actually net after selling into the pool; `at_block` reruns the price-impact math against a historical pool state)
- **Branches**: a summary of branch-level state (`GET /v1/branches/summary`, including `license_price_in_branch_days_basis` for which price basis (current or floor) priced the branch-days figure), and what a license costs converted to ETH right now (`GET /v1/branches/license-cost`) compared against the charter auction
- **Auctions**: the license Dutch auction and the charter auction, current and historical sales, and per-day history (`GET /v1/auctions/days`, whose rows carry `close_price`, `first_sale_ts`, `last_sale_ts`, and `sold_out_ts` for days that have closed). For `kind=license` days, rows also carry purchase aggregates derived from indexed sales (`transaction_count`, `distinct_charters`, `average_price_raw`, `total_paid_raw`, `purchase_size_distribution`); these are null on `kind=charter` days, and zero/null on a license day with no indexed sales. `GET /v1/auctions/license/bids` lists the license auction's standing bids with per-bid `fillable` and `blocking` reasons. `GET /v1/auctions/current` reports `phase` (`not_started` / `live` / `sold_out` / `paused` / `unknown`), `sold_out` (only true when the allotment sold was actually positive), `units_offered`/`units_sold`/`units_remaining`, `freshness_basis`, and `value_last_changed_at`/`_block` on each side
- **Epochs and policy**: policy multiplier, net-flow signal, and issuance per epoch, current and historical. `GET /v1/policy/current` falls back to a live estimate before the epoch settles (`regime_provisional`, `issuance_per_day_raw`, `time_to_close_source`, `epoch_start_ts`/`epoch_end_ts`), with every fallback field carrying its own `_source`. `GET /v1/policy/epochs` rows carry `pre_emissions: true` for epochs that predate issuance starting
- **Policy outlook and issuance runway**: a current-sign-holds projection of the multiplier and issuance rate (`GET /v1/policy/outlook`), and cumulative issuance against budget with a same-rate projected end date (`GET /v1/issuance/runway`)
- **Governance**: param-change history, queued-but-not-applied changes, one-way switch states, and guardian pause state (`GET /v1/governance/changes`)
- **Protocol money movements**: vault migrations, forwarded taxes, recycled fees, team claims, POL buybacks, fee collections and pairings, incentive sends, and every outflow from the reserve vault Safe (`GET /v1/flows/protocol`)
- **Exit pressure and exit economics**: the current snapshot, history, and the validated mainnet resolution-fee formula. `GET /v1/exit-pressure/history` rows carry a `formula` field, and the response reports `rows_without_formula` for the count of older rows predating that field; `GET /v1/exit/fee-curve` (a withdrawal-size ladder) and `GET /v1/exit/fee-forecast` (a no-further-exits projection, plus an `expiry_schedule` of exact window-day expiries and the resulting fee rate when the window data is complete) sit alongside it
- **Backing and buybacks**: vault ETH and reserve-asset holdings valued per circulating STANDARD (`GET /v1/backing/current`, `/history`), and contraction-vault buyback-tick readiness (`GET /v1/buybacks/readiness`, which also reports `regime_provisional`/`regime_note` before the first epoch settles)
- **Launch tax**: the current buy/sell tax bps and decay schedule (`GET /v1/tax/schedule`)
- **Participants**: holder concentration and cohort split (`GET /v1/holders/concentration`), one wallet's full position (`GET /v1/wallets/{address}`), and wallets past their reportable dormancy window ranked by bounty (`GET /v1/dormancy/bounties`)
- **Composite decisions**: `GET /v1/decisions/branch`, `/exit`, and `/charter` bundle the routes above into the one call a specific decision needs. `GET /v1/decisions/plan` is a different kind of composite: it seeds a keep/selective/aggressive Branch-expansion simulation from this API's own live routes and runs it forward over a horizon you choose, rather than answering "what's true right now". `GET /v1/decisions/stake` bundles the S-Bills quote, live state, optional wallet cap headroom, and a derived 1h/6h/24h rate projection; `GET /v1/decisions/hold` bundles policy, issuance, governance, tax, buyback, and dormancy-count reads; `GET /v1/decisions/sbill` takes one S-Bill and returns its exit-fee schedule or its redeem and roll figures; `GET /v1/decisions/bid` takes a max unit price and returns when the license price reaches it, who is ahead in the bid book, and how fills happen. `/exit` also prices staking the net STANDARD as an S-Bill, `/plan` prices staking each strategy's released STANDARD instead of selling it, and `/charter` carries the charter auction's price path for the rest of the day
- **Supply, vaults, and pool**: current and historical supply, the two vaults, and AMM pool state (price, tick, liquidity, reserves). `GET /v1/supply/current` includes `minted_as_of_block`, the block each supply field's own read reflects, plus `burned_forever_cum_raw`/`deposited_cum_raw`/`ledger_retired_cum_raw` for the three distinct ways `maxSupply` can move (see [Protocol overview and issuance](./protocol-and-issuance#supply-the-hard-cap-and-what-actually-shrinks-it)). `policy/current`, `pool`, `exit-pressure/current` (system-wide, gross=0), `supply/current`, `vaults/current`, and `backing/current` all take an optional `at_block` for a point-in-time read. `GET /v1/pool` and `GET /v1/pool/candles` add optional USD-denominated fields, converted via a Chainlink ETH/USD read on Ethereum mainnet
- **S-Bills**: fixed-term STANDARD staking at a rate fixed per term, with live quotes, maturity/settle/redeem/roll state, early-exit economics, and a decoded event feed (`GET /v1/sbills/current`, `/rate-history`, `/quote`, `/bills`, `/bills/{id}`, `/events`). See [S-Bills: fixed-term staking](./s-bills)
- **Flow and events**: hourly flow aggregates and a raw event feed
- **Constants**: chain-read parameters (base rate, epoch length, floors, and similar), each with its own `basis`
Full parameter and schema detail lives in the [OpenAPI spec](https://api.onesource.io/standard/openapi.yaml); that document is the canonical route list; don't rely on this page for it.
## MCP
The hosted [`@one-source/mcp`](https://www.npmjs.com/package/@one-source/mcp) server exposes this same data as the Standard tools (category `standard`). See the [tool reference](/getting-started/mcp/tool-reference) for the full tool list rather than the partial set named here.
---
## Which tool answers: mechanics questions
[Deciding with the data](./deciding-with-the-data) maps five financial *decisions* (exit, branch cost, policy, backing, participants) to routes and tools. This page is the companion for **mechanics questions**, "how does X work" rather than "what should I do." It points each one at the page that explains it and the route or tool that reports its current state.
| Question | Explained in | Live state from |
|---|---|---|
| How do charter withdrawals (exits) work? | [Exits and the resolution fee curve](./exits-and-resolution-fee) | `GET /v1/charters/{id}/exit-quote`, `1s_std_exit_quote` |
| What is the multiplier epoch policy? | [Protocol overview and issuance](./protocol-and-issuance#the-multiplier-bounds-and-the-step-rule) | `GET /v1/policy/current`, `1s_std_policy_current` |
| How does a branch actually earn STANDARD? | [Protocol overview and issuance](./protocol-and-issuance#issuance-charters-branches-and-the-base-rate) | `GET /v1/branches/summary`, `1s_std_branches_summary` |
| How does the license auction price work day to day? | [Charters, branches, and licenses](./charters-branches-licenses#license-what-you-burn-to-open-a-branch) | `GET /v1/auctions/current`, `1s_std_auctions_current` |
| What's the difference between buying a license and buying a charter? | [Charters, branches, and licenses](./charters-branches-licenses#charter-auction-the-other-way-in) | `GET /v1/branches/license-cost`, `1s_std_license_cost` |
| How is the resolution fee calculated? | [Exits and the resolution fee curve](./exits-and-resolution-fee#the-fee-pressure-based-and-quadratic) | `GET /v1/exit-pressure/current`, `1s_std_exit_pressure` |
| What counts toward exit pressure? | [Exits and the resolution fee curve](./exits-and-resolution-fee#exit-pressure-the-input-to-the-fee) | `GET /v1/exit-pressure/history`, `1s_std_exit_pressure` |
| What backs STANDARD, and how is POL different from vault ETH? | [Reserves, vaults, POL, and buybacks](./reserves-vaults-and-buybacks) | `GET /v1/backing/current`, `1s_std_backing` |
| When does a buyback actually fire? | [Reserves, vaults, POL, and buybacks](./reserves-vaults-and-buybacks#buybacks) | `GET /v1/buybacks/readiness`, `1s_std_buyback_readiness` |
| Where has protocol money gone, including anything that left the reserve vault? | [Reserves, vaults, POL, and buybacks](./reserves-vaults-and-buybacks#where-protocol-money-moves) | `GET /v1/flows/protocol`, `1s_std_protocol_flows` |
| Why does my swap get taxed, and how much? | [Trading tax hook](./trading-tax-hook) | `GET /v1/tax/schedule`, `1s_std_tax_schedule` |
| Is this contract address actually part of The Standard Reserve? | [Contracts and addresses](./contracts-and-addresses) | `GET /v1/addresses`, `1s_std_addresses` |
| What can governance change, and has anything one-way already happened? | [Governance switches and risks](./governance-and-risks) | `GET /v1/governance/changes`, `1s_std_governance_changes` |
| What are the actual risks / unresolved mechanics right now? | [Governance switches and risks](./governance-and-risks#whats-confirmed-vs-genuinely-unresolved) | n/a (documentation only; no single route summarizes this) |
| How dormant can a charter get before someone can claim it? | [Deciding with the data](./deciding-with-the-data#who-else-is-here) | `GET /v1/dormancy/bounties`, `1s_std_dormancy_bounties` |
| How do S-Bills work / what rate would I lock? | [S-Bills: fixed-term staking](./s-bills) | `GET /v1/sbills/quote`, `1s_std_sbills_quote` |
| What does exiting an S-Bill early cost? | [S-Bills: fixed-term staking](./s-bills#early-exit) | `GET /v1/sbills/bills/{id}`, `1s_std_sbills_bills` |
## Reading the answer, not just finding it
Every route above returns fields labeled with `basis`, `serving_state`, and (where relevant) `assumptions[]` or `_source`/`_basis` siblings. Read [How to read a TSR response](./reading-a-response) once before trusting a number pulled from any of them. A `basis: inferred_from_bytecode` field is real, deployed-contract behavior, but pinned without a verified source file; a `measured` field came straight off a chain call or log. Neither is "wrong" to use, but they're different strengths of claim, and conflating them is the single easiest way to overstate what this API actually knows.
---
## Protocol overview and issuance
The Standard Reserve ("STANDARD") is an on-chain reserve protocol on Robinhood Chain (chain id `4663`): a Central Bank contract, a supply of `$STANDARD` tokens, an ETH/STANDARD Uniswap v4 pool, and a set of charters and branches that mint new supply over time. The `Standard` token contract itself and the `TaxHook` have Sourcify-verified source (exact match, 2026-09-16); the Central Bank and the other 9 contracts don't, so this page states each mechanic's `basis` the way the API does: `measured` (read straight off a contract call or log), `constant` (a fixed parameter read once from chain), or `inferred_from_bytecode` (the rule was pinned by running the real deployed bytecode against controlled inputs on a fork, because no verified source exists to read it from). See [Contracts and addresses](./contracts-and-addresses#verification-status-read-this-honestly) for the full verification breakdown and [How to read a TSR response](./reading-a-response) for the full field vocabulary.
## Supply: the hard cap, and what actually shrinks it
`Standard`'s verified source fixes a hard cap of 1,000,000,000 (one billion) STANDARD. `maxSupply()`, the number `GET /v1/supply/current` reports as the ceiling issuance can ever reach, starts there and only ever decreases, never back up. Two different things can lower it, and only one of them is a "burn" in the sense of tokens leaving circulation for good:
- **Permanent burns** (the public `burn`/`burnFrom` functions, used for buybacks, POL fee disposal, and voluntary burns, plus the STANDARD leg the tax hook burns on a non-POL LP withdrawal) reduce `maxSupply` and can never be re-minted. `GET /v1/supply/current`'s `burned_forever_cum_raw` field tracks this cumulative total. This is what "burned" means in the sense that matters for supply: gone.
- **Ledger retirements** (the Central Bank's `retire`, used for license payments and the non-recycled half of resolution and revocation fees) also reduce `maxSupply` by the same amount, but no token ever moves: it's unminted ledger value written off, not a `Transfer` to the burn address. `ledger_retired_cum_raw` tracks this total separately.
Neither of those is the same as **depositing** STANDARD into the Central Bank (`convertFrom`, the deposit path underlying a charter's pending-credit balance). A deposit does send STANDARD to the burn address, and so it's counted in `burned_cum_raw` (the raw sum of every `Transfer` to `0x0`), but it does **not** reduce `maxSupply` and is **not** part of `burned_forever_cum_raw`: the value becomes ledger credit that a later `withdraw` re-mints back into circulation. `deposited_cum_raw` tracks this cumulative total separately so it isn't mistaken for a permanent burn. Concretely: `burned_cum_raw` ≈ `burned_forever_cum_raw` + `deposited_cum_raw`, but only the first term is gone for good.
The practical read: if you want "how much STANDARD has left circulation forever," use `burned_forever_cum_raw` plus `ledger_retired_cum_raw`, not `burned_cum_raw`. `circulating_raw` (minted minus everything burned, deposited or not) is unaffected by which bucket a burn falls into, since a deposit is a real supply move even though it's reversible; it's specifically the label "burned" that overstates permanence if applied to the raw `Transfer`-to-`0x0` total.
## Issuance: charters, branches, and the base rate
A charter is a soulbound (non-transferable) ERC-721 token, your membership in the reserve. A charter earns nothing by itself; it becomes productive once it opens one or more branches, which it does by burning STANDARD for a license (see [Charters, branches, and licenses](./charters-branches-licenses)). Every open branch accrues new STANDARD issuance continuously, at a per-branch rate derived from a chain-read `base_rate` divided across the total number of branches and scaled by the current multiplier. `GET /v1/constants` (`1s_std_addresses`'s sibling constants call) and `GET /v1/supply/current` report the live inputs; there are no whitepaper numbers baked into this page, since whitepaper constants are redacted by design and the deployed contract is the only source of truth.
## Epochs: the accounting window for policy
An epoch is the Central Bank's fixed-length settlement window. At the end of each epoch, the bank reads the **net ETH flow** into or out of the ETH/STANDARD pool over that period. That's pool-side ETH: what the pool's own reserve actually gained or lost, not what any individual trader paid or received (buy-side taxes are excluded from inflow; sell-side taxes are included in outflow, `basis: inferred_from_bytecode`, confirmed by fork replay of six real transactions). That flow becomes the epoch's own signal. The step rule below actually reads `signal_n = F_n + F_{n-1}` (this epoch's flow plus the prior epoch's), not a single epoch's flow in isolation.
## The multiplier: bounds and the step rule
The multiplier scales issuance up or down between epochs. It only moves at a settlement boundary, and only in the direction the two-epoch signal points (`basis: inferred_from_bytecode`, pinned by running the deployed Central Bank bytecode through 15 scripted scenarios on a mainnet fork, see `docs/tsr/BYTECODE_SEMANTICS_2026-09-15.md` in `sre-services` for the transcripts):
- **Positive signal** (`> 0`): the consecutive-positive streak increments, and the multiplier raises starting from the *second* consecutive positive settlement, not the first.
- **Zero signal**: a **hold**. The streak resets to zero, but the multiplier does not move. Zero is neither a raise nor a cut.
- **Negative signal** (`< 0`): the streak resets to zero, and the multiplier cuts, except the very first settlement the bank ever performs never cuts, regardless of how negative the signal is. Every settlement after the first cuts normally.
The streak keeps counting even after the multiplier hits its ceiling (it doesn't reset on a raise), and cuts can repeat every epoch the signal stays negative, all the way to the floor. Constants (`M_STEP_UP`, `M_STEP_DOWN`, `M_MIN`, `M_MAX`, `M_START`) are chain-read and reported by `GET /v1/constants`; this page doesn't restate their values since they're exactly the kind of number that would drift out of sync with a live read.
**What's unresolved:** no epoch had settled on mainnet as of the fork analysis behind this page, so the step rule (including the first-settlement no-cut exemption and the exact reason for it) is pinned by executing the real bytecode with chosen inputs, not by observing a real `EpochSettled` event. It should be re-checked against the first live settlement. `GET /v1/policy/current` and `GET /v1/policy/outlook` both label every pre-settlement field with its own `_source` and `_basis` so a caller can tell a live constants-derived estimate from a settled, event-sourced one.
## Where to look
| Question | Route | MCP tool |
|---|---|---|
| What's the current epoch's signal, regime, and multiplier? | `GET /v1/policy/current` | `1s_std_policy_current` |
| What would the multiplier be if this epoch's sign holds? | `GET /v1/policy/outlook` | `1s_std_policy_outlook` |
| How much of the issuance budget is left, and when does it run out at this rate? | `GET /v1/issuance/runway` | `1s_std_issuance_runway` |
| What are the chain-read constants behind all of this? | `GET /v1/constants` | n/a |
| What's the current supply, and how much of the cap is permanently gone vs. just deposited? | `GET /v1/supply/current`, `/history` | `1s_std_supply` |
See [Deciding with the data](./deciding-with-the-data#what-will-policy-do-next) for the fuller decision-oriented walkthrough of "what will policy do next."
---
## How to read a TSR response
Every route under `/standard/v1/...` returns a flat JSON object (not the `data`/`error`/`meta` envelope used elsewhere in the REST API; see [Response envelope](/getting-started/direct-rest/response-envelope)). A handful of fields repeat across almost every route, and they're what tells you whether a number is safe to act on. Read this once and the rest of the [OpenAPI spec](https://api.onesource.io/standard/openapi.yaml) gets much faster to skim.
## Freshness: `serving_state`, `lag_blocks`, `as_of_block`
- **`as_of_block`**: the Robinhood Chain block height the response reflects. Two calls with different `as_of_block` values aren't necessarily comparable, especially across the composite `decisions/*` routes, where each part can land on a slightly different block (see `parts_as_of_blocks` below).
- **`serving_state`**: `fresh` (the indexer is caught up to chain head), `lagging` (behind but still serving its last good read, usually with a `Warning` response header), or `withheld` (too stale to serve at all, so you get `HTTP 503` instead of a wrong number).
- **`lag_blocks`**: how many blocks behind head the served data is. Zero or small on `fresh`, larger on `lagging`.
Check `serving_state` before treating a number as current, the same way you'd check a timestamp on any cached value.
## Composite routes: one response, several as-of-blocks
The `decisions/branch`, `decisions/exit`, and `decisions/charter` routes each bundle several underlying routes into one call. Every part is reported under `parts.` with its own `status` (`ok`, `not_ready`, or `error`), so one part failing never fails the whole response: you get partial data plus a clear reason for the gap, not a single opaque 500.
The top-level `as_of_block` on a composite is the *minimum* `as_of_block` across every part that succeeded: the one block height every part in the response is simultaneously valid through. Each part's own `as_of_block` is also broken out under `parts_as_of_blocks`, so you can see how wide the actual spread is if you need finer granularity than the conservative top-level figure.
## How sure the API is: `basis`
`basis` says where a value came from, in decreasing order of directness:
- **`measured`**: read directly off a contract call or event log. The strongest claim the API makes.
- **`derived`**: computed from one or more measured values (the exit-pressure fee curve, a backing ratio, a buyback capacity, the auction `day_clock` described below).
- **`constant`**: a fixed parameter read once from chain and cached (a floor, a cap, a rate).
- **`inferred_from_bytecode`**: the underlying rule isn't in a verified source file for the contract. 2 of the 12 Standard Reserve contracts (`standard` and `taxHook`) have Sourcify-verified source (exact match, 2026-09-16); the other 10 don't, so a rule that lives in one of those 10 was pinned by running the real bytecode against controlled inputs on a fork, rather than read out of Solidity. It's the deployed code actually executing, but only over the cases exercised. See `docs/tsr/BYTECODE_SEMANTICS_2026-09-15.md` in the `sre-services` repo for the method and confidence level behind each one, and [Contracts and addresses](./contracts-and-addresses#verification-status-read-this-honestly) for which contracts are which. Any field built this way carries a `basis: inferred_from_bytecode` (or `_basis` / `assumptions[]` entry prefixed `basis inferred_from_bytecode:`) so it's never confused for a `measured` read of the same contract.
Three rules the API depends on were pinned this way:
- **Launch-tax decay** (`GET /v1/tax/schedule`): the *excess over the floor* rate halves every 4 minutes and the result is truncated, not rounded; it isn't a whole-rate decay.
- **Policy multiplier step rule** (`GET /v1/policy/outlook`): a zero net-flow signal holds the multiplier unchanged (it's neither a raise nor a cut), and no cut can fire at the very first epoch settlement regardless of signal.
- **License auction next-day opening price** (`GET /v1/auctions/current`, `/v1/auctions/days`): `max(start_multiplier × yesterday's last sale, today's floor price)` if a sale happened the prior day, otherwise `start_multiplier × today's floor price`.
All three are pending confirmation against the contracts' own on-chain behavior at the first real epoch settlement (~2026-09-18 01:00Z); until then they're the best available reconstruction, not a verified read.
## `formula_version` gates
Several derived numbers (the exit-fee forecast and the formula-side fee fields most importantly) are gated on a `formula_version` or `exit_formula_version` value in the response. The fee an exit would pay right now is not: it is the contract's own `previewResolutionFeeWad` reading (`basis: measured`) and is served whatever the formula's status. This exists because more than one resolution-fee formula is validated against the contract's own `previewResolutionFeeWad`, and the field tells you which one produced the number you're looking at, so a cached client-side assumption about the shape of the math doesn't silently go stale. Don't hardcode the formula version and compare it as a boolean "is this new enough"; treat it as an opaque label and re-read the field it accompanies each time.
## Null means "couldn't read it," not zero
An unreadable or not-yet-settled value is JSON `null`. It is never coerced to `0` to fill a gap. `GET /v1/policy/current` is the clearest example: before the Central Bank contract settles an epoch, `regime` and `issuance_raw` are `null` (not `"expansion"` or `0`) and the response instead offers `regime_provisional` and `issuance_per_day_raw` as clearly-labeled live estimates, each with its own `_source` field naming where it came from (`"constants"` for a live read, `"epochs"` once the epoch has settled). If you see a `0` in one of these fields, the contract measured a real zero, not a missing read.
## `day_clock`: when the auction day boundary comes from
`GET /v1/auctions/current`, `GET /v1/branches/auction/live`, and `GET /v1/branches/doi` each carry a `day_clock` object (`current_day`, `day_started_at`, `next_day_starts_at`, `seconds_to_next_day`, `auction_day_seconds`), and `GET /v1/decisions/branch` lifts `next_day_starts_at` and `seconds_to_next_day` into its `summary`. Every field in it is `basis: "derived"`: it's computed from the auction's start time and day length rather than read off the contract's own day-roll event, which only fires on the first sale of a new day and so can't be used to predict a boundary that hasn't happened yet. If the auction in question hasn't started, its anchor was never set on-chain, so the whole object comes back `null` and a sibling `day_clock_reason` field names why, the same null-plus-reason pattern used elsewhere in this API.
## Projections are labeled, never presented as predictions
Anything forward-looking carries a `projection` field naming exactly what it holds constant, plus an `assumptions[]` array spelling out the assumption in full sentences:
- `current_sign_holds` (`GET /v1/policy/outlook`): what the multiplier and issuance rate would be if this epoch's net-flow sign doesn't change before it closes.
- `current_stream_rate_holds` (`GET /v1/issuance/runway`): when the issuance budget runs out if the current per-second rate doesn't change.
- `no_further_exits` (`GET /v1/exit/fee-forecast`): how the system-wide exit fee would drift down if nobody exits between now and then.
None of these are forecasts of what will happen. They're "if nothing changes" projections computed from the same measured and derived fields as everything else, so the label and the held-constant assumption are exactly as important as the number itself. Use "current-pace estimate" or "days of issuance" when you describe them; never ROI, APY, payback period, or "opportunity."
## `_raw` fields are 18-decimal integers, as strings
Any field ending in `_raw` is a `uint256`-range integer serialized as a JSON string (so it survives round-tripping through parsers that treat all JSON numbers as 64-bit floats), scaled by 1e18 (a "WAD", the standard 18-decimal fixed-point convention this protocol and most Solidity DeFi code use). Divide by `1e18` to get the token or ETH amount as a decimal. A sibling field without the `_raw` suffix, when present, is the same value already divided down for convenience and should be treated as approximate for display, not for arithmetic.
## Estimates carry `assumptions[]`
Anything computed by simulating a swap through the STANDARD/ETH pool (`realizable_eth_raw` on an exit quote, the ETH cost of a Branch license, price impact) is marked `estimate: true` and comes with an `assumptions[]` array spelling out the approximation: a single-range constant-liquidity treatment of Uniswap v4's concentrated liquidity (it doesn't model the swap crossing into an adjacent initialized tick), and which side of the swap the buy or sell tax was applied to. These are approximations of what a live quote from the contracts would return, not a substitute for one. Don't build an execution path that skips the real transaction simulation because this number looked close enough.
---
## Reserves, vaults, POL, and buybacks
STANDARD is backed by ETH held in two vaults, plus any reserve assets the expansion vault has acquired, plus the reserve's own share of the ETH/STANDARD pool's liquidity.
## The two vaults
- **Expansion vault**: accumulates ETH during expansion (net-inflow) epochs, intended to deploy it into hard reserve assets (the whitepaper describes "tokenized gold, comparable"). Since 2026-10-02 the address registry points the expansion vault at a multisig Safe (2 of 4 signers) instead of a contract, and that Safe has swapped some of its ETH for USDG. OneSource follows whatever address the registry currently lists. `GET /v1/backing/current` reports each vault's `custody` (`type`, `owner_count`, `threshold`) and the vault's USDG as `expansion_vault_usdg`. That figure stays in USDG's own units and is not converted to ETH, because no USDG price source is available. `GET /v1/vaults/current` reports `expansion_eth_raw` as `basis: measured`; reserve-asset fields (`reserve_assets.gold_raw`, etc.) stay `null` until that path is actually exercised on-chain, not coerced to zero. See [Null means "couldn't read it," not zero](./reading-a-response#null-means-couldnt-read-it-not-zero).
- **Contraction vault**: funds automated buybacks that burn STANDARD from the market during contraction (net-outflow) epochs. `contraction_eth_raw` is likewise `basis: measured`.
Both vaults' balances, plus `pol_eth_raw`/`pol_std_raw` when observed, feed `GET /v1/backing/current` and `/history`. `incentives_vault_std_raw` (below) does not: it's STANDARD held for a different purpose, not ETH-equivalent backing, so it's reported alongside the backing ratios rather than folded into them.
## Hook-held taxes are not vault backing
Collected trading taxes (see [Trading tax hook](./trading-tax-hook#taxes-sit-in-the-hook-until-forwarded)) sit in the `TaxHook` contract's own ETH balance until the permissionless `forwardTaxes()` pushes them to the FeeSplitter, and from there they route onward by the protocol's normal fee logic, not into either vault directly. `GET /v1/buybacks/readiness` reports that pending balance as `hook_eth_balance_raw`, plus `fee_splitter_eth_balance_raw` for ETH that reached the FeeSplitter but hasn't been routed further. Neither figure is counted in `backing/current`'s vault totals, with or without POL: it's protocol revenue in transit, not reserve backing a STANDARD holder could redeem against. Don't add either balance into a backing-per-token calculation built from `/v1/backing/current` alone.
## Protocol-owned liquidity (POL)
POL is liquidity the protocol itself owns inside the ETH/STANDARD Uniswap v4 pool, as opposed to liquidity supplied by outside market makers, managed by a dedicated POL Manager contract. It's seeded at genesis and can grow from a share of ongoing fees. Because POL sits inside the pool rather than in a vault the protocol could otherwise redeploy, `GET /v1/backing/current` reports two figures rather than one: **excluding POL** (only the two vaults' direct holdings) and **including POL** (adding the reserve's pool share). Neither is "the" backing number; they answer different questions. One is what the protocol could redeploy today; the other is what the reserve's full on-chain footprint adds up to.
The POL Manager also carries a **TWAP deviation gate**: a bound (in ticks) on how far the pool's price can have moved from its time-weighted average before certain POL Manager operations (deploying ETH into the pool, for instance) are allowed to proceed. This exists to stop POL operations from executing against a price that's been manipulated or is moving unusually fast in a single block. The exact selectors and events behind this (`setTwapDeviationTicks`, `TwapDeviationSet`, a public/private deviation-ticks split) are confirmed by keccak-matching the observed event signatures against the deployed bytecode (`basis: inferred_from_bytecode`, hash-confirmed, a *confirmed signature*, not a confirmed account of every field's semantics). The time-weighted reference itself lives on the verified `TaxHook` contract, not the POL Manager: the hook tracks a running tick-cumulative and rotates a pair of window checkpoints on every swap, giving a time-weighted average tick over roughly one to two 30-minute windows, which is what a buyback or POL crank's TWAP-deviation check actually compares the current tick against.
### Protocol v1.1: bid-side-only buybacks and the incentives vault
Protocol v1.1 (live 2026-09-21) changed where the POL share of protocol fees goes. Per the whitepaper, that share used to top up POL Manager's own paired ETH/STANDARD position in the pool; it no longer does. Instead a separate `polBuyback` contract uses it to buy STANDARD off the market (bid-side only, no new pairing) and forwards those tokens to a new `incentivesVault` contract, held there for future distribution rather than burned. The whitepaper is explicit that this doesn't touch what POL Manager already holds: "the genesis liquidity positions remain in place and no longer receive new liquidity from the POL share... their original manager continues collecting LP fees." So `pol_eth_raw`/`pol_std_raw`/`pol_total_controlled_*_raw` keep meaning what they meant before v1.1; only the *growth* path changed.
`GET /v1/vaults/current` and `/history` report the incentives vault's balance as `incentives_vault_std_raw`, with an `incentives_vault_note` explaining why it's kept separate from the backing figures above. Each `polBuyback` purchase is decoded and served as a `pol_buyback` row in `GET /v1/flows/protocol` (ETH spent and STANDARD received), and each LP fee collection by the POL Manager as a `pol_fees_collected` row (see [Where protocol money moves](#where-protocol-money-moves)).
The incentives vault is also where the S-Bills premium budget's 1.5M STANDARD seed came from, moved via the owner Safe as a plain transfer rather than a tracked `polBuyback` distribution. See [S-Bills: fixed-term staking](./s-bills#funding) for what that seed funds and how OneSource tells it apart from an ordinary deposit.
## Where protocol money moves
`GET /v1/flows/protocol` (`1s_std_protocol_flows`, $0.02) lists the protocol's own money movements, newest first, one row per movement with a `kind`:
- `holdings_migrated`: a vault or the POL Manager moved its whole balance to a successor address.
- `taxes_forwarded`: the tax hook paid settled taxes to the FeeSplitter (ETH).
- `fees_recycled`: resolution and revocation fees the Central Bank returned to issuance for one epoch (STANDARD).
- `team_claimed`: a team claim paid out of the FeeSplitter (ETH).
- `pol_buyback` and `pol_fees_collected`: the buybacks and LP fee collections described above.
- `pol_paired`: a POL Manager `pairPol` call, which puts ETH into the STANDARD/ETH pool as protocol-owned liquidity by swapping part of it for STANDARD and adding both as a full-range position. `eth_amount` is the ETH committed in that call.
- `incentive_sent`: the incentives vault sent a token. The 1.5M STANDARD S-Bills seed is one of these rows.
- `vault_outflow`: value that left the reserve vault Safe, in ETH or an ERC-20 token such as USDG, with a `method` field naming how it moved (a swap call, a plain ETH send, or a token `transfer`).
Sends between Safes that hold the reserve at that block are internal moves and get no row. A Safe can batch several sends into one transaction (`multiSend`); each send that leaves custody gets its own row, told apart by `sub_index`. Amounts are integer strings in the token's smallest unit, each with a `*_dec` field already divided by the token's decimals. Filter with `kind` and page with `cursor`.
## The pool's LP fee
Separately from the tax hook's own buy/sell tax, the ETH/STANDARD pool charges the ordinary Uniswap LP fee that liquidity providers earn on every swap. `GET /v1/pool` reports its *rate* as `lp_fee_bps`, read live from Uniswap and cached in `tsr.constants` as `pool_lp_fee_ppm`; it's a static 100 bps (1%), not a value that moves with the launch-tax decay. This rate is folded into the price-impact estimates used elsewhere in this API, for example the estimated ETH cost of a license on `GET /v1/branches/license-cost` and the strategy simulation on `GET /v1/decisions/plan`, both of which take a fee at this rate off the post-tax amount as one of the haircuts between a nominal price and what actually settles, as a modeling simplification rather than a confirmed statement of which side of the swap bears it on-chain. **Which side actually pays the LP fee is a separate, still-unresolved question**; see [Governance switches and risks](./governance-and-risks#whats-confirmed-vs-genuinely-unresolved) for why knowing the rate doesn't settle that.
## Buybacks
A buyback tick burns STANDARD purchased with contraction-vault ETH. `GET /v1/buybacks/readiness` reports whether a tick could fire right now, covering capacity, any cooldown since the last tick, the TWAP-deviation check above, and a log of recent executed ticks. Before the first epoch has settled, this route (like `/v1/policy/current`) falls back to a live, clearly-labeled `regime_provisional`/`regime_note` estimate rather than presenting an unsettled regime as final.
Whether a buyback tick (or a bid-side POL buyback) can be triggered by anyone, or only by a designated crank operator, is a separate question that `permissionless` answers directly: it reads the `AddressRegistry`'s `executionPermissionless` one-way switch, with `permissionless_source` naming the exact call (`registry.executionPermissionless`), since the registry itself exposes no such flag from the outside without reading it directly. See [Governance switches and risks](./governance-and-risks#the-address-registry-whos-allowed-to-point-where) for what the switch governs and its current state, and for the `executor`/`guardian` addresses `GET /v1/buybacks/readiness` also reports.
**What's not yet confirmed:** the whitepaper's own regime-based ETH-split (it describes a 70/15/15 split across uses by regime) is unverified against the deployed Central Bank contract. Two of the fifteen Standard Reserve contracts (`Standard` and `TaxHook`) have Sourcify-verified source (exact match, 2026-09-16); the Central Bank is not among them, so its internal fee-routing logic isn't something this API can read out of Solidity and confirm. Treat any specific split percentage you see elsewhere as whitepaper framing, not a confirmed mainnet behavior, until a verified source or a bytecode-fork analysis pins it the way the tax decay curve and multiplier step rule were pinned.
## USD pricing on pool state and candles
`GET /v1/pool` and `GET /v1/pool/candles` (with `quote=usd`) both add USD-denominated fields (`price_usd_per_std`, and `open_usd`/`high_usd`/`low_usd`/`close_usd` on candles) alongside the native ETH-denominated ones. The conversion comes from Chainlink's ETH/USD price aggregator on Ethereum mainnet, read separately from the Robinhood Chain pool state and stamped with its own `usd_as_of_block` (the Ethereum mainnet block the oracle read came from, distinct from the response's own Robinhood Chain `as_of_block`). If the feed's answer is non-positive or more than an hour stale, every `*_usd` field comes back `null` with a `usd_unavailable_reason`, and the ETH-denominated fields are unaffected either way.
## Where to look
| Question | Route | MCP tool |
|---|---|---|
| Is the token backed, and by how much per STANDARD? | `GET /v1/backing/current`, `/history` | `1s_std_backing` |
| Could a buyback fire right now? | `GET /v1/buybacks/readiness` | `1s_std_buyback_readiness` |
| What's in each vault right now, and historically? | `GET /v1/vaults/current`, `/history` | `1s_std_vaults` |
| Where has protocol money moved, including value that left the reserve vault? | `GET /v1/flows/protocol` | `1s_std_protocol_flows` |
| What's the pool's current price, tick, and liquidity? | `GET /v1/pool` | `1s_std_pool` |
See [Deciding with the data](./deciding-with-the-data#is-the-token-backed-and-is-the-buyback-mechanism-working) for the fuller backing/buyback decision walkthrough.
---
## S-Bills: fixed-term staking
S-Bills (fixed-term STANDARD staking) let a holder lock STANDARD into the SBills contract for one term at a rate fixed when they deposit. OneSource reads the deployed contract (`0x163b36fd07622486cb889feef97bfbe2c1d7c05b` on Robinhood Chain, deployed block `77,727,505`, 2026-10-01) and serves the result as the six routes and five MCP tools on this page.
## What an S-Bill is
A deposit opens one bill: it locks principal for one term (currently 15 days; see [The rate](#the-rate-and-why-its-per-term-not-annual) for why that number can change) at the rate in force at deposit time, and escrows the bill's premium from the contract's own budget up front. The premium then vests evenly over the term (see [Early exit](#early-exit)). Three caps gate deposits: a 30M STANDARD total cap across all open bills, a 1M STANDARD per-wallet cap, and a 1,000 STANDARD minimum deposit. `GET /v1/sbills/quote?amount=` runs a live `quote()` call against the contract and reports whether a hypothetical deposit of that size would clear each cap, alongside the rate it would lock and the rate left for the next depositor. Add an optional `wallet=` address and `checks` also reports that wallet's open principal, its headroom under the per-wallet cap, and `exceeds_wallet_cap`. `GET /v1/decisions/stake` wraps this quote with the live S-Bills state and a derived 1h/6h/24h rate projection.
## The rate, and why it's per term, not annual
The contract exposes `rate()` and `currentRate()` as a fraction per term, not per year, and this API reports that fraction as a WAD integer, not a decimal: a `rate_per_term_wad` of `14700000000000000` means 1.47% over one term, not 1.47% annualized. The rate drifts upward between deposits (`drifting: true`) toward a ceiling, and resets downward on a fresh deposit; a configurable `impactScale` determines how much a single deposit moves it. Floor and ceiling (currently 1% and 7% per term) bound where it can land.
Every rate field in this API carries `rate_basis: "per_term"` and the `term_seconds` it was computed against, plus a derived `apr_simple_wad`: the rate times 365 days divided by the term in seconds (`rate_wad * 31536000 / term_seconds`), a simple (non-compounding) annualization of the per-term rate at the live term length. This is deliberately not an APY: the term itself is admin-adjustable (`setTerm`, bounded `MIN_TERM`/`MAX_TERM`), and there is no on-chain auto-roll, so nothing compounds automatically. A bill's premium is paid once at maturity, or partly forfeited on early exit (see [Early exit](#early-exit) for exactly what's forfeited). `GET /v1/sbills/rate-history?hours=` reports the drift curve and the exact rate any given deposit locked, but it's a Postgres-only read: a union of `RateChanged` events and 5-minute drift snapshots, not a live chain call. For the live rate right now, use `GET /v1/sbills/current`'s `rate_live` block, `GET /v1/sbills/quote`, or a specific bill's exit/redeem quote.
## Maturity, settle, redeem, and roll
A bill matures at `term_start + term_seconds`. After maturity, three things can happen, and only one of them is open to anyone:
- **`settle`** is permissionless: anyone can call it on a matured bill to release its principal out of the contract's locked-principal total. Settling does not pay the bill out; it only frees the capacity that principal was occupying.
- **`redeem`** is owner-only: the bill's owner claims principal, premium, and any bonus, and the bill closes.
- **`roll`** is owner-only: the owner re-locks the bill for a new term at the then-current rate, compounding the premium into the new principal.
`GET /v1/decisions/sbill?bill_id=` returns the redeem quote and a derived roll block for a matured bill. Rolling locks the redeem payout as the new principal at a fresh quote taken at roll time, and only the bill's owner can do it. There is no on-chain auto-roll. A bill that matures and is never redeemed or rolled simply sits matured; nothing the contract does on its own moves it forward, a point the team's own stake page agrees with ("will not renew by itself"). `GET /v1/sbills/bills/{id}` reports a `status_effective` of `matured` for exactly this state, derived from comparing `maturity_ts` against the current time, and distinct from the terminal, event-sourced `status` (`active`, `exited`, `redeemed`).
## Early exit
A bill owner can exit before maturity for a fee, taken from principal. The fee is dynamic: it moves between a floor and a cap (currently 2% and 15% of principal) by this formula, read off the team's own stake page and checked against several live `quoteExit` calls: `fee_bps = floor_bps + (cap_bps - floor_bps) * pressure^2`, truncated to an integer and capped at `cap_bps`, where `pressure` is the exiting bill's principal plus everything exited over the trailing window (currently 7 days), divided by whichever is larger: that same sum plus the contract's locked principal, or the pressure floor. So far this has only been verified at low pressure (under 0.07); treat it as a checked estimate, not a guaranteed match at every pressure level.
Premium vests linearly over the term (`vested = premium * elapsed / term`). Exiting early splits three ways: half the fee is burned, the other half plus the premium vested so far goes to remaining stakers through `bonusPerPrincipal` (a per-principal-unit bonus rate, emitted as `BonusDistributed`), and the unvested premium returns to the contract's budget rather than being paid to anyone. The observed `Exited` event reports this as `vestedForfeited` (the vested premium the leaver gives up) and `bonusForfeited` (any bonus the bill had accrued but hadn't claimed).
`GET /v1/sbills/quote?amount=` reports an `exit_fee_estimate` for a hypothetical bill of that size exiting the instant it opens (fee, payout, the pressure inputs, `basis: "derived"`), and `GET /v1/sbills/current` reports the same estimate at four fixed amounts (1,000 / 10,000 / 100,000 / 1,000,000 STANDARD) alongside `fee_formula` in words, so you can see roughly where a deposit would land before making one. Neither replaces a real quote: `GET /v1/sbills/bills/{id}` runs a live `quoteExit()` for an exact fee, payout, and forfeited-bonus figure for a specific existing bill, using that bill's own principal and the exit volume at the moment you ask. Either way, an early exit pays back principal minus the fee; the bill's premium and any stayer bonus are not paid, however long the bill was held. Time held only changes where the forfeited premium goes, not what the leaver receives.
## Funding
The contract's premium budget was seeded with 1.5M STANDARD transferred from the protocol's incentives vault via the owner Safe (see [Reserves, vaults, POL, and buybacks](./reserves-vaults-and-buybacks#protocol-v11-bid-side-only-buybacks-and-the-incentives-vault) for what that vault is). That seed arrived as a plain transfer with no accompanying `Funded` event, which this API's loader distinguishes from a deposit by checking whether a `Deposited` event fired in the same transaction. No `Funded` event has been observed on mainnet yet. Any buyback-funded top-ups the team's site describes are likewise unobserved on-chain; this documentation reports what has actually been measured, not what is claimed.
## How this changes supply reading
STANDARD locked in S-Bills is still counted in `circulating_raw`: this API does not redefine what "circulating" means, because every existing backing ratio and historical comparison reads `circulating_raw` as total supply. Instead, `GET /v1/supply/current` adds a `sbills` block (locked principal, total principal, premium budget, escrow, and the contract's own STANDARD balance) alongside a derived `circulating_excluding_sbills_raw`, so you can see circulating supply either way without anything moving underneath the figures that already existed. The same response adds `burned_sbill_exit_cum_raw`: the STANDARD burned as the burned half of early-exit fees, a measured subset of `burned_cum_raw`, the same pattern as the buyback-burn breakdown already on that route.
## Not in the address registry
Unlike the other 15 Standard Reserve contracts (see [Contracts and addresses](./contracts-and-addresses)), the SBills contract is not registered in the `AddressRegistry`. Its address is hard-coded in OneSource's indexer rather than resolved through the registry, verified the same way every address on this page is verified: cross-checked as present on-chain with non-zero bytecode at its stated deployment block. The deployed bytecode does embed a registry address and a STANDARD token address of its own, so OneSource's indexer reads those back from the contract on its hourly full pass and compares them against its own configured addresses, reporting the result as `registry_matches_config` / `token_matches_config` (booleans) on `GET /v1/sbills/current`. Both are `null` until that first hourly check has run. A `false` would mean the contract was redeployed or that OneSource is pointed at an impostor; treat that field, not the address's mere presence in this documentation, as the live check.
## Where to look
| Question | Route | MCP tool |
|---|---|---|
| What are the current S-Bills staking terms: rate, APR, caps, pause state? | `GET /v1/sbills/current` | `1s_std_sbills_current` |
| How has the S-Bill rate moved, and over what term lengths? | `GET /v1/sbills/rate-history` | `1s_std_sbills_rate_history` |
| What would I lock in if I staked N STANDARD right now? | `GET /v1/sbills/quote` | `1s_std_sbills_quote` |
| The quote, live state, wallet cap headroom, and rate projection in one call | `GET /v1/decisions/stake` | `1s_std_decision_stake` |
| What are the exit and roll figures for one bill, in one call? | `GET /v1/decisions/sbill` | `1s_std_decision_sbill` |
| Show me one bill, or every bill for a wallet or status | `GET /v1/sbills/bills`, `/v1/sbills/bills/{id}` | `1s_std_sbills_bills` |
| What would exiting or redeeming this bill pay out right now? | `GET /v1/sbills/bills/{id}` | `1s_std_sbills_bills` |
| What has actually happened on the S-Bills contract? | `GET /v1/sbills/events` | `1s_std_sbills_events` |
See [Which tool answers: mechanics questions](./mechanics-question-map) for how this fits alongside the rest of the protocol, and [How to read a TSR response](./reading-a-response) for what `basis`, `serving_state`, and `_raw` fields mean on every route above.
---
## Trading tax hook
The ETH/STANDARD pool is a **hooked** Uniswap v4 pool: a dedicated tax-hook contract runs on every swap and applies a buy-side and sell-side tax, on top of whatever the standard pool fee is.
## Sourcify-verified source
The `TaxHook` contract has Sourcify-verified source (exact match, 2026-09-16), alongside the `Standard` token contract. Everything below is readable directly in Solidity, the strongest basis this documentation reports: the mechanics described here match the verified source exactly, and this page calls out the few places the source shows detail beyond what measurement alone would reveal.
## Launch decay to floors
At launch, the tax starts high on both sides and decays toward a permanent floor rate. The decay curve was measured directly against the live contract at 12 historical blocks inside the launch hour (`basis: measured`, `decay_model_basis: measured_on_chain`):
```
t = block.timestamp − tax_decay_start
bps(t) = floor( decay_floor_bps + (decay_start_bps − decay_floor_bps) · 0.5^(t / tax_half_life) )
```
The key detail: it's the **excess over the floor** that halves every half-life (a chain-read constant), not the whole rate. A "whole-rate" reading (`max(floor, start · 0.5^(t/half_life))`) was directly ruled out by the measurements; at the same timestamps, the whole-rate curve and the excess-over-floor curve diverge by hundreds of basis points, and the live contract tracked the excess-over-floor curve exactly, truncated (not rounded) at each step. Once elapsed time reaches the decay duration (also chain-read), the hook returns the floor rate outright and the launch schedule flips to inactive.
`GET /v1/tax/schedule` reports the currently measured buy/sell bps, the decay configuration, and (only while the schedule is still active and not overridden) a labeled projection at a few forward horizons, computed from the same chain-read constants (not illustrative whitepaper numbers). Each response also runs `decay_model_check`, comparing the measured current rate against the excess-over-floor curve: `consistent` is the expected case; `inconsistent` would mean the measured rate instead matches the ruled-out whole-rate curve, which would only happen if the hook were redeployed with different logic. The route's `freshness_basis` tells you whether `as_of_block`/`serving_state`/`lag_blocks` come from the snapshot step's own per-pass marker (the normal, healthy case) or from a fallback read of the newest constant timestamp, which matters once the rate stops changing (see below) and the normal marker alone would otherwise look stale.
The launch schedule itself ran on a fixed clock: a half-life (a chain-read constant, currently 240 seconds) and a total decay duration (currently 3600 seconds) anchored at pool initialization, with no admin input while it runs. That window closed around 2026-09-15 ~02:00Z, so `launch_schedule_active` reads `false` and the live buy/sell rates sitting at their floors (roughly 2% and 3%) are what actually gets charged, unless the owner has called `setTaxes` to override them (see below).
## Where the tax is taken
- **Buy (ETH → STANDARD)**: taken in the hook's pre-swap step, as `bps / 10000` of the ETH **input**. The pool itself only ever sees the post-tax amount.
- **Sell (STANDARD → ETH)**: taken in the hook's post-swap step, as `bps / 10000` of the ETH **output**. The seller receives the post-tax amount; the pool-side swap itself moves the full pre-tax amount.
This was confirmed against real transaction receipts by matching the tax-collection event's log index and amount against the adjacent pool `Swap` event, on both buy and sell sides, and reproduced by fork replay. It matters for any price-impact estimate: applying the buy tax to ETH going in versus the sell tax to ETH coming out, at the correct point in the swap, is what makes an exit quote's `realizable_eth` or a license's ETH-converted cost line up with what actually settles.
**The "buy before / sell after" rule above holds for exact-input swaps only.** The verified source shows the hook actually keys off which side of the swap ETH is the *specified* (caller-stated) leg, not off buy-vs-sell: an exact-input buy and an exact-output sell both state the ETH leg directly, so both are taxed pre-swap on the requested amount; an exact-output buy and an exact-input sell both leave ETH as the leg the pool determines, so both are taxed post-swap on the realized amount. For an exact-input swap (the common case for a router-driven trade) that collapses to exactly the buy-before/sell-after rule above. For an exact-output swap it flips: an exact-output **buy** is taxed post-swap (so its `TaxCollected` event can log after the pool's own `Swap` event), and an exact-output **sell** is taxed pre-swap. A price-impact tool that infers "buy" or "sell" purely from event log order, rather than from the swap's actual direction, will mis-tag exact-output trades.
An exact-output swap also can't tax the requested amount directly, since it only specifies the net leg it wants; the hook grosses the fee up (`fee = net · rate / (1 − rate)`) so the *effective* rate on the full gross ETH leg still matches what an exact-input swap would have paid at the same rate. Nothing in this API distinguishes exact-input from exact-output trades in its price-impact estimates today, since `realizable_eth` and the license-cost estimate both model a simple constant-liquidity swap; treat this as a property of the underlying mechanism rather than something the API's `assumptions[]` currently calls out swap-shape by swap-shape.
## Net flow excludes the protocol's own swaps
The hook also tracks `epochNetFlow`, the pool-crossing ETH used to compute the Central Bank's issuance signal (see [Protocol overview and issuance](./protocol-and-issuance#epochs-the-accounting-window-for-policy)). Every taxed swap still pays its tax, but two senders are excluded from that net-flow tally even though their swaps go through the same pool: the **Contraction Vault**'s buyback swaps and the **POL Manager**'s pairing swaps. This keeps a buyback from flipping the very signal that triggered it, and keeps a permissionless POL-pairing crank from injecting positive flow using the protocol's own ETH. Any reconstruction of the net-flow signal from raw swap events needs to apply the same sender exclusion, or it will overstate net inflow whenever a buyback or POL pairing swap ran during the window.
Protocol v1.1 (live 2026-09-21) moved POL's market-buying activity to a separate `polBuyback` contract rather than POL Manager itself (see [Reserves, vaults, POL, and buybacks](./reserves-vaults-and-buybacks#protocol-v11-bid-side-only-buybacks-and-the-incentives-vault)). Whether the hook's sender-exclusion list was updated to also exclude `polBuyback`, or only ever excluded POL Manager, hasn't been independently confirmed. If it wasn't updated, `polBuyback`'s market buys would now show up as ordinary positive net flow, which would matter for anyone reconstructing `epochNetFlow` from raw swap events.
## Taxes sit in the hook until forwarded
Collected taxes are held in the `TaxHook` contract itself, not pushed anywhere automatically. `address(hook).balance` at any moment is exactly the taxes accrued since the last forward. The permissionless `forwardTaxes()` function pushes that balance to the FeeSplitter, but only after an epoch boundary closes the running window (before the first boundary is armed, it forwards everything held). "Permissionless" here means anyone can call it, not that it happens automatically: until it's called (or the boundary closes and the bank pulls the window), collected taxes just accumulate in the hook rather than reaching the FeeSplitter. `GET /v1/buybacks/readiness` reports the ETH currently sitting in both the hook and the FeeSplitter (`hook_eth_balance_raw`, `fee_splitter_eth_balance_raw`); see [Reserves, vaults, POL, and buybacks](./reserves-vaults-and-buybacks#hook-held-taxes-are-not-vault-backing) for why that balance is protocol revenue in transit rather than reserve backing.
## LP withdrawals are taxed on principal
Withdrawing liquidity from the pool is also a taxable event for anyone other than the POL Manager. On `afterRemoveLiquidity`, the principal portion of a non-POL withdrawal (not the LP fees earned, which are untaxed) pays the same live rates as a trade: the ETH leg pays the sell rate and stays in the hook, and the STANDARD leg pays the buy rate and is burned immediately by the hook rather than returned to the withdrawer. This closes off using a concentrated-liquidity range position as a way to exit at the taxed traders' expense without paying the tax yourself. Relatedly, **third-party liquidity provision is closed only while the launch schedule is active**: the POL Manager is the only address that can add liquidity to the pool during that window. The schedule no longer runs, so anyone can add liquidity, subject to the ordinary tick-reservation rule that protects the POL Manager's own position.
## Launch holding cap
A separate launch-only control, a per-wallet holding cap of 1,200,000 STANDARD, applies only while the launch schedule is active; like the third-party-LP restriction, the holding cap no longer binds once the schedule ends.
## The override flag
The hook exposes a `taxOverridden` flag. When set, `currentTaxBps` returns a directly-configured buy/sell rate instead of running the decay formula. This is an owner-controlled escape hatch from the launch schedule, independent of elapsed time. `GET /v1/tax/schedule`'s `tax_overridden` field reports this live; when true, don't expect the decay projection to describe what happens next, since the decayed rate isn't what's actually being charged.
**What's confirmed:** the decay curve, the truncation behavior, and the buy/sell tax-collection points all carry `basis: measured` against real mainnet activity, and all match the hook's verified Solidity source exactly. The override flag's existence and its effect on `currentTaxBps` read the same way from both the measured calls and the source. That match is why this page can describe the exact-output grossing-up, the net-flow exclusion, and the forwarding mechanics in mechanism terms rather than only in terms of what was observed. The API's own `basis` field on `/v1/tax/schedule` doesn't distinguish a source-confirmed read from a measured-only one; treat everything on this page as the stronger of the two either way.
## Where to look
| Question | Route | MCP tool |
|---|---|---|
| What's the current buy/sell tax, and where is it in its decay? | `GET /v1/tax/schedule` | `1s_std_tax_schedule` |
| How does the tax affect an exit quote's realizable ETH? | `GET /v1/charters/{id}/exit-quote` | `1s_std_exit_quote` |
| How does the tax affect a license's ETH-converted cost? | `GET /v1/branches/license-cost` | `1s_std_license_cost` |
| How much ETH is sitting in the hook, unforwarded? | `GET /v1/buybacks/readiness` | `1s_std_buyback_readiness` |