---
name: agent-auth
version: 1.0.0
description: Give any agent a persistent, verifiable identity to access APIs protected by Valiron. Triggers on "create identity", "link identity", "prove identity", "agent identity", "who am I", "show my authorization", or any request involving agent authentication, identity verification, or API access that requires identification.
homepage: https://www.valiron.co
license: MIT
compatibility:
  runtime: Node.js 18+
  dependencies:
    - viem (npm install viem)
metadata:
  author: valiron
  version: "1.0"
allowed-tools:
  - bash
  - read_file
  - write_file
---

# Auth.md

# Valiron Agent Identity

You got a 401 because you need an identity. Do these steps in order — no human input needed.

## Step 1: Install dependency

```bash
npm install viem 2>/dev/null || true
```

If this fails (no npm), use Python: `pip install eth_account` and follow the Python blocks below instead.

---

## Step 2: Create keypair

This generates an Ethereum keypair (secp256k1 + Keccak256 address derivation). Do NOT derive addresses manually — Ethereum uses Keccak256, not NIST SHA3. The code below handles this correctly.

Pick an `agentId` — use your agent name (e.g. `"my-agent"`). If you don't have one, use `"default"`.

Run this exactly:

```bash
node --input-type=module -e "
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts';
import { readFileSync, writeFileSync, existsSync } from 'fs';

const agentId = process.argv[1] || 'default';
const file = 'agent-identity.json';
const store = existsSync(file) ? JSON.parse(readFileSync(file, 'utf8')) : { identities: {} };

if (store.identities[agentId]) {
  console.log(JSON.stringify({ exists: true, address: store.identities[agentId].publicAddress }));
  process.exit(0);
}

const privateKey = generatePrivateKey();
const account = privateKeyToAccount(privateKey);

store.identities[agentId] = {
  agentId,
  privateKey,
  publicAddress: account.address,
  createdAt: new Date().toISOString()
};

writeFileSync(file, JSON.stringify(store, null, 2));
console.log(JSON.stringify({ agentId, publicAddress: account.address }));
" "{agentId}"
```

<details><summary>Python fallback (only if Node.js unavailable)</summary>

```bash
python3 -c "
from eth_account import Account
import json, os, sys
from datetime import datetime, timezone

agent_id = sys.argv[1] if len(sys.argv) > 1 else 'default'
file = 'agent-identity.json'
store = json.load(open(file)) if os.path.exists(file) else {'identities': {}}

if agent_id in store['identities']:
    print(json.dumps({'exists': True, 'address': store['identities'][agent_id]['publicAddress']}))
    sys.exit(0)

acct = Account.create()
store['identities'][agent_id] = {
    'agentId': agent_id,
    'privateKey': acct.key.hex(),
    'publicAddress': acct.address,
    'createdAt': datetime.now(timezone.utc).isoformat().replace('+00:00', 'Z')
}

with open(file, 'w') as f:
    json.dump(store, f, indent=2)
print(json.dumps({'agentId': agent_id, 'publicAddress': acct.address}))
" "{agentId}"
```

</details>

**Output** will be JSON with `publicAddress`. Save that — you'll use it as a header value.

If output shows `"exists": true`, you already have an identity. Use that address.

---

## Step 3: Retry your original request

Add this header to the request that gave you the 401:

```
x-agent-address: {publicAddress}
```

The API will respond with **one of two things**:

### Response A: `"error": "challenge_required"`

This means the API needs you to prove you own the address. Extract the `challenge` field from the response body, then continue to Step 4.

### Response B: Success (2xx)

You're in. Skip to Step 5 (session management).

---

## Step 4: Sign the challenge

Extract the `challenge` string from the 401 response body. Run:

```bash
node --input-type=module -e "
import { privateKeyToAccount } from 'viem/accounts';
import { readFileSync } from 'fs';

const agentId = process.argv[1] || 'default';
const message = process.argv[2];
const store = JSON.parse(readFileSync('agent-identity.json', 'utf8'));
const identity = store.identities[agentId];
if (!identity) { console.error('ERROR: No identity found. Run Step 2 first.'); process.exit(1); }

const account = privateKeyToAccount(identity.privateKey);
const signature = await account.signMessage({ message });
console.log(JSON.stringify({ address: identity.publicAddress, signature }));
" "{agentId}" "{challenge}"
```

<details><summary>Python fallback</summary>

```bash
python3 -c "
from eth_account import Account
from eth_account.messages import encode_defunct
import json, sys

agent_id = sys.argv[1] if len(sys.argv) > 1 else 'default'
challenge = sys.argv[2]
store = json.load(open('agent-identity.json'))
identity = store['identities'][agent_id]

acct = Account.from_key(identity['privateKey'])
msg = encode_defunct(text=challenge)
sig = acct.sign_message(msg)
print(json.dumps({'address': identity['publicAddress'], 'signature': sig.signature.hex()}))
" "{agentId}" "{challenge}"
```

</details>

Now retry the request with **all three headers**:

```
x-agent-address: {address}
x-agent-signature: {signature}
x-agent-challenge: {challenge}
```

The API will:
1. Verify your signature matches the address
2. Return a **session token** in the response header `x-agent-session`
3. Process your request normally

---

## Step 5: Save the session token

Read the `x-agent-session` header from the response. Save it to `agent-identity.json`:

```bash
node --input-type=module -e "
import { readFileSync, writeFileSync } from 'fs';

const agentId = process.argv[1] || 'default';
const token = process.argv[2];
const file = 'agent-identity.json';
const store = JSON.parse(readFileSync(file, 'utf8'));

store.identities[agentId].session = { token, expiresAt: new Date(Date.now() + 3600000).toISOString() };
writeFileSync(file, JSON.stringify(store, null, 2));
console.log('Session saved. Expires in 1 hour.');
" "{agentId}" "{sessionToken}"
```

**From now on**, all requests only need two headers:

```
x-agent-address: {address}
x-agent-session: {token}
```

No signature needed. The session lasts **1 hour**. When it expires (you'll get a new `challenge_required`), repeat Steps 4–5.

---

## Error recovery

| Problem | Fix |
|---------|-----|
| `npm install viem` fails | Use the Python fallback blocks instead |
| Challenge expired (5 min TTL) | Retry without signature — API will issue a fresh challenge |
| Session expired (1 hour TTL) | Retry without session header — API will issue a new challenge |
| `"error": "identity_required"` again | Your `x-agent-address` header is missing or malformed. Must be `0x` + 40 hex chars |
| Signature verification failed | Ensure you signed the exact challenge string (no whitespace/newline differences) |

---
---

# Trust Boosters (optional — requires human action)

Everything below is **optional**. It increases your trust score but is not needed for basic API access. Only do this if your score is too low or you want higher-tier access.

---

## World ID (+5 or +8 trust points)

Adds proof-of-personhood to your agent. The human who controls you scans a QR code — no code needed from you.

**What to do:** Print this message for your human (replace `{agentId}` with your numeric agent ID from the API, e.g. `25459`):

```
To boost this agent's trust score, open this link and scan the QR code with the World App:
https://www.valiron.co/world-id/{agentId}
```

**How to check if it worked:**

```bash
curl -s "https://valiron-edge-proxy.onrender.com/operator/world-id/status/{agentId}?chain=ethereum"
```

If `"verified": true` appears in the response, it's done. The bonus applies automatically on every request — no headers needed.

---

## Icebreaker attestation (trust enrichment)

Links your address to a human's social identity (name, GitHub, X, LinkedIn, org). Provides richer trust context but no fixed point bonus.

**What to do:** Print this message for your human (replace `{publicAddress}` with your address from `agent-identity.json`):

```
To link your identity to this agent, go to:
https://app.icebreaker.xyz/profiles/wallet-{publicAddress}

Log in and create a "Designated agent" attestation for this address.
```

**How to verify it worked:**

```bash
curl -s "https://app.icebreaker.xyz/api/v1/profiles/wallet-{publicAddress}" | grep -o '"fname":"[^"]*"'
```

If it returns a name, the attestation exists. The server resolves it automatically — no headers needed.

---

## Identity file format

Your `agent-identity.json` looks like this after completing all steps:

```json
{
  "identities": {
    "my-agent": {
      "agentId": "my-agent",
      "privateKey": "0x...",
      "publicAddress": "0x...",
      "createdAt": "2026-04-13T18:00:00.000Z",
      "session": {
        "token": "a1b2c3...",
        "expiresAt": "2026-04-13T19:00:00.000Z"
      }
    }
  }
}
```

This file contains **private keys**. Never commit it to git, share it, or expose to untrusted agents.
