> For the complete documentation index, see [llms.txt](https://gotts.gitbook.io/docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://gotts.gitbook.io/docs/prd-shared/credential-architecture.md).

# Credential Architecture

> **Referenced by**: [vault/00-quickstart.md](/docs/gotts-vaults/vault/00-quickstart.md), [vault/03-custody.md](/docs/gotts-vaults/vault/03-custody.md), [mcp-server/10-wallets.md](/docs/gotts-safe-mcp-server/mcp-server/10-wallets.md), [shared/onboarding-workflow.md](/docs/prd-shared/onboarding-workflow.md) | **Last Updated**: 2026-02-21
>
> *Read this first. This document explains who holds what, where keys live, what happens when things fail, and how to rotate credentials. It is the foundation for all wallet and custody decisions.*

***

## 1. The Agent Never Holds Keys

The most important thing to understand: **the LLM agent never sees, holds, or manages any private key or signing credential.** The agent is completely stateless with respect to wallet access.

The operator (human) sets up the wallet before the agent runs. The agent inherits access via the MCP server process, which reads credentials from environment variables or config files. If the agent crashes, restarts, or is swapped for a different model, the wallet and funds are unaffected.

**The agent is disposable. The wallet is not.**

***

## 2. Two Authentication Models

Wallet providers fall into two authentication models. The choice determines the entire credential lifecycle.

### Model A: API Key Authentication (Privy)

The operator creates an account at the Privy dashboard, generates API credentials, and puts them in a `.env` file or MCP server config. The MCP server process uses these credentials to request signatures from Privy's TEE (Trusted Execution Environment).

```
Operator (human)
    |
    |--- Creates account at console.privy.io
    |--- Generates API credentials (App ID + App Secret + P-256 auth key)
    |--- Writes credentials to .env or MCP config on agent machine
    |
    v
MCP Server Process (runs alongside agent)
    |
    |--- Reads credentials from environment at startup
    |--- Caches wallet provider as singleton
    |--- On each tool call: sends signing request to Privy API
    |
    v
Privy TEE (TEE + Shamir's Secret Sharing)
    |
    |--- Verifies P-256 authorization key signature
    |--- Evaluates policy rules (contract allowlists, spending limits, etc.)
    |--- Signs transaction inside enclave (key never leaves TEE)
    |--- Returns signed transaction
    |
    v
MCP Server Process
    |
    |--- Broadcasts signed tx to blockchain RPC
    |--- Returns result to agent
```

**Who holds what:**

| Layer           | What                                        | Where It Lives                                       | Who Can Access                                 |
| --------------- | ------------------------------------------- | ---------------------------------------------------- | ---------------------------------------------- |
| Signing key     | Private key that signs transactions         | Inside Privy TEE (Shamir shares + enclave)           | Nobody — not even Privy's employees            |
| P-256 auth key  | Authorization key to request signatures     | `.env` file or secrets manager on operator's machine | MCP server process + operator                  |
| App ID + Secret | API authentication                          | `.env` file or secrets manager                       | MCP server process + operator                  |
| Policy rules    | Constraints on what the TEE will sign       | Privy policy engine (configured via API)             | Operator (sets policy), TEE (evaluates policy) |
| Wallet address  | Public address derived from the signing key | On-chain, public, deterministic                      | Everyone (it's public)                         |

**Production provider:**

| Provider                     | Credential Type                               | Dashboard URL                                | SDK              |
| ---------------------------- | --------------------------------------------- | -------------------------------------------- | ---------------- |
| **Privy** (default and only) | App ID + App Secret + P-256 Authorization Key | [console.privy.io](https://console.privy.io) | `@privy-io/node` |

### Model A-proxy: Portal-Routed Authentication (Privy)

Same Privy TEE signing infrastructure as Model A, but the App Secret is isolated on a Portal server. The MCP server signs requests locally with its P-256 authorization key and routes them through the Portal, which adds the App Secret and forwards to Privy.

```
Operator (human)
    |
    |--- Deploys Portal server with PRIVY_APP_ID + PRIVY_APP_SECRET
    |--- Agent machine has only: P-256 auth key, wallet ID, Portal URL
    |
    v
MCP Server Process (agent machine)
    |
    |--- Builds Privy RPC body for the transaction
    |--- Signs payload: SHA-256(appId + walletId + JSON(rpcBody))
    |--- POST webAppUrl/api/send with {walletId, rpcBody, signature}
    |
    v
Portal Server (operator-controlled)
    |
    |--- Reads PRIVY_APP_ID + PRIVY_APP_SECRET from own environment
    |--- Adds Basic Auth header (appId:appSecret)
    |--- Forwards signature as privy-authorization-signature header
    |--- POST /v1/wallets/{walletId}/rpc → Privy TEE
    |
    v
Privy TEE
    |
    |--- Verifies P-256 authorization signature
    |--- Evaluates policy rules
    |--- Signs transaction inside enclave
    |--- Returns signed transaction → Portal → MCP Server
```

**Who holds what (proxy mode):**

| Layer          | What                                    | Where It Lives                                      | Who Can Access                                 |
| -------------- | --------------------------------------- | --------------------------------------------------- | ---------------------------------------------- |
| Signing key    | Private key that signs transactions     | Inside Privy TEE (Shamir shares + enclave)          | Nobody — not even Privy's employees            |
| P-256 auth key | Authorization key to request signatures | Config file on agent machine                        | MCP server process + operator                  |
| App ID         | API identification (public)             | Portal server env + optionally agent config         | Portal, MCP server, operator                   |
| App Secret     | API authentication (secret)             | Portal server env **only** — never on agent machine | Portal server + operator                       |
| Policy rules   | Constraints on what the TEE will sign   | Privy policy engine (configured via API)            | Operator (sets policy), TEE (evaluates policy) |

**Key security property**: The App Secret never touches the agent machine. Even if the agent machine is fully compromised (P-256 auth key + wallet ID + Portal URL leaked), the attacker cannot sign transactions without also compromising the Portal server (to obtain the App Secret) or reaching the Portal `/api/send` endpoint (which requires the content-bound P-256 signature).

**When to use Model A-proxy:**

* Multiple agents sharing one Portal deployment (App Secret managed once, not per-agent)
* Browser-managed wallet creation via the pairing flow (`gotts setup --pair`)
* Environments where the App Secret must not exist on agent machines (compliance, multi-tenant)

**Trade-off**: Portal must be running for write operations. Read-only MCP tools (data queries, simulations) work without Portal since they don't require signing.

**Implementation**: `packages/wallet/src/wallet.ts:86-121` (routing), `packages/wallet/src/proxy.ts` (`sendViaProxy()`), `packages/core/src/config.ts` (`GottsConfig.webAppUrl`). See [mcp-server/10-wallets.md](/docs/gotts-safe-mcp-server/mcp-server/10-wallets.md) for the full endpoint specs.

### Model A-dev: Local Private Key (dev/testing only)

For local development and CI testing, a raw Ethereum private key in `.env` provides a zero-configuration wallet:

```
Operator (human)
    |
    |--- Generates or uses a well-known test private key
    |--- Writes PRIVATE_KEY=0x... to .env
    |
    v
MCP Server Process
    |
    |--- Reads PRIVATE_KEY from environment at startup
    |--- Uses viem's privateKeyToAccount() for signing
    |--- Signs transactions locally (no TEE, no policy enforcement)
    |--- Broadcasts signed tx to blockchain RPC
```

**Security**: Lowest possible. Key is in memory on the agent machine. No TEE isolation. No policy enforcement. **Never use in production. Never commit .env files containing real private keys.**

Appropriate for: local devenv, CI testing, prototyping. The `@gotts.ai/wallet` package exposes this as `mode: 'local'`.

***

## 3. Wallet Mode Summary

| Mode                             | Key Storage                          | Policy Enforcement                       | TEE              | Use Case              |
| -------------------------------- | ------------------------------------ | ---------------------------------------- | ---------------- | --------------------- |
| **Privy** (default, self-hosted) | Privy TEE (Shamir + enclave)         | Yes (contract + method + amount + chain) | Yes              | All production use    |
| **Privy (proxy)**                | Privy TEE (via Portal)               | Yes (same as self-hosted)                | Yes              | Multi-agent, pairing  |
| **Local key** (`PRIVATE_KEY`)    | In-memory (viem privateKeyToAccount) | No                                       | No               | Dev/testing only      |
| **Smart account** (Safe/ZeroDev) | Privy signer + on-chain enforcement  | On-chain (guards, session keys)          | Via Privy signer | Maximum security      |
| **Lit Protocol** (Vincent)       | Distributed (2/3 node threshold)     | On-chain                                 | No               | Censorship resistance |

***

## 4. Recommended Provider: Privy

After evaluating all providers, **Privy is the default recommendation** for production agents interacting with the vault protocol. Key reasons:

* **Granular policy engine**: Contract allowlists, method restrictions, transfer limits, time-based controls, recipient restrictions, and chain restrictions -- all in a single policy definition. This maps directly to the vault protocol's role-based policies (participant vs manager vs creator).
* **Agent-controlled wallet model**: Model 1 (developer-owned) gives the agent full autonomy within policy constraints. Model 2 (user-owned with agent signers) enables supervised agent patterns.
* **Authorization key quorums**: Multi-party approval for critical operations (policy changes, wallet export). P-256 keys with configurable thresholds.
* **OpenClaw integration**: Official `privy-agentic-wallets-skill` for OpenClaw agents.
* **Multi-chain**: EVM, Solana, and Tier 2 chains.
* **x402 support**: Built-in `useX402Fetch` hook for machine-to-machine payments.
* **Webhooks**: Transaction event webhooks and balance event webhooks for monitoring.

***

## 5. Failure and Recovery Matrix

| What Failed                                                     | Impact                                                            | Recovery                                                                                                                             | Time              |
| --------------------------------------------------------------- | ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | ----------------- |
| **Privy App Secret lost**                                       | Cannot sign new transactions. Existing on-chain state unaffected. | Regenerate at [console.privy.io](https://console.privy.io). Update `.env`. Restart MCP server.                                       | Minutes           |
| **Privy App Secret compromised**                                | Attacker can request signatures within policy limits              | Revoke old secret at dashboard. Generate new one. Policy engine still constrains what can be signed.                                 | Minutes           |
| **P-256 authorization key compromised**                         | Attacker can authorize signing requests                           | Rotate: add new key to wallet's auth quorum, remove old one. No lockout window if threshold-safe.                                    | Minutes           |
| **Local private key compromised** (dev mode)                    | Attacker has full wallet access — no policy limits                | This is why local mode must never hold real funds. Generate new key.                                                                 | Minutes           |
| **Session key compromised** (smart account)                     | Attacker can act within session key's policy scope                | Sudo key disables compromised session key, issues new one. Wallet address unchanged.                                                 | Minutes           |
| **Operational signing key compromised** (smart contract wallet) | Attacker can sign within policy limits                            | Rotate via `setOwner()` or `swapOwner()`. Wallet address unchanged. Reputation preserved.                                            | Minutes           |
| **All signing keys compromised, guardians available**           | Full access compromised                                           | Social recovery via guardian threshold (2-of-3). 48-hour mandatory delay. Wallet address unchanged.                                  | 48 hours          |
| **All keys lost, no guardians configured**                      | **Funds unrecoverable**                                           | None. This is why guardian setup is mandatory for production.                                                                        | Permanent         |
| **Portal server down** (proxy mode)                             | Cannot sign new transactions via proxy agents                     | Restart Portal. Or switch agents to self-hosted mode (add App Secret to agent config). Read-only tools unaffected.                   | Minutes           |
| **Portal server compromised** (proxy mode)                      | Attacker gains App Secret but still needs P-256 auth key to sign  | Rotate App Secret at Privy dashboard. Redeploy Portal. Agent P-256 keys are unaffected — no agent-side changes needed.               | Minutes           |
| **Privy outage**                                                | Cannot sign new transactions                                      | Wallet is a smart contract on-chain. If guardians are configured, they can initiate key rotation. Funds are safe during outage.      | Hours-days        |
| **Identity NFT stolen** (ERC-8004)                              | Attacker inherits reputation tier                                 | Guardian-protected 7-day transfer cooldown. Monitoring bot auto-cancels unauthorized transfers. 30-day reputation decay on transfer. | 7 days (cooldown) |

**Lesson**: The only unrecoverable scenario is losing all keys with no guardians. Every production deployment must configure at least one guardian. See [vault/10-safety.md](/docs/gotts-vaults/vault/10-safety.md) for the full security checklist.

***

## 6. Credential Rotation Playbook

### Privy (Production)

1. **Rotate authorization key**: Generate a new P-256 key pair via `generateKeyPair()` from `@gotts.ai/crypto` or via Privy Dashboard. Add the new key to the wallet's auth key quorum. Verify signing works with the new key. Remove the old key from the quorum. Never remove before adding — would cause a lockout window.
2. **Rotate app secret**: Generate new App Secret in Privy Dashboard ([console.privy.io](https://console.privy.io)). Update `PRIVY_APP_SECRET` in `.env`. Restart MCP server. Old secret is immediately invalid.
3. **Rotate wallet ownership**: If using key quorums with multiple authorization keys, add the new key to the quorum before removing the old one. Threshold-based — no single key rotation causes lockout.

### Local Private Key (Dev Mode)

1. **Regenerate**: Generate a new key with `viem`'s `generatePrivateKey()`. Update `PRIVATE_KEY` in `.env`. Restart MCP server.
2. **If leaked**: Generate a new key immediately. The old key's wallet should be considered fully compromised. Any real funds should have been transferred immediately.
3. **Prevention**: Never use local mode for any wallet that holds real value. Use Privy for all production agents.

### Smart Contract Wallet Key Rotation (Any Provider)

1. **ERC-4337 (ZeroDev Kernel)**: Call `setOwner(newAddress)` via a UserOp signed by the current owner (sudo key). After execution, only the new key's signatures are accepted. Wallet address unchanged.
2. **Safe**: Call `swapOwner(prevOwner, oldOwner, newOwner)` requiring threshold signatures. Atomically replaces a signer.
3. **Impact**: No ERC-721 Transfer event emitted. Identity NFT stays in the wallet. All reputation preserved. Zero downtime.

### ERC-8004 Identity Transfer (Last Resort)

1. Call `DANGER__requestTransfer(tokenId, recipient)` from the current wallet
2. 7-day mandatory cooldown begins
3. Any guardian can veto during cooldown
4. After cooldown, 48-hour execution window
5. **Reputation impact**: Effective reputation drops to near-zero, recovers linearly over 30 days

Prefer key rotation on a smart contract wallet (no reputation impact) over identity transfer (30-day decay).

***

## 6a. SIWE Authentication for Remote MCP (D-085)

When the MCP server is deployed remotely (HTTP/Streamable HTTP transport), connecting agents authenticate via SIWE (Sign-In with Ethereum, EIP-4361). This is a network-level authentication mechanism separate from wallet key management.

**How it works**: The agent signs a structured SIWE message with its wallet, proving ownership of the address. The server verifies the signature, checks the ERC-8004 Identity Registry for agent registration and role metadata, then issues a scoped JWT. The JWT includes `agentId`, `address`, `role`, `tier`, and `scopes`. All subsequent MCP requests include the JWT as a Bearer token.

**What SIWE does NOT do**: SIWE does not grant the server access to the agent's wallet. The agent's signing key stays in its own wallet (Privy enclave or other provider). The server only receives a proof of address ownership -- it cannot initiate transactions on behalf of the agent.

**Local STDIO transport**: No SIWE needed. The agent launched the server -- trust is implicit. Credentials pass via environment variables as described in Sections 2-4.

## 6b. Privy P-256 Authorization Key Architecture (D-081)

When using Privy agentic wallets with the Agent0 SDK, the credential hierarchy is:

| Credential                       | Where It Lives                   | What It Does                                                          |
| -------------------------------- | -------------------------------- | --------------------------------------------------------------------- |
| Ethereum signing key (secp256k1) | Privy secure enclave             | Signs transactions and messages. Never leaves the enclave.            |
| P-256 authorization key          | Server `.env` or secrets manager | Authorizes signing requests to Privy API. Cannot extract signing key. |
| Privy App ID + Secret            | Server `.env` or secrets manager | Authenticates to Privy API. Required alongside authorization key.     |

**EIP-1193 bridge**: The `@gotts.ai/wallet` package provides `createEIP1193Provider(wallet)` as the canonical bridge function between a `GottsWallet` and the Agent0 SDK. Signing operations (`eth_sendTransaction`, `personal_sign`, `eth_signTypedData_v4`) route through the Privy TEE enclave; all other JSON-RPC calls proxy to the configured chain RPC URL. The same P-256 auth key and Privy credentials that sign transactions also power Agent0 SDK registration -- no additional credentials are needed. See [erc8004-integration.md](/docs/prd-shared/erc8004-integration.md) Section 1 for the full spec.

**Security properties**:

* **Signing key isolation**: The Ethereum private key lives in Privy's enclave. The server holds only the P-256 authorization key. Even if the authorization key is compromised, the attacker needs both the key AND access to the Privy API (authenticated by App ID + Secret) to authorize transactions.
* **EIP-712 support**: ERC-8004's `agentWallet` metadata verification requires EIP-712 typed data signatures. Privy's enclave handles this natively via the `eth_signTypedData_v4` method.
* **Key rotation**: The P-256 authorization key can be rotated without changing the wallet address. Generate a new keypair, update the wallet's owner in Privy Dashboard, update `.env`.
* **Deterministic behavior**: Existing wallet path (Path A) is fully deterministic -- same env vars produce identical wallet behavior every run. No local state required.

**Production recommendation**: Store `PRIVY_AUTH_PRIVATE_KEY` in a secrets manager (AWS Secrets Manager, HashiCorp Vault, GCP Secret Manager) rather than `.env` files for remote deployments. The `.privy-wallet.json` file from auto-create Path B should never be committed to version control.

***

## 7. Operator vs Agent Responsibilities

| Responsibility                  | Operator (Human)             | Agent (LLM) | MCP Server Process       |
| ------------------------------- | ---------------------------- | ----------- | ------------------------ |
| Provision wallet at provider    | **Yes**                      | No          | No                       |
| Generate API credentials        | **Yes**                      | No          | No                       |
| Write `.env` / config files     | **Yes**                      | No          | No                       |
| Configure wallet policies       | **Yes**                      | No          | No                       |
| Fund wallet with gas            | **Yes**                      | No          | No                       |
| Set up guardian addresses       | **Yes**                      | No          | No                       |
| Start MCP server                | **Yes**                      | No          | N/A (it *is* the server) |
| Read credentials from env       | No                           | No          | **Yes** (at startup)     |
| Call MCP tools                  | No                           | **Yes**     | Receives calls           |
| Request signatures from TEE     | No                           | No          | **Yes** (on tool calls)  |
| Decide what to trade/LP/deposit | No                           | **Yes**     | Executes decisions       |
| Route transactions via Portal   | **Yes** (deploys Portal)     | No          | **Yes** (proxy mode)     |
| Rotate credentials when needed  | **Yes**                      | No          | No                       |
| Monitor for anomalies           | **Yes** (sets up monitoring) | No          | No                       |

The LLM agent's only job is to call MCP tools. Everything else is the operator's responsibility, mediated by the MCP server process.

***

## 7a. Policy Presets (Normative)

The install wizard ([mcp-server/15-install-wizard.md](/docs/gotts-safe-mcp-server/mcp-server/15-install-wizard.md)) MUST offer exactly these named policy presets for v1. Each preset maps an operator's intent to a concrete tool profile, wallet policy allowlist, spending limits, and emergency cancel authority. Presets reduce setup errors and cognitive load — operators select a persona, not individual policy parameters.

### Preset Definitions

| Preset          | Tool Profile                                                   | Wallet Policy Scope                                                                                                                                           | Daily Limit (default) | Emergency Cancel              |
| --------------- | -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------- | ----------------------------- |
| **Participant** | `vault` (read + deposit/withdraw + share trading if supported) | Vault contracts: `deposit`, `withdraw`, `redeem`; Permit2: `approve`; Share pool: `swap`; Identity Registry (`0x8004...`): `register`                         | $50,000               | Cancel authority key required |
| **Manager**     | `vault` (participant + rebalance/harvest within bounds)        | Participant scope + `rebalance`, `collectFees`, `harvest`, `reportProfit`; RiskEngine: read-only; Identity Registry (`0x8004...`): `register`                 | $100,000              | Cancel authority key required |
| **Creator**     | `vault` (manager + deploy vault templates and hooks)           | Manager scope + Factory: `createVault`; HookMiner + PoolManager: `initialize`; ParameterDecisionTable: `propose`; Identity Registry (`0x8004...`): `register` | $200,000              | Cancel authority key required |

**Identity Registry allowlist note**: The ERC-8004 Identity Registry (`0x8004A818BFB912233c491871b3d84c89A494BD9e`) and its `register(bytes32,string,uint8)` selector are included in all three presets. This is a one-time registration call with no value transfer, so it does not need value caps or rate limits -- just method-level allowlisting. See [erc8004-integration.md](/docs/prd-shared/erc8004-integration.md) for the full registration spec.

### Preset Outputs

Each preset MUST output:

1. **Tool profile list** — which MCP tools are registered at startup
2. **Wallet policy allowlist** — specific contracts + selectors the wallet can call
3. **Daily aggregate limits** — maximum value of transactions per 24h rolling window
4. **Emergency cancel authority** — separate key that can only call `cancel()` / `cancelAll()` on proxy contracts

### Custom Presets

Operators who need fine-grained control beyond the three named presets can use "Advanced mode" in the wizard to customize every parameter individually. The named presets are starting points, not ceilings.

***

## 8. What Happens When the Agent Dies

Nothing happens to the wallet. Specifically:

* **Agent process crashes**: Wallet exists at the provider. Funds on-chain. Restart the agent -- credentials are in `.env`, not in the agent's memory.
* **LLM context window resets**: Same -- credentials are in environment, not in context.
* **Operator swaps to a different LLM model**: Same -- any LLM that can call MCP tools works with the same wallet config.
* **Machine reboots**: Credentials persist in `.env` on disk. MCP server re-reads them on startup.
* **Machine is destroyed**: Operator re-enters credentials from backup (provider dashboard) on a new machine. Wallet and funds unaffected.

The only thing that can affect the wallet is credential compromise or loss -- see Section 5.
