Agents that buy, sell and pay onchain — under a policy you wrote
Strix Hood is an intent-based commerce layer for autonomous agents. An agent states what it wants; the protocol decides whether it is allowed, how it should be routed, and proves what happened. This is the reference for the intent format, the policy engine, the security model and the onchain contracts.
Introduction
Large models can already decide what to buy. They cannot be trusted with a private key. Strix Hood exists to close that gap: it accepts a declarative intent from an agent, checks it against a signed spending policy, simulates it against live chain state, auctions execution to competing solvers, settles it, and writes an attestation that can be audited later.
The problem
Every practical approach to agent-driven commerce today fails in one of three ways.
| Approach | Failure mode | Consequence |
|---|---|---|
| Give the agent a hot wallet | Unbounded authority | One prompt injection drains the balance. No recovery, no recourse. |
| Human signs every transaction | Latency and attention | Defeats autonomy. A 20-second approval loop loses the fill. |
| Custodial API broker | Counterparty risk, no proof | You trust an operator's ledger. Nothing is verifiable onchain. |
All three collapse the same distinction: capability (the agent can produce a transaction) versus authority (the transaction is permitted). Strix Hood separates them. The agent produces intents. Authority lives in a policy that the agent cannot edit, committed onchain as a hash and enforced by the account's validator module at signing time.
The core promise
An agent holding a Strix Hood session key can spend only what the policy allows, only on the actions the policy names, only on the chains the policy lists, and only if the simulated asset diff matches what the intent claimed. Everything else reverts before it reaches the mempool.
Who it is for
- Agent developers shipping an autonomous trader, treasury manager, procurement bot or research agent that needs to move value without a human in the loop for every action.
- Applications that want to offer "let the assistant do it" without becoming a custodian or building a policy engine, simulator and router themselves.
- Solvers and market makers competing for agent order flow through the routing auction, and earning a share of the 0.25% fee.
- Risk and compliance owners who need a signed, replayable record of why every automated transaction was permitted.
What it is not
Strix Hood is not a wallet, not an LLM, and not a custodian. It never holds user funds outside of the atomic settlement window, it does not generate the agent's reasoning, and it does not decide whether a trade is a good idea. It decides whether a trade is permitted and executes it well. Read Security model for the explicit list of risks it does not remove.
Quickstart
This walkthrough gets a policy-governed agent from zero to a settled swap on Base Sepolia. It uses test keys throughout; nothing here touches mainnet value.
1. Install a client
The SDKs are thin, typed wrappers over the REST API. Every method in them maps to exactly one endpoint, so you can drop to raw HTTP at any point without losing behaviour.
npm install @strixhood/sdk
# or: pnpm add @strixhood/sdk / bun add @strixhood/sdkpip install strixhood
# requires Python 3.10+cargo add strix-hood --features rustls,stream2. Create an API key
Keys are created in the console under Settings → API keys. Two prefixes exist and they are not interchangeable.
| Prefix | Environment | Where it may be used | Scopes |
|---|---|---|---|
| strx_sk_test_ | Testnets | Server side only | All |
| strx_sk_live_ | Mainnets | Server side only | Granted per key |
| strx_pk_live_ | Mainnets | Browser, mobile, agent runtime | quotes:read, prices:read |
An agent that can read its own sk_ key can create a new policy for itself. Keep
secret keys on a server the model cannot reach, and give the agent runtime a
pk_ key plus a scoped session key. See
the five enforcement layers.
export STRIX_API_KEY="strx_sk_test_9f2c41bd7a084e6cb35d0e17"
export STRIX_ENV="testnet"3. Write the policy first
Policies are created before agents, not after. An agent without a bound policy can be registered but cannot be issued a session key, so it can never sign anything.
{
"name": "dca-conservative",
"limits": {
"per_tx_usd": 250,
"daily_usd": 1000,
"monthly_usd": 20000,
"max_open_intents": 4
},
"allow": {
"chains": ["eip155:8453", "eip155:42161"],
"actions": ["swap", "transfer"],
"tokens": ["USDC", "WETH", "cbBTC"],
"venues": ["uniswap_v4", "aerodrome", "curve"]
},
"deny": {
"categories": ["leverage", "gambling", "unverified_contract"]
},
"simulation": {
"require_success": true,
"max_price_impact_bps": 120,
"min_liquidity_usd": 250000
},
"hitl": {
"threshold_usd": 200,
"channels": ["webhook"],
"timeout_sec": 180,
"on_timeout": "reject"
},
"expires_at": "2027-01-01T00:00:00Z"
}curl -sS https://api.strixhood.xyz/v1/policies \
-H "Authorization: Bearer $STRIX_API_KEY" \
-H "Content-Type: application/json" \
--data @policy.json | jq '{id, version, hash}'{
"id": "pol_01JQ8ZK3M2X7YB4N6PA0RTVCWD",
"version": 1,
"hash": "0x7d41a9c0b83e5f2d16c4a87b90ef3524ca1d6b8f0472e93a5c18df6027ab4e91"
}4. Register the agent
Registration mints an Agent NFT Passport and locks a 2,500 $STRX bond. On testnets the bond is waived and the passport is minted on Base Sepolia.
import { Strix } from "@strixhood/sdk";
const strix = new Strix({ apiKey: process.env.STRIX_API_KEY! });
const agent = await strix.agents.create({
name: "dca-eth",
kind: "trader",
policyId: "pol_01JQ8ZK3M2X7YB4N6PA0RTVCWD",
chains: ["eip155:8453"],
sessionKey: { ttlSeconds: 86_400, rotate: true },
});
console.log(agent.id, agent.smartAccount, agent.passport.tokenId);
// agt_01JQ8ZM7T4B0KC2N9XE5RVDHQP 0x1F3c…9aE2 #4182import os
from strixhood import Strix
strix = Strix(api_key=os.environ["STRIX_API_KEY"])
agent = strix.agents.create(
name="dca-eth",
kind="trader",
policy_id="pol_01JQ8ZK3M2X7YB4N6PA0RTVCWD",
chains=["eip155:8453"],
session_key={"ttl_seconds": 86400, "rotate": True},
)
print(agent.id, agent.smart_account, agent.passport.token_id)use strix_hood::{Strix, CreateAgent, SessionKey};
let strix = Strix::from_env()?;
let agent = strix
.agents()
.create(CreateAgent {
name: "dca-eth".into(),
kind: "trader".into(),
policy_id: "pol_01JQ8ZK3M2X7YB4N6PA0RTVCWD".into(),
chains: vec!["eip155:8453".into()],
session_key: Some(SessionKey { ttl_seconds: 86_400, rotate: true }),
})
.await?;
println!("{} {}", agent.id, agent.smart_account);5. Submit the first intent
An intent is a statement of outcome, not a calldata blob. You never encode a router call, choose a pool or set a gas price — the solver auction does that, bounded by your constraints.
curl -sS https://api.strixhood.xyz/v1/intents \
-H "Authorization: Bearer $STRIX_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: dca-2026-08-16-0900" \
-d '{
"agent_id": "agt_01JQ8ZM7T4B0KC2N9XE5RVDHQP",
"action": "swap",
"chain": "eip155:8453",
"params": {
"sell_token": "USDC",
"buy_token": "WETH",
"sell_amount": "150.00"
},
"constraints": { "max_slippage_bps": 40, "route_preference": "best_price" }
}'{
"id": "int_01JQ8ZP1V6C3MD8R0YF2WKGSTA",
"object": "intent",
"status": "policy_check",
"agent_id": "agt_01JQ8ZM7T4B0KC2N9XE5RVDHQP",
"policy_version": 1,
"estimated": { "buy_amount": "0.04129", "fee_usd": 0.375, "price_impact_bps": 6 },
"created_at": "2026-08-16T09:00:00.412Z"
}6. Follow it to settlement
Poll GET /v1/intents/{id} if you must, but the stream is authoritative and costs no
rate-limit budget. Both surfaces emit the same status values.
const stream = strix.stream.executions({ agentId: agent.id });
for await (const evt of stream) {
console.log(evt.status, evt.txHash ?? "-");
if (evt.status === "settled") {
console.log("filled", evt.fills[0].buyAmount, "WETH");
console.log("attestation", evt.attestation.uid);
break;
}
}The intent was normalised, checked against policy version 1, simulated on a fork of the pending Base block, auctioned to three solvers, signed by a session key whose validator contains your policy hash, included, and attested. The full sequence is documented in Architecture.
Core concepts
Seven objects carry the whole protocol. Everything else in this documentation is a detail of how they interact.
Intents
An intent is a signed, expiring statement of a desired outcome — "end up holding at least 0.041 WETH, spending at most 150 USDC, on Base, within 90 seconds". It contains no calldata, no route and no gas parameters. That deliberate omission is what makes intents safe to hand to a language model: the worst an adversarial prompt can produce is a request that the policy engine rejects.
Intents are immutable once accepted. Changing terms means cancelling and submitting a new one.
Every intent carries an idempotency_key; replaying the same key inside 24 hours returns
the original intent rather than creating a second one.
Agents
An agent is the protocol-side identity of an autonomous actor. It owns an ERC-4337 smart account, one bound policy, zero or more session keys, a reputation score, and a $STRX bond that can be slashed. An agent is not a wallet: the smart account is controlled by the owner's root key, and the agent only ever holds a scoped session key with an expiry.
| Agent kind | Typical actions | Default bond |
|---|---|---|
| trader | swap, transfer, equity_order | 2,500 STRX |
| collector | nft_bid, nft_buy, transfer | 2,500 STRX |
| treasury | swap, transfer, subscribe | 10,000 STRX |
| service | agent_hire, transfer | 2,500 STRX |
| verified | Any, plus marketplace listing | 25,000 STRX |
The Agent NFT Passport
Registration mints an ERC-721 passport to the agent's owner. The passport is the portable record of what an agent is allowed to be, and it is the only object in the protocol that survives a full redeploy of the agent runtime.
- Identity —
tokenIdis the canonical agent reference onchain;agent_idis its offchain mirror. - Dynamic metadata — level, lifetime settled notional, success rate and capability traits are re-rendered on every 1,000 settlements or on demand.
- Permission traits — the passport records which capability modules are equipped (execution, data, payment, intelligence, security). Equipping a module raises specific policy ceilings; it never lowers a policy floor.
- Revenue rights — for service agents, marketplace fees settle to the passport holder, so selling the NFT transfers the income stream.
- Slashing surface — the bond is escrowed against the
tokenId. A slashed passport keeps its history; the history is the point.
Transferring a passport revokes every live session key for that agent in the same transaction and forces a policy re-bind by the new owner. There is no window in which the previous owner's policy governs the new owner's funds.
The policy engine
A policy is a versioned document of limits, allow lists, deny lists, simulation thresholds and
human-in-the-loop rules. It is evaluated offchain for speed and committed onchain as a
bytes32 hash for enforcement. The two must agree: the session key validator recomputes
nothing, but it refuses to validate a user operation whose attached policy hash is not the one the
PolicyRegistry currently holds for that agent. Full schema in
Policy engine.
Solvers and routing
Solvers are independent parties that compete to fill intents. When an intent clears policy and
simulation, the router broadcasts a sealed request for quotes; solvers respond with a committed
execution path and an output guarantee. The best quote by the intent's
route_preference wins, and the winner is bound to its quote — under-delivering slashes
25% of the solver's bond and refunds the difference to the agent.
| route_preference | Objective | Typical use |
|---|---|---|
| best_price | Maximise output after fees and gas | Default. Rebalancing, DCA. |
| fastest | Minimise time to inclusion | Liquidations, NFT snipes. |
| lowest_gas | Minimise gas paid | Batched maintenance work. |
| private | Private orderflow, no public mempool | Size that would be sandwiched. |
Settlement
Settlement is atomic per intent. The winning solver's path is executed through
SettlementVault, which enforces the output guarantee in the same transaction: if the
agent would receive less than the quoted minimum, the whole call reverts. Protocol fees are taken
from the output leg at 0.25% and split on settlement. Cross-chain intents settle as two locally
atomic legs with a bonded relayer, never as an optimistic promise.
Reputation and slashing
Every settled intent updates two scores. Agent reputation is a decayed ratio of settled to submitted intents weighted by notional, and gates marketplace visibility. Solver reliability is the ratio of honoured to won quotes, and gates auction participation. Both are recomputed onchain at each epoch (7,200 blocks on Ethereum, daily elsewhere). Slashing conditions and amounts are listed in Slashing.
Architecture
One intent travels through eight stages. Four of them can terminate it. The diagram below is the authoritative flow; the table under it names the component that owns each stage and what it is allowed to do.
Stage reference
| Stage | Owner | Does | Can terminate | p50 |
|---|---|---|---|---|
| 01 received | API gateway | Authenticates the key, enforces rate limits, deduplicates on Idempotency-Key, assigns a ULID. | No | 3 ms |
| 02 parsed | Intent compiler | Resolves token symbols to canonical addresses per chain, normalises decimals, expands defaults, validates the schema. | Yes — invalid_request_error | 6 ms |
| 03 policy check | Policy engine | Loads the bound policy at its committed hash, evaluates limits, lists and rolling windows. Escalates to the human gate above hitl.threshold_usd. | Yes — policy_violation | 4 ms |
| 04 simulation | Simulator | Executes the candidate path on a fork of the pending block with state overrides. Produces a signed asset diff. Runs drainer, approval-sweep and honeypot detectors. | Yes — simulation_failed | 118 ms |
| 05 routing | Router | Sealed request for quotes to eligible solvers, 150 ms window, ranks by route_preference, binds the winner to its output guarantee. | No — falls back to direct route | 176 ms |
| 06 execution | Bundler | Builds the ERC-4337 user operation, signs with the scoped session key, submits to the bundler or private relay. | No | 41 ms |
| 07 settlement | SettlementVault | Enforces the minimum output onchain, takes the 0.25% fee from the output leg, splits it, and emits Settled. | Reverts on shortfall | 1 block |
| 08 attestation | Attestor | Writes an EAS attestation binding intent hash, policy hash, simulation digest, solver and receipt. Fires execution.settled. | No | 92 ms |
Latencies are p50 measured over the last 30 days on Base and exclude block time. The end-to-end
budget from received to a broadcast user operation is 440 ms; anything slower than
1,200 ms trips an internal alert and the intent is re-quoted rather than executed on a stale price.
Failure modes
| Symptom | Cause | Protocol behaviour |
|---|---|---|
| Solver wins then under-delivers | Adverse move between quote and inclusion | Settlement reverts. Solver forfeits 25% of bond, agent is refunded gas, intent is re-quoted once. |
| No solver responds | Illiquid pair or all solvers rate-limited | Falls back to the direct canonical route inside the same slippage bound. Marked route_fallback. |
| Simulation and execution disagree | State changed between fork and inclusion | Onchain minimum-output check reverts the whole call. Intent moves to failed, funds never leave. |
| Human gate times out | No approval inside timeout_sec | on_timeout decides: reject (default) or hold until explicit action. |
| Chain reorg after settlement | Reorg deeper than the finality target | Attestation is marked reorged, webhook execution.reverted fires, balances re-derived from the canonical chain. |
Intent specification
The intent object is the single input surface of the protocol. It is stable across REST, WebSocket and all three SDKs; the SDKs only change the casing convention.
The intent object
| Field | Type | Required | Description |
|---|---|---|---|
| id | string | read only | ULID with an int_ prefix. Monotonic, so it doubles as a pagination cursor. |
| object | string | read only | Always "intent". |
| agent_id | string | required | The agent that will execute. Must be active and hold a live session key for chain. |
| action | enum | required | One of the seven values in Action types. Determines the shape of params. |
| chain | string | required | CAIP-2 identifier, for example eip155:8453 or solana:5eykt4Us…. Must appear in policy.allow.chains. |
| params | object | required | Action-specific payload. Unknown keys are rejected rather than ignored. |
| constraints | object | optional | Execution bounds. Defaults come from the policy, never from the market. |
| policy_id | string | optional | Overrides the agent's bound policy. The override must be stricter on every axis or the request is rejected. |
| expires_at | timestamp | optional | RFC 3339. Defaults to created_at + 300 s. Maximum 24 hours; NFT bids may set up to 30 days. |
| simulate_only | boolean | optional | Runs stages 01–05 and returns the quote and asset diff without signing. Default false. |
| idempotency_key | string | recommended | ≤ 128 chars. Also accepted as the Idempotency-Key header. Retained 24 hours. |
| metadata | object | optional | Up to 20 string keys, 512 bytes per value. Echoed on every webhook and included in the attestation payload. |
| status | enum | read only | See Status values. |
| execution_id | string | null | read only | Set once the intent enters routing. |
| rejection | object | null | read only | { code, message, rule, stage } — rule is the exact policy path that failed, e.g. limits.daily_usd. |
| created_at | timestamp | read only | Millisecond precision, UTC. |
constraints
| Field | Type | Default | Description |
|---|---|---|---|
| max_slippage_bps | integer | 50 | 1–5000. Enforced onchain as a minimum-output amount, not as a router hint. |
| max_fee_usd | number | null | Ceiling on protocol fee plus solver fee. Intent is rejected before routing if unreachable. |
| max_gas_usd | number | null | Ceiling on gas paid by the agent. Sponsored intents ignore this. |
| limit_price | decimal string | null | Quote asset per base asset. Present makes the intent a resting order; absent makes it marketable. |
| valid_after | timestamp | null | Do not route before this time. Used for scheduled DCA legs. |
| route_preference | enum | best_price | best_price, fastest, lowest_gas, private. |
| mev_protection | boolean | true | Routes through a private relay and rejects public-mempool solver paths. |
| partial_fill | boolean | false | Allows multiple fills against one intent. Each fill settles and attests independently. |
Action types
| action | Required params | Optional params | Notes |
|---|---|---|---|
| swap | sell_token, buy_token, sell_amount | buy_amount | recipient, pools | Exactly one of sell_amount / buy_amount. The other becomes the guaranteed side. |
| transfer | token, amount, to | memo | to must clear allow.contracts or be an EOA on the agent's address book. |
| nft_bid | collection, max_price, currency | token_id, traits, marketplaces, expiry | Trait bids are matched continuously until expires_at. One fill per bid unless partial_fill. |
| nft_buy | collection, token_id, max_price | marketplaces | Immediate purchase at or below max_price, aggregated across listed marketplaces. |
| equity_order | symbol, side, quantity | notional, order_type | limit_price, time_in_force, venue | Tokenized equities only. Subject to the issuer's transfer-agent hours; see the FAQ. |
| subscribe | merchant, token, amount, interval | start_at, max_cycles | Creates a recurring child intent per cycle. Each child is policy-checked at its own execution time. |
| agent_hire | target_agent_id, task, max_price | deadline, spec_uri | Escrowed agent-to-agent payment. Released on the target agent's signed completion attestation. |
Examples
Swap
A market swap with a hard 40 bps slippage bound and private routing. The sell_amount is
exact; buy_amount is guaranteed at a minimum by the settlement contract.
{
"agent_id": "agt_01JQ8ZM7T4B0KC2N9XE5RVDHQP",
"action": "swap",
"chain": "eip155:8453",
"params": {
"sell_token": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"buy_token": "0x4200000000000000000000000000000000000006",
"sell_amount": "1500.000000"
},
"constraints": {
"max_slippage_bps": 40,
"max_fee_usd": 6.00,
"route_preference": "private",
"mev_protection": true
},
"idempotency_key": "rebalance-2026-08-16T09:00Z",
"metadata": { "strategy": "weekly-rebalance", "leg": "1/3" }
}NFT trait bid
A standing bid across two marketplaces for any token in the collection matching both traits, live for seven days, denominated in WETH.
{
"agent_id": "agt_01JQ8ZQ4E9F1NH7T2XM6BKWCVD",
"action": "nft_bid",
"chain": "eip155:1",
"params": {
"collection": "0xBd3531dA5CF5857e7CfAA92426877b022e612cf8",
"traits": [
{ "type": "Background", "value": "Cosmic" },
{ "type": "Eyes", "value": "Laser" }
],
"max_price": "3.85",
"currency": "WETH",
"marketplaces": ["opensea", "blur"]
},
"constraints": { "partial_fill": false, "route_preference": "fastest" },
"expires_at": "2026-08-23T09:00:00Z"
}Tokenized equity order
A limit order for tokenized Apple equity, good until cancelled, settled onchain against the issuer's transfer agent. Fractional quantities are permitted to six decimals.
{
"agent_id": "agt_01JQ8ZR8H2K5PM3W9YT0CDNXBF",
"action": "equity_order",
"chain": "eip155:42161",
"params": {
"symbol": "AAPLX",
"side": "buy",
"notional": "2500.00",
"order_type": "limit",
"limit_price": "231.40",
"time_in_force": "gtc",
"venue": "backed_rwa"
},
"constraints": { "max_fee_usd": 8.00, "route_preference": "best_price" },
"expires_at": "2026-09-16T20:00:00Z",
"metadata": { "mandate": "core-equity", "reviewed_by": "risk-desk" }
}Tokenized equity intents submitted outside the venue's session are accepted and held in
routing until the session opens, unless expires_at falls first. Check
venue.session on the quote before assuming immediate fill.
Status values
| status | Terminal | Meaning |
|---|---|---|
| received | No | Accepted by the gateway, not yet compiled. |
| policy_check | No | Being evaluated against the bound policy. |
| awaiting_approval | No | Escalated to a human. Clock is hitl.timeout_sec. |
| simulating | No | Fork execution and asset-diff analysis in progress. |
| routing | No | Solver auction open, or waiting on valid_after / venue session. |
| submitted | No | User operation broadcast. tx_hash is populated. |
| settled | Yes | Included and attested. fills[] is final. |
| rejected | Yes | Failed a check. rejection.rule names the exact clause. |
| failed | Yes | Reverted onchain or the solver defaulted. No value moved. |
| expired | Yes | Passed expires_at without a fill. |
| cancelled | Yes | Cancelled by the owner before submitted. |
Policy engine
A policy is the only thing standing between an agent and your balance. It is written by a human, versioned, hashed, committed onchain, and enforced twice — once offchain for a fast rejection, once onchain because offchain checks can be bypassed.
The policy object
| Field | Type | Required | Description |
|---|---|---|---|
| id | string | read only | ULID with a pol_ prefix. |
| name | string | required | 1–64 chars, unique per account. Used in approval prompts, so make it readable by a human at 3 a.m. |
| version | integer | read only | Increments on every update. Old versions stay readable for audit. |
| hash | bytes32 | read only | keccak256 of the canonicalised document. See Policy hash. |
| limits | object | required | Spending ceilings. At least per_tx_usd must be set. |
| allow | object | required | Positive lists. An empty list means "nothing", never "everything". |
| deny | object | optional | Negative lists. Evaluated after allow and always wins. |
| simulation | object | optional | Thresholds applied to the simulated asset diff. |
| hitl | object | optional | Human-in-the-loop escalation. Absent means never escalate. |
| expires_at | timestamp | recommended | After this, every intent under the policy is rejected. A policy without an expiry is a standing grant. |
| commitment | object | read only | { chain, registry, tx_hash, block, committed_at } for the onchain hash commitment. |
Spending limits
All limits are denominated in USD and evaluated against the notional of the intent at the price observed during simulation, not at submission. Rolling windows are true sliding windows, computed over settled and in-flight intents so two concurrent requests cannot both slip under a cap.
| Field | Type | Window | Description |
|---|---|---|---|
| per_tx_usd | number | — | Maximum notional of a single intent. Required. |
| daily_usd | number | 24 h sliding | Sum of settled plus in-flight notional. |
| weekly_usd | number | 7 d sliding | Applied after daily_usd. |
| monthly_usd | number | 30 d sliding | Applied after weekly_usd. |
| max_open_intents | integer | instant | Concurrency ceiling. Prevents a runaway loop from queueing a thousand orders. |
| max_position_pct | number | instant | Ceiling on any single asset as a percentage of the agent's portfolio after the trade. |
| gas_budget_daily_usd | number | 24 h sliding | Separate from notional. Stops gas-griefing loops. |
An intent reserves against its window from policy_check until it reaches a
terminal status. This is why a rejected intent frees budget immediately while a
routing intent does not.
Allow and deny lists
Evaluation is strict: an intent must match every relevant allow dimension and
no deny entry. Omitting a dimension from allow denies it
entirely. There is no wildcard for contracts.
{
"allow": {
"chains": ["eip155:8453", "eip155:42161"],
"actions": ["swap", "transfer", "equity_order"],
"tokens": ["USDC", "WETH", "cbBTC", "AAPLX"],
"collections": [],
"contracts": ["0x2626664c2603336E57B271c5C0b26F421741e481"],
"venues": ["uniswap_v4", "aerodrome", "backed_rwa"],
"categories": ["spot", "rwa_equity"]
},
"deny": {
"tokens": ["*_LEVERAGED", "*_3L", "*_3S"],
"contracts": ["0x0000000000000000000000000000000000000000"],
"categories": ["gambling", "leverage", "unverified_contract", "sanctioned"]
}
}| Category | Matches |
|---|---|
| unverified_contract | Target has no verified source on the canonical explorer, or its proxy implementation changed within 72 hours. |
| leverage | Perpetuals, margin, leveraged tokens, and any position with a liquidation price. |
| gambling | Prediction markets, lotteries, casino contracts on the maintained registry. |
| sanctioned | Addresses on OFAC SDN and the equivalent EU/UK lists, refreshed hourly. |
| low_liquidity | Pair depth below simulation.min_liquidity_usd at simulation time. |
| rwa_equity | Tokenized equities and ETFs with a named transfer agent. |
Human-in-the-loop
The human gate is a policy outcome, not a separate product. Above the threshold the intent moves to
awaiting_approval, an approval.requested webhook fires with the full
simulated asset diff, and the clock starts.
{
"hitl": {
"threshold_usd": 500,
"actions": ["transfer", "nft_buy"],
"always_for_new_counterparty": true,
"channels": ["webhook", "push"],
"timeout_sec": 300,
"on_timeout": "reject",
"approvers": ["usr_01JQ8ZS2M4N6P8R0T2V4X6Z8B0"],
"quorum": 1
}
}threshold_usd— escalate any intent whose notional exceeds this.actions— escalate these actions at any notional. Union with the threshold rule.always_for_new_counterparty— escalate the first interaction with any address the agent has never settled with before, regardless of size.on_timeout—rejectfails safe (default);holdkeeps the intent pending until an explicit decision, at the cost of a stale price.quorum— number of distinct approvers required. Approvals are signed by the approver's key and included in the attestation.
Policy hash and onchain commitment
The hash is what makes the policy enforceable rather than advisory. It is computed as follows, and the algorithm is fixed for the lifetime of an API version.
- Drop every server-assigned field:
id,version,hash,commitment,created_at,updated_at. - Canonicalise the remainder with RFC 8785 JSON Canonicalisation Scheme — keys sorted by UTF-16 code unit, no insignificant whitespace, numbers in shortest round-trip form.
- Prefix the domain separator
"strixhood.policy.v1"as UTF-8 bytes. - Take
keccak256of the concatenation. That 32-byte digest ispolicy.hash. - Call
PolicyRegistry.commit(agentId, policyHash, version). The registry stores one live hash per agent and emitsPolicyCommitted.
import { canonicalize } from "@strixhood/sdk/jcs";
import { keccak256, toBytes, concat } from "viem";
export function policyHash(policy: Record<string, unknown>): `0x${string}` {
const { id, version, hash, commitment, created_at, updated_at, ...doc } = policy as never;
const body = toBytes(canonicalize(doc));
const domain = toBytes("strixhood.policy.v1");
return keccak256(concat([domain, body]));
}
// Recompute locally and compare with what the registry holds.
const local = policyHash(await strix.policies.get("pol_01JQ8ZK3M2X7YB4N6PA0RTVCWD"));
const onchain = await registry.read.policyOf([agent.passport.tokenId]);
if (local !== onchain) throw new Error("policy drift: refuse to sign");The session-key validator module reads the committed hash on every user operation and compares it with the hash embedded in the key's permission blob at issuance. Rotating a policy therefore invalidates every session key issued under the previous version — by construction, not by convention.
Evaluation order
Order matters because the first failure short-circuits and is reported as
rejection.rule. Knowing the order tells you which rule to loosen.
| # | Check | Failure code |
|---|---|---|
| 1 | Policy is live, not expired, and its hash matches the registry | policy_stale |
| 2 | chain ∈ allow.chains | chain_not_allowed |
| 3 | action ∈ allow.actions | action_not_allowed |
| 4 | Every token, collection, contract and venue in params is allow-listed | asset_not_allowed |
| 5 | Nothing in params matches deny | denied |
| 6 | per_tx_usd | limit_exceeded |
| 7 | Sliding windows, daily then weekly then monthly | limit_exceeded |
| 8 | max_open_intents, max_position_pct, gas budget | limit_exceeded |
| 9 | Human gate evaluation | approval_required |
| 10 | Simulation thresholds, after stage 04 returns | simulation_failed |
Security model
This section is written to be argued with. It states what the protocol enforces, what it assumes, and what it cannot do. If a claim here is not testable against the contracts, it should not be here.
The five layers
Each layer is independent. Removing any one of them still leaves the others enforcing; none of them depends on another being honest.
| Layer | Mechanism | Stops | Enforced |
|---|---|---|---|
| 1 · Account abstraction | ERC-4337 v0.7 smart account. Session keys carry a permission blob: selector allowlist, value ceiling, chain, expiry, policy hash. | Key exfiltration turning into unlimited spend. An escaped session key expires and cannot call unlisted selectors. | Onchain, in the validator module |
| 2 · Spending policy | Versioned policy document, keccak256-committed to PolicyRegistry, evaluated offchain and bound into the key. | Scope creep. An agent cannot widen its own authority, because it cannot produce a valid commitment. | Offchain for speed, onchain for truth |
| 3 · Simulation | Fork execution against the pending block with state overrides. Signed asset diff. Drainer, approval-sweep, honeypot and price-impact detectors. | Malicious calldata that looks benign: infinite approvals, hidden transfer hooks, fee-on-transfer traps. | Offchain, gates signing |
| 4 · Contract audit | Source verification, proxy-admin and implementation-age checks, deployer reputation, sanctions screening, maintained registries. | Interaction with a contract deployed 40 seconds ago by an address with no history. | Offchain, at policy-check time |
| 5 · Human in the loop | Threshold, action and new-counterparty escalation with signed approvals and a fail-safe timeout. | Everything the first four layers considered permissible but a person would not. | Offchain, blocks signing |
Threat model
Assumed adversaries, and what the protocol does about each.
| Adversary | Capability assumed | Mitigation | Residual risk |
|---|---|---|---|
| Prompt injection into the agent | Full control of the intents the agent emits | Intents carry no calldata; policy bounds every dimension; simulation checks the diff. | Attacker can burn the agent's allowed budget on permitted actions — for example swapping USDC to WETH repeatedly within limits. |
| Compromised agent host | Reads the session key from memory | Key is scoped, expiring and policy-bound; rotation is automatic; owner can revoke in one transaction. | Value up to the remaining window limits until revocation lands. |
| Malicious counterparty contract | Arbitrary code at the target address | Layer 3 and 4: asset-diff must match intent; unverified and fresh contracts are denied by category. | A contract that behaves correctly in simulation and maliciously on a later call path. |
| Dishonest solver | Wins the auction, then under-delivers | Output guarantee enforced onchain in SettlementVault; 25% bond slash; reliability score gates future auctions. | Griefing by repeatedly losing the auction to delay a fill. |
| Searcher / MEV | Observes the mempool, reorders | mev_protection routes privately; minimum output enforced atomically. | Timing leakage from public settlement events after the fact. |
| Leaked API key | Full REST access with the key's scopes | Scoped keys; secret keys cannot sign — only session keys can; policy is unchanged by API access without policies:write. | A key with policies:write can widen a policy. Do not issue one to anything an agent can read. |
| Protocol operator | Controls the offchain services | Onchain policy hash and settlement checks are independent of the operator; attestations are verifiable by anyone. | Operator can censor or delay intents. It cannot move funds outside policy. |
What this does not protect against
Strix Hood bounds the blast radius of an autonomous actor. It does not make that actor correct, and it removes none of the following risks.
- Your root key. If the owner key controlling the smart account is compromised, the attacker rewrites the policy and every layer above is void.
- Bad strategy. A policy-compliant trade can still lose money. The protocol has no opinion on whether a permitted action is a good one.
- Market and liquidity risk. Slippage bounds guarantee an execution price, not a fair one. Thin markets stay thin.
- Third-party protocol failure. If you allowlist a lending market and it is exploited, funds you sent there are gone. Allowlisting is an explicit trust decision.
- Oracle and price-feed failure. Notional limits use observed prices. A manipulated venue price manipulates the limit calculation with it.
- RWA issuer and transfer-agent risk. Tokenized equities carry issuer credit risk, redemption risk and jurisdictional restrictions. The token is a claim, not the share.
- Deep reorgs and chain halts. Attestations follow the canonical chain; a reorg beyond the finality target can unwind a settled intent. See Failure modes.
- Phishing the human approver. The human gate is only as good as the person reading the diff. Approval fatigue is a real failure mode; set thresholds you will actually respect.
- Regulatory and tax exposure. Automation does not change your obligations, and the protocol does not file anything on your behalf.
- Availability. The protocol depends on the underlying chains, bundlers and relays. Degraded modes are published on the status page.
Audits and disclosure
No audit has been completed. No firm is engaged, no report exists, and every contract currently running on a testnet is unaudited. The table below is the scope we intend to put in front of an external firm, published now so the order is on the record before the engagement is.
| Component | Scope | Status |
|---|---|---|
| Core contracts — router, vault, registry | Settlement path, minimum-output enforcement, fee split, registry writes. | SCHEDULED |
| Session-key validator module | ERC-4337 validation, capability-to-selector mapping, expiry and revocation. | SCHEDULED |
| Solver auction and bonding | Sealed-bid mechanics, bond accounting, slashing and the challenge window. | NOT STARTED |
| RWA token and allow-list | Permissioned transfer hooks, register reconciliation, corporate-action freeze. | NOT STARTED |
| Indexer and API | No funds and no signing authority, so it is last — but it can still misreport state. | NOT STARTED |
There is no funded bug bounty. Report vulnerabilities to security@strixhood.xyz with
the PGP key published at /.well-known/security.txt anyway — findings are credited and
published, and the reward schedule is set out on the
security page. Do not open a public issue. Disclosure target is
90 days or on-fix, whichever comes first.
Tokenomics
$STRX exists to make agent identity expensive to fake and dishonest execution expensive to attempt. It is a work token and a bond, not a payment rail — commerce settles in USDC, WETH and the assets being traded.
No token is deployed, there has been no TGE, there is no market and there is no sale. The figures in this section are the designed parameters — supply, splits and unlock shape — not a description of anything you can hold or buy. Testnet STRX is a faucet token with no value. Any $STRX offered to you today is a scam.
Supply and distribution
| Allocation | Share | $STRX | Unlock | Controlled by |
|---|---|---|---|---|
| Community & Ecosystem | 40% | 400,000,000 | 48-month linear emission to stakers, solvers and grant recipients. No TGE unlock. | Emissions contract |
| Team & Advisors | 20% | 200,000,000 | 12-month cliff, then 36-month linear vesting. | Vesting escrow |
| Protocol Treasury | 15% | 150,000,000 | Unlocked, governance-gated. Spend requires a passed proposal and a 48-hour timelock. | Governance timelock |
| Liquidity | 15% | 150,000,000 | 20% at TGE for initial depth; remainder released against depth targets over 24 months. | Liquidity multisig 4/7 |
| Early Contributors | 10% | 100,000,000 | 6-month cliff, then 24-month linear vesting. | Vesting escrow |
Protocol fee
Every settled intent pays 0.25% of settled notional, taken from the output leg
inside the settlement transaction. There is no fee on rejected, failed or expired intents, and no
fee on simulate_only calls. Subscription tiers in
Rate limits & pricing are separate and buy throughput, not lower fees.
| Destination | Share of fee | Mechanism |
|---|---|---|
| Protocol Treasury | 40% | Accrues in the settled asset, swept to USDC weekly. Spend is governance-gated. |
| Stakers | 30% | Streamed pro-rata to staked $STRX, claimable continuously, no epoch lock. |
| Buyback & burn | 30% | Executed as TWAP over 24 hours by the treasury keeper; burned to 0x…dEaD with an onchain receipt. |
settled notional $1,500.00
protocol fee 0.25% $ 3.75 taken from the WETH output leg
├─ treasury 40% $ 1.50
├─ stakers 30% $ 1.125
└─ buyback 30% $ 1.125 → TWAP buy, burn, receipt emitted
solver fee (quoted) $ 0.90 paid by the solver's own margin, not added on top
agent receives output − 3.75 USD equivalent, ≥ quoted minimumStaking and registration bonds
Two distinct locks use the same token and must not be confused.
| Registration bond | Fee stake | |
|---|---|---|
| Purpose | Sybil resistance and slashable collateral for one agent | Claim on 30% of protocol fees |
| Minimum | 2,500 STRX | no minimum |
| Locked against | The agent's passport tokenId | The staker's address |
| Slashable | Yes — see below | No |
| Unbonding | 14 days after the agent is retired | 21 days |
| Yield | None | Fee share, streamed |
Solvers post a separate bond sized to their maximum in-flight quote exposure, with a floor of 50,000 $STRX. A solver whose bond falls below its exposure is excluded from the auction until it tops up.
Slashing conditions
| Condition | Penalty | Detection | Destination of slashed bond |
|---|---|---|---|
| Forged policy commitment — submitting a user operation whose policy hash does not match the registry | 100% | Onchain, deterministic | Treasury |
| Reputation fraud — wash volume, self-dealing between agents under one owner to inflate a score | 50% | Graph analysis, challenge period of 7 days | 50% to the challenger, 50% burned |
| Solver default — winning a quote and settling below the guaranteed output | 25% | Onchain, at settlement revert | Refund to the affected agent, remainder to stakers |
| Liveness failure — a service agent accepting a hire and missing the deadline | 5% | Deadline elapses without a completion attestation | Refund to the hiring agent |
Slashing is executed by the StakingBond contract. Everything except the deterministic
onchain cases passes through a 7-day challenge window in which the accused can post evidence; an
unchallenged claim executes automatically, a challenged one goes to governance.
Networks & contracts
Nothing is deployed to mainnet. Registry and settlement contracts run on three EVM testnets; four more networks are queued behind them. EVM contracts deploy with CREATE2 from the same factory, so every EVM chain will share one address per contract. Solana runs a separate program set.
Deployment status
The target set is seven networks. Where a contract exists today it exists on a testnet, and it is redeployed without notice. Treat every row below as the current state, not a roadmap.
| Network | Target chain | Chain ID | CAIP-2 | Finality target | Status | Explorer |
|---|---|---|---|---|---|---|
| Ethereum | Sepolia | 1 | eip155:1 | 2 epochs (~13 min) | TESTNET | Etherscan |
| Base | Base Sepolia | 8453 | eip155:8453 | L1 inclusion (~3 min) | TESTNET | Basescan |
| Arbitrum One | Arbitrum Sepolia | 42161 | eip155:42161 | L1 inclusion (~4 min) | TESTNET | Arbiscan |
| OP Mainnet | OP Sepolia | 10 | eip155:10 | L1 inclusion (~3 min) | QUEUED | Etherscan |
| Polygon PoS | Amoy | 137 | eip155:137 | 128 blocks (~4 min) | QUEUED | Polygonscan |
| BNB Chain | BNB Testnet | 56 | eip155:56 | 15 blocks (~45 s) | QUEUED | BscScan |
| Solana | Devnet | — | solana:5eykt4Us… | 32 slots (~13 s) | QUEUED | Solscan |
Use a strx_sk_test_ key against anything above. strx_sk_live_ keys exist in
the key schema but have no mainnet to address, so they are rejected everywhere today.
Contract addresses
There are none to publish. No Strix Hood contract is deployed to any mainnet, there is no $STRX
token contract, and the testnet deployments are not stable enough to pin. Addresses will appear in
this table, in @strixhood/sdk/deployments.json and on the repository release tags at the
same time — never one before the others.
| Contract | Purpose | Mainnet address | Status |
|---|---|---|---|
| IntentRouter | Accepts routed intents, opens the solver auction, forwards the winner. | not deployed | TESTNET ONLY |
| PolicyRegistry | One live policy hash per agent. Source of truth for the validator module. | not deployed | TESTNET ONLY |
| AgentPassport | ERC-721 identity, dynamic metadata, capability traits, revenue rights. | not deployed | TESTNET ONLY |
| SettlementVault | Enforces minimum output, takes and splits the 0.25% fee, emits Settled. | not deployed | TESTNET ONLY |
| SolverRegistry | Solver bonds, reliability scores, auction eligibility. | not deployed | TESTNET ONLY |
| StakingBond | Registration bonds, fee staking, slashing execution and challenges. | not deployed | TESTNET ONLY |
| STRX token | ERC-20, 18 decimals, fixed supply. Canonical deployment will be Base. | not deployed | NOT DEPLOYED |
| Program | Purpose | Program ID | Status |
|---|---|---|---|
| Intent router | Intent account creation and solver crank. | not deployed | QUEUED |
| Passport | Metaplex-compatible agent passport with policy PDA. | not deployed | QUEUED |
| Settlement | Minimum-output enforcement and fee split. | not deployed | QUEUED |
Nobody from Strix Hood will ever DM you a contract address, and today anyone offering you one
is lying, because none exist. When mainnet addresses are published, cross-check them against
this page, the SDK's deployments.json and the explorer's verified-source badge
before granting an allowance. Testnet contracts are unaudited and get redeployed without notice.
Rate limits & pricing tiers
Subscription tiers buy throughput and support. They do not change the 0.25% protocol fee, and they do not change what a policy allows. Nothing is billable today — testnet runs on the Sandbox tier and it is free.
Tiers
| Tier | Price | Requests / min | Concurrent intents | Intents / month | WS connections | Webhooks | Networks | Support |
|---|---|---|---|---|---|---|---|---|
| Sandbox | free | 60 | 2 | 1,000 | 1 | 1 | Testnets only | Community |
| Builder | TBA | 600 | 25 | 50,000 | 5 | 10 | All | Email, 2 business days |
| Growth | TBA | 3,000 | 200 | 500,000 | 25 | 50 | All | Email, 8 h · 99.9% SLA |
| Scale | TBA | 12,000 | 1,000 | 5,000,000 | 100 | 200 | All + priority solver lane | Shared channel, 1 h · 99.95% SLA |
| Enterprise | TBA | Negotiated | Negotiated | Unmetered | Negotiated | Negotiated | All + private solver pool | Named engineer · 99.99% SLA |
The limits are real and enforced now; the prices are not set. Paid tiers are priced at mainnet, and no card is taken before then. Overage on intents per month will be billed per intent rather than blocked, so a traffic spike degrades your invoice and not your agents. Overage on requests per minute is never billed — it is rate limited.
Rate-limit headers
Every response carries the current window state. Read them; do not guess.
HTTP/1.1 200 OK
X-RateLimit-Limit: 600
X-RateLimit-Remaining: 574
X-RateLimit-Reset: 1786953600
X-Request-Id: req_01JQ8ZT6P0Q2S4U6W8Y0A2C4E6
Strix-Api-Version: 2026-07-01| Header | Meaning |
|---|---|
| X-RateLimit-Limit | Requests permitted in the current 60-second window. |
| X-RateLimit-Remaining | Requests left. Treat 0 as "stop", not "try harder". |
| X-RateLimit-Reset | Unix seconds at which the window resets. |
| Retry-After | Only on 429. Seconds to wait. Authoritative — ignore your own backoff if it is shorter. |
| X-Request-Id | Include this in any support request. It resolves to the full trace. |
Bursts and backoff
The limiter is a token bucket refilled continuously at limit / 60 per second with a
burst capacity of limit / 4. Short spikes pass; sustained overload does not. On
429, back off with full jitter and honour Retry-After.
async function withRetry<T>(fn: () => Promise<T>, tries = 5): Promise<T> {
for (let attempt = 0; ; attempt++) {
try {
return await fn();
} catch (err: unknown) {
const e = err as { status?: number; retryAfter?: number };
const retryable = e.status === 429 || (e.status ?? 0) >= 500;
if (!retryable || attempt >= tries - 1) throw err;
const ceiling = e.retryAfter != null ? e.retryAfter * 1000 : Math.min(2 ** attempt * 250, 8_000);
await new Promise((r) => setTimeout(r, Math.random() * ceiling));
}
}
}WebSocket frames do not consume the request budget. If you are polling
GET /v1/intents in a loop, replace it with the intents channel and the
rate limit stops being your problem.
FAQ
Does Strix Hood ever hold my funds?
No, outside the settlement transaction itself. Assets live in your
ERC-4337 smart account, which you control with your root key. SettlementVault
touches them only inside the atomic call that fills your intent; if that call does not satisfy
the minimum output, it reverts and nothing moved.
What happens if I lose the session key?
Nothing catastrophic. A session key is scoped, expiring and
policy-bound. Revoke it with DELETE /v1/agents/{id}/session-keys/{keyId}, which
submits an onchain revocation. Until that lands the key can still spend up to the remaining
window limits, which is the argument for short TTLs.
Can the agent change its own policy?
Only if you gave the agent runtime a key with
policies:write. Do not. The intended split is: server holds the secret key and
writes policy; agent holds a publishable key plus a session key and writes intents.
Why is my intent stuck in routing?
Three common causes: valid_after has not passed; the
venue's trading session is closed (tokenized equities); or no solver has quoted inside your
slippage bound. The quote_status field on the execution object names which.
Are tokenized equities tradable 24/7?
Depends on the venue. Some issuers support continuous onchain
secondary trading; primary issuance and redemption follow the transfer agent's hours and
settlement calendar. The quote object returns venue.session with the next open and
close, and orders outside a session are held rather than rejected.
How do I test without spending real money?
Use a strx_sk_test_ key against Base Sepolia or Arbitrum
Sepolia. Test-mode agents skip the $STRX bond, and the faucet in the console funds the smart
account. Alternatively set simulate_only: true on mainnet to get a real quote and
a real asset diff without signing anything.
What is the difference between rejected and failed?
rejected means a check refused the intent before signing —
policy, schema or simulation. Nothing was broadcast. failed means it was broadcast
and reverted onchain, usually because state moved between simulation and inclusion. Both are
terminal; only failed costs gas.
Can two agents share one policy?
Yes. A policy can be bound to many agents, and its limits then apply to the union of their activity — a shared $1,000 daily cap is $1,000 in total, not per agent. Use this for a fleet that must respect one budget.
Do I need $STRX to use the API?
Not on testnets, and not for simulate_only. Mainnet agent
registration posts a bond, which the console can source for you at registration time. Fees are
paid in the settled asset, not in $STRX.
Is the protocol upgradeable?
IntentRouter and SolverRegistry sit behind a
governance timelock of 48 hours. SettlementVault, PolicyRegistry and
AgentPassport are immutable — a new version means a new address and an explicit
migration, never a silent implementation swap under your allowances.
Glossary
- Agent
- Protocol-side identity of an autonomous actor: a smart account, one bound policy, session keys, a reputation score and a slashable bond.
- Asset diff
- The signed before/after balance delta produced by simulation. The intent is only signed if the diff matches what the intent claimed.
- Attestation
- An EAS record binding intent hash, policy hash, simulation digest, solver identity and receipt. The audit artefact.
- Bond
- $STRX locked against an agent passport or a solver's exposure, slashable under the conditions in Slashing.
- CAIP-2
- Chain-agnostic identifier standard, e.g.
eip155:8453. Used everywhere a chain is named. - Execution
- The object created when an intent enters routing. Holds quotes, fills, transaction hashes and the attestation.
- Fill
- One settled portion of an intent. An intent without
partial_fillhas exactly one. - HITL
- Human in the loop. The policy clause that escalates an intent to a person before signing.
- Intent
- A declarative, expiring statement of a desired outcome with no calldata and no route.
- Notional
- USD value of an intent at simulation-time prices. The unit all policy limits are denominated in.
- Passport
- ERC-721 token that carries an agent's identity, level, traits and revenue rights.
- Policy hash
- keccak256 of the domain-separated, RFC 8785 canonicalised policy document. Committed onchain, embedded in session keys.
- Route preference
- The objective the solver auction optimises: price, speed, gas or privacy.
- Session key
- A short-lived signing key with a permission blob: selectors, value ceiling, chain, expiry and the policy hash it was issued under.
- Simulation
- Fork execution of the candidate path against the pending block, with state overrides, producing the asset diff.
- Solver
- A bonded third party that competes to fill intents and is contractually bound to the output it quoted.
- ULID
- The lexicographically sortable identifier format used for every object ID, which makes IDs usable as pagination cursors.
- User operation
- The ERC-4337 transaction envelope signed by a session key and validated by the account's validator module.