Intents API transaction signing
Let an agent sign and submit an on-chain transaction while the private key stays in the vault.
The Intents API lets an agent sign transactions without ever seeing the raw private key. The vault decrypts the key inside the HSM, signs, and broadcasts. The agent only gets back a tx_hash.
- POST /v1/agents/:id/transactions signs and broadcasts
- POST /v1/agents/:id/transactions/sign signs only (BYORPC), returns signed_tx
- Unified POST /v1/agents/:id/sign takes intent_type: personal_sign, typed_data, or transaction
The intents_api_enabled flag is two-sided. It grants the transaction endpoints and also blocks direct reads of private_key or ssh_key through the normal secrets endpoint, so the agent can only use keys through the proxy.
- 1
Get a human API key from the dashboard. Replace with your 1ck_ key from Settings → API Keys.
bashexport TOKEN=1ck_your_key_here - 2
Create an agent with intents_api_enabled and guardrails. Replace 0xRecipient with a real Ethereum address: use the same address in the transaction to field later.
bashAGENT_RESPONSE=$(curl -s -X POST "https://api.1claw.co/v1/agents" \ -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \ -d '{ "name": "DeFi Bot", "intents_api_enabled": true, "tx_allowed_chains": ["sepolia", "base"], "tx_to_allowlist": ["0xRecipient"], "tx_max_value_eth": "0.01", "tx_daily_limit_eth": "0.05" }') echo "$AGENT_RESPONSE" - 3
Extract the agent id and api_key from the creation response. The agent id is at .agent.id and the api_key is at the top level. The ocv_ key is shown only once: save it.
bashexport AGENT_ID=$(echo "$AGENT_RESPONSE" | jq -r .agent.id) export AGENT_API_KEY=$(echo "$AGENT_RESPONSE" | jq -r .api_key) echo "AGENT_ID=$AGENT_ID" echo "AGENT_API_KEY=$AGENT_API_KEY" - 4
Provision an HSM-backed Ethereum signing key for the agent (using $AGENT_ID from the previous step). The private key lands in the __agent-keys vault.
bashcurl -X POST "https://api.1claw.co/v1/agents/$AGENT_ID/signing-keys" \ -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \ -d '{ "chain": "ethereum" }' - 5
Exchange the agent api_key for a JWT (using $AGENT_ID and $AGENT_API_KEY captured earlier).
bashexport AGENT_JWT=$(curl -s -X POST https://api.1claw.co/v1/auth/agent-token \ -H "Content-Type: application/json" \ -d "{\"agent_id\":\"$AGENT_ID\",\"api_key\":\"$AGENT_API_KEY\"}" | jq -r .access_token) - 6
Submit a transaction on Sepolia with simulate_first so a revert returns 422 and nothing is signed. Replace 0xRecipient with a real Ethereum address. Use the same address in both tx_to_allowlist and the transaction to field.
bashcurl -X POST "https://api.1claw.co/v1/agents/$AGENT_ID/transactions" \ -H "Authorization: Bearer $AGENT_JWT" -H "Content-Type: application/json" \ -d '{ "chain": "sepolia", "to": "0xRecipient", "value": "0.001", "data": "0x", "simulate_first": true }'
Signing keys provisioned as 'ethereum' work on Sepolia and other EVM chains allowed by your guardrails.
Fund the returned address with Sepolia ETH from a faucet (e.g. sepoliafaucet.com) before submitting a real transaction.
Want to sign but broadcast yourself? Call /transactions/sign with the same body. You get signed_tx and tx_hash back, and all guardrails still apply.
You now have an agent that submits a simulated, guardrailed transaction with the signing key locked inside the vault.
Where this goes wrong in practice. Guardrails are configured once and then quietly outgrown, and signing failures are unusually expensive because they are irreversible.
- Value caps are set in native units and the price moves. A 0.5 ETH daily limit written when ETH was cheap is a different limit a year later, and nothing re-evaluates it.
- EIP-712 is left at the default and then widened for one integration. Typed-data signing is deny-by-default for good reason, and every allowlisted domain is a permanent widening made for a temporary need.
- Unlimited ERC-20 approvals slip through. A transfer cap bounds what one transaction moves; an approval grants standing authority to move everything later, and it looks like a small transaction at signing time.
- Simulation failures are treated as noise. A failing simulation is the cheapest warning available, and routing it to a policy rather than a log is the difference between catching a bad call and funding one.
- Gas budgets are forgotten. Value caps say nothing about fees, so an agent stuck in a retry loop drains the account in overhead while every individual transaction stays inside its limit.
Set a calendar reminder to re-read every guardrail quarterly. They are written against conditions that change and they do not fail loudly when those conditions do.
Decide
An integration asks you to enable raw_signing_enabled on a trading agent. Their SDK computes an ERC-1271 nested EIP-712 hash client-side and needs the agent to sign the resulting 32 bytes. They point out that the hash is derived from data they control, so nothing unexpected can be signed.
How do you handle the request?
Check your understanding
3 questionsWhat does intents_api_enabled: true do besides granting the transaction endpoints?
What happens when simulate_first: true and the simulation reverts?
Which endpoint signs a transaction without broadcasting it?