Back to blog

One endpoint, seven signing modes: full EVM signing support for AI agents

1Claw’s unified sign endpoint now supports every modern EVM signing standard — legacy, EIP-2930, EIP-1559, EIP-4844, EIP-7702 transactions plus EIP-191 and EIP-712 — with per-agent signing keys, human-controlled rotation, and optional TEE signing via Shroud.

When we launched the Intents API, agents could sign legacy EIP-155 transactions without ever touching a private key. That covered the common case — simple ETH transfers and contract calls — but the EVM has evolved far beyond type-0 transactions. Today we're shipping support for every modern EVM signing standard through a single unified endpoint.

One endpoint, seven signing modes

POST /v1/agents/{id}/sign accepts an intent_type and dispatches to the right signing flow. No separate routes, no SDK version gymnastics — just one call with the fields that match your intent.

Transaction signing: types 0 through 4

Set intent_type: "transaction" and pass a tx_type to choose the envelope format. Here's what's supported:

tx_typeStandardWhat it does
0Legacy (EIP-155)The original transaction format with replay protection. Uses gas_price.
1EIP-2930Access list transactions. Pre-declare storage slots to reduce gas costs on cross-contract calls. Uses gas_price + access_list.
2EIP-1559The post-London fee market standard. Separate base fee and tip for predictable pricing. Uses max_fee_per_gas + max_priority_fee_per_gas.
3EIP-4844Blob-carrying transactions for L2 data availability (proto-danksharding). Adds max_fee_per_blob_gas and blob_versioned_hashes.
4EIP-7702Set-code transactions that let EOAs delegate to smart contract logic for the duration of a transaction. Adds authorization_list.

Every type returns a signed_tx (raw hex, ready to broadcast), a tx_hash, and the from address — derived server-side so the agent never needs the private key.

EIP-191: personal_sign

Off-chain message signing with the familiar \x19Ethereum Signed Message\n prefix. Pass intent_type: "personal_sign" with a hex-encoded message and get back a 65-byte signature. Useful for proving wallet ownership, signing login challenges, or authorizing off-chain actions without spending gas.

Messages are capped at 10 KB, and the agent must have message_signing_enabled: true set by a human — it's off by default so agents can't silently start signing messages the moment they get a key.

EIP-712: typed structured data

The most dangerous signing surface on Ethereum — Permit, Permit2, and typed approvals — is now available through intent_type: "typed_data". Pass the full EIP-712 payload (types, primaryType, domain, message) and 1Claw hashes it per-spec (domain separator, struct hash, referenced types, array encoding) and signs with secp256k1.

Because this surface is so powerful, we built in deny-by-default guardrails:

  • Default policy is deny. Unless you explicitly set eip712_default_policy: "allow", every typed-data signing request is blocked.
  • Known dangerous types are always guarded. Permit, Permit2, DaiPermit, PermitSingle, and PermitBatch require an explicit verifying contract in the allowlist regardless of the default policy.
  • Domain allowlist. eip712_domain_allowlist is a list of { verifying_contract } entries checked case-insensitively. If the contract isn't listed, the request is rejected.

This means an agent can interact with a specific Uniswap router or a governance contract, but can't be tricked into signing a token approval for a contract you didn't authorize.

Per-agent, per-chain signing keys

Every signing flow above needs a key. Instead of managing raw hex strings yourself, 1Claw now provisions dedicated signing keys per agent, per chain. A human creates the key from the dashboard or SDK — the private key is generated server-side, encrypted with Cloud KMS, and stored in the agent's __agent-keys vault. The agent never sees it. Only the public key and derived address are returned.

The full lifecycle is human-controlled:

  • Provision POST /v1/agents/{id}/signing-keys with { "chain": "ethereum" }. Returns the public key, address, and curve (secp256k1 for Ethereum). Agents are blocked from calling this endpoint — only human tokens are accepted.
  • Rotate POST /v1/agents/{id}/signing-keys/ethereum/rotate. Generates a fresh keypair, overwrites the old private key in the vault, and returns the new public material. The old key version is retained for audit but deactivated.
  • Deactivate DELETE /v1/agents/{id}/signing-keys/ethereum. Deactivates the key. The agent can no longer sign on that chain.
  • List GET /v1/agents/{id}/signing-keys. Returns all active keys with chain, curve, public key, and address.

End-to-end: provision a key, sign a transaction, never touch the private key

Here's the full flow using the TypeScript SDK. A human provisions the key once, then the agent signs as many transactions as its guardrails allow.

// ── Step 1: Human provisions an Ethereum signing key ──
import { OneclawClient } from "@1claw/sdk";

const admin = new OneclawClient({
  baseUrl: "https://api.1claw.xyz",
  token: process.env.HUMAN_JWT,
});

const key = await admin.signingKeys.create(agentId, {
  chain: "ethereum",
});
console.log(key.data.address);
//=> "0x7a3b…"  (public — safe to share)
//   Private key? Encrypted in the __agent-keys vault.
//   The human never sees it. The agent never sees it.

// ── Step 2: Agent authenticates and signs ──
const agent = new OneclawClient({
  baseUrl: "https://api.1claw.xyz",
  apiKey: process.env.ONECLAW_AGENT_API_KEY,
});

const tx = await agent.agents.signIntent(agentId, {
  intent_type: "transaction",
  chain: "ethereum",
  tx_type: 2,                          // EIP-1559
  to: "0xRecipient…",
  value: "0.05",
  max_fee_per_gas: "30000000000",
  max_priority_fee_per_gas: "1500000000",
  gas_limit: 21000,
  // signing_key_path auto-resolves to
  // agents/{id}/chains/ethereum/private_key
});

console.log(tx.data.signed_tx);   // raw hex
console.log(tx.data.from);        // matches key.data.address
// The agent sent an intent. 1Claw fetched the encrypted
// key, decrypted it in memory, signed, and returned the
// result. The private key never left the server.

// ── Step 3: Human rotates the key (agent keeps working) ──
const rotated = await admin.signingKeys.rotate(
  agentId, "ethereum"
);
console.log(rotated.data.address);
//=> "0x91f2…"  (new address, new key, zero downtime)

Even stronger: signing inside the Shroud TEE

If you route your agent's traffic through shroud.1claw.xyz instead of api.1claw.xyz, the signing happens inside a Trusted Execution Environment (AMD SEV-SNP on GKE Confidential Nodes). The private key is fetched as an encrypted blob, decrypted inside the TEE's isolated memory, used to sign, and immediately discarded. Not even the host operating system or 1Claw operators can observe it.

The agent's code doesn't change — just swap the base URL:

const agent = new OneclawClient({
  baseUrl: "https://shroud.1claw.xyz",  // ← TEE signing
  apiKey: process.env.ONECLAW_AGENT_API_KEY,
});

// Same signIntent call as above — but the signing
// happens inside an AMD SEV-SNP enclave. The key
// is decrypted in TEE memory and never persisted.
const tx = await agent.agents.signIntent(agentId, {
  intent_type: "transaction",
  chain: "ethereum",
  tx_type: 2,
  to: "0xRecipient…",
  value: "0.05",
  max_fee_per_gas: "30000000000",
  max_priority_fee_per_gas: "1500000000",
});

Shroud also enforces the same per-agent guardrails (allowlisted addresses, per-tx value cap, rolling 24-hour daily spend limit, chain restrictions) before signing, so you get both hardware isolation and policy enforcement in a single hop.

Why this matters for agents

AI agents are starting to do real work on-chain — deploying contracts, providing liquidity, managing DAO treasuries, posting blob data for rollups. Before today, most of those workflows required handing the agent a raw private key and hoping for the best.

With per-agent signing keys and the unified sign endpoint, the private key is generated, stored, and used entirely server-side — in an HSM-backed vault or inside a Shroud TEE. The agent submits an intent (“sign this EIP-1559 transfer” or “approve this typed-data payload”) and gets back a signed result. The key never enters the agent's memory, context window, or logs. Every signing action is audit-logged, subject to per-agent guardrails, and revocable in real time. Humans provision and rotate keys; agents just sign.

Quick example: EIP-1559 transfer via SDK

const result = await client.agents.signIntent(agentId, {
  intent_type: "transaction",
  chain: "ethereum",
  tx_type: 2,
  to: "0xRecipient...",
  value: "0.1",
  max_fee_per_gas: "30000000000",
  max_priority_fee_per_gas: "1500000000",
  gas_limit: 21000,
});

// result.signed_tx  → raw hex, ready to broadcast
// result.tx_hash    → keccak hash
// result.from       → derived address (no key exposure)

Quick example: EIP-712 typed data via SDK

const result = await client.agents.signIntent(agentId, {
  intent_type: "typed_data",
  chain: "ethereum",
  typed_data: {
    types: {
      EIP712Domain: [
        { name: "name", type: "string" },
        { name: "verifyingContract", type: "address" },
      ],
      Vote: [
        { name: "proposalId", type: "uint256" },
        { name: "support", type: "bool" },
      ],
    },
    primaryType: "Vote",
    domain: {
      name: "MyGovernance",
      verifyingContract: "0xGovernanceContract...",
    },
    message: { proposalId: "42", support: true },
  },
});

// result.signature       → 65-byte ECDSA sig
// result.typed_data_hash → EIP-712 signing hash

Available now

The unified sign endpoint is live on api.1claw.xyz and supported in the TypeScript SDK, CLI, and MCP server. All five transaction types, EIP-191 personal_sign, and EIP-712 typed_data are ready to use. Agents on the Business tier and above with the Intents API enabled can start signing today.

Get started · Read the docs · More from the blog