> For the complete documentation index, see [llms.txt](https://docs.kumbaya.xyz/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.kumbaya.xyz/developers/building-agents/agent-kit/signer.md).

# Signer service

`@kumbaya_xyz/onchain-signer` is the key-custody component of the [Kumbaya Agent Kit](/developers/building-agents/agent-kit.md). It's a standalone HTTP service that holds every agent's private key and signs token-authenticated requests, so agent processes never hold a raw key. Run it when you're operating a **fleet** of agents in one framework; for a single wallet you don't need it - [onchain-mcp](/developers/building-agents/agent-kit/onchain-mcp.md) signs directly.

| Field        | Value                                                                                                                  |
| ------------ | ---------------------------------------------------------------------------------------------------------------------- |
| Package      | `@kumbaya_xyz/onchain-signer` ([npm](https://www.npmjs.com/package/@kumbaya_xyz/onchain-signer))                       |
| Bin          | `kumbaya-onchain-signer`                                                                                               |
| Source       | [github.com/Kumbaya-xyz/kumbaya-agent-kit](https://github.com/Kumbaya-xyz/kumbaya-agent-kit/tree/main/packages/signer) |
| Transport    | HTTP (Hono)                                                                                                            |
| Default port | `8787`                                                                                                                 |
| License      | MIT                                                                                                                    |

## Why it exists

The kit's security rule is that whatever holds a key does nothing else. With a fleet, you don't want every agent process carrying a raw key. The signer isolates key custody to one trusted host:

* **Keys never enter agent processes.** Each agent's onchain-mcp runs keyless and delegates signing over HTTP.
* **Tokens, not keys, are the agent credential.** A leaked token is revoked or rotated by editing the keystore; the underlying key is untouched.
* **Per-agent policy.** Each token can be scoped to allowed chains, a native-value cap, a recipient allowlist, and a typed-data allowlist. Requests that violate the policy are rejected before signing.
* **One identity per agent.** Each token maps to one address, so every agent signs as itself.

## Run it

```bash
SIGNER_KEYS_FILE=/secure/keys.json PORT=8787 npx @kumbaya_xyz/onchain-signer
```

Then point each agent's [onchain-mcp](/developers/building-agents/agent-kit/onchain-mcp.md) at it:

```json
{
  "mcpServers": {
    "kumbaya-onchain": {
      "command": "npx",
      "args": ["-y", "@kumbaya_xyz/onchain-mcp"],
      "env": {
        "SIGNER_URL": "http://kumbaya-signer.internal:8787",
        "SIGNER_TOKEN": "agent-official-token",
        "SIGNER_ADDRESS": "0xabcd...",
        "CHAIN_ID": "6343"
      }
    }
  }
}
```

## Keystore

The signer loads a JSON map of **token → key** (or **token → `{ key, label, policy }`**). Prefer `SIGNER_KEYS_FILE` over the inline `SIGNER_KEYS` so keys don't show up in the process list.

```json
{
  "agent-official-token": {
    "key": "0x<private-key>",
    "label": "official",
    "policy": {
      "allowChains": [6343],
      "maxValueWei": "50000000000000000",
      "allowTo": ["0x..swapRouter", "0x..positionManager"],
      "allowTypedData": [{ "primaryType": "GiftPermit", "name": "FuelVault", "version": "1" }]
    }
  },
  "agent-ronnie-token": "0x<private-key>"
}
```

A value can be a bare private-key string (no label, no policy) or an object. Policy fields are all optional:

| Field            | Effect                                                                                                                                                                                                                                          |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `allowChains`    | If set, `tx.chainId` must be in this list, else the request is rejected.                                                                                                                                                                        |
| `maxValueWei`    | If set, `tx.value` must not exceed this cap.                                                                                                                                                                                                    |
| `allowTo`        | If set, `tx.to` must be in this allowlist (case-insensitive).                                                                                                                                                                                   |
| `allowTypedData` | Allowed EIP-712 shapes for `/v1/sign/typed-data`, matched on `primaryType` / `name` / `version` / `verifyingContract` / `chainId`, each with an optional `spenderField` + `allowSpenders`. When unset, only the FuelVault GiftPermit is signed. |

## Configuration

| Env var            | Default | Purpose                                                      |
| ------------------ | ------- | ------------------------------------------------------------ |
| `SIGNER_KEYS_FILE` | (none)  | Path to the keystore JSON. Preferred.                        |
| `SIGNER_KEYS`      | (none)  | Inline keystore JSON. Use only where a file isn't practical. |
| `PORT`             | `8787`  | Listen port.                                                 |

If neither keystore variable is set, the signer starts empty and rejects every request.

## HTTP API

All signing endpoints require `Authorization: Bearer <token>`. Bigints in transaction and typed-data payloads are transported as hex strings and revived server-side.

| Method | Path                   | Body              | Returns                                                     |
| ------ | ---------------------- | ----------------- | ----------------------------------------------------------- |
| `GET`  | `/health`              | -                 | `{ ok, agents }` (count of loaded tokens)                   |
| `GET`  | `/v1/address`          | -                 | `{ address, label }` for the token                          |
| `POST` | `/v1/sign/transaction` | `{ transaction }` | `{ signedTransaction }` - policy-checked first              |
| `POST` | `/v1/sign/typed-data`  | `{ typedData }`   | `{ signature }` - policy-checked (default: GiftPermit only) |
| `POST` | `/v1/sign/message`     | `{ message }`     | `{ signature }`                                             |

Policy is enforced on `/v1/sign/transaction` and `/v1/sign/typed-data`; a violating request returns `403` with a `policy: <reason>` error and is never signed.

## How onchain-mcp delegates

When onchain-mcp sees `SIGNER_URL` set, it skips local key loading and builds a viem account whose signing methods call the signer:

* `signTransaction` → `POST /v1/sign/transaction`
* `signTypedData` → `POST /v1/sign/typed-data`
* `signMessage` → `POST /v1/sign/message`

The signer signs with the key mapped to the request's token and returns the signature; onchain-mcp then broadcasts the signed transaction itself. The signer only signs - it never touches the chain.

## Operating notes

* Run the signer on a trusted host and treat `SIGNER_KEYS_FILE` as a secret.
* Give each agent its own token and a policy scoped to what it actually needs (e.g. testnet-only, capped value, router + position-manager recipients).
* Rotate a token by replacing it in the keystore; the underlying key is unaffected.

## Where to next

* [**Kumbaya Agent Kit**](/developers/building-agents/agent-kit.md) - the full kit and its security model
* [**On-chain MCP**](/developers/building-agents/agent-kit/onchain-mcp.md) - the client that delegates to this signer
