| name | erc8128 |
| description | Sign and verify HTTP requests with Ethereum wallets using ERC-8128. Use when building authenticated APIs that need wallet-based auth, making signed requests to ERC-8128 endpoints, implementing request verification in servers, or working with agent-to-server authentication. Covers both the @slicekit/erc8128 JS library and the erc8128 CLI. |
ERC-8128: Ethereum HTTP Signatures
ERC-8128 extends RFC 9421 (HTTP Message Signatures) with Ethereum wallet signing. It enables HTTP authentication using existing Ethereum keys—no new credentials needed.
📚 Full documentation: erc8128.slice.so
When to Use
- API authentication — Wallets already onchain can authenticate to your backend
- Agent auth — Bots and agents sign requests with their operational keys
- Replay protection — Signatures include nonces and expiration
- Request integrity — Sign URL, method, headers, and body
Packages
| Package | Purpose |
|---|
@slicekit/erc8128 | JS library for signing and verifying |
@slicekit/erc8128-cli | CLI for signed requests (erc8128 curl) |
Library: @slicekit/erc8128
Sign requests
import { createSignerClient } from '@slicekit/erc8128'
import type { EthHttpSigner } from '@slicekit/erc8128'
import { privateKeyToAccount } from 'viem/accounts'
const account = privateKeyToAccount('0x...')
const signer: EthHttpSigner = {
chainId: 1,
address: account.address,
signMessage: async (msg) => account.signMessage({ message: { raw: msg } }),
}
const client = createSignerClient(signer)
const response = await client.fetch('https://api.example.com/orders', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ amount: '100' }),
})
const signedRequest = await client.signRequest('https://api.example.com/orders')
Verify requests
import { createVerifierClient } from '@slicekit/erc8128'
import type { NonceStore } from '@slicekit/erc8128'
import { createPublicClient, http } from 'viem'
import { mainnet } from 'viem/chains'
const nonceStore: NonceStore = {
consume: async (key: string, ttlSeconds: number): Promise<boolean> => {
}
}
const publicClient = createPublicClient({ chain: mainnet, transport: http() })
const verifier = createVerifierClient({
verifyMessage: publicClient.verifyMessage,
nonceStore
})
const result = await verifier.verifyRequest({ request: request })
if (result.ok) {
console.log(`Authenticated: ${result.address} on chain ${result.chainId}`)
} else {
console.log(`Failed: ${result.reason}`)
}
Sign options
| Option | Type | Default | Description |
|---|
binding | "request-bound" | "class-bound" | "request-bound" | What to sign |
replay | "non-replayable" | "replayable" | "non-replayable" | Include nonce |
ttlSeconds | number | 60 | Signature validity |
components | string[] | — | Additional components to sign |
contentDigest | "auto" | "recompute" | "require" | "off" | "auto" | Content-Digest handling |
request-bound: Signs @authority, @method, @path, @query (if present), and content-digest (if body present). Each request is unique.
class-bound: Signs only the components you explicitly specify. Reusable across similar requests. Requires components array.
📖 See Request Binding for details.
Verify policy
| Option | Type | Default | Description |
|---|
maxValiditySec | number | 300 | Max allowed TTL |
clockSkewSec | number | 0 | Allowed clock drift |
replayable | boolean | false | Allow nonce-less signatures |
classBoundPolicies | string[] | string[][] | — | Accepted class-bound component sets |
📖 See Verifying Requests and VerifyPolicy for full options.
CLI: erc8128 curl
For CLI usage, see references/cli.md.
Quick examples:
erc8128 curl --keystore ./key.json https://api.example.com/data
erc8128 curl -X POST \
-H "Content-Type: application/json" \
-d '{"foo":"bar"}' \
--keyfile ~/.keys/bot.key \
https://api.example.com/submit
erc8128 curl --dry-run -d @body.json --keyfile ~/.keys/bot.key https://api.example.com
📖 See CLI Guide for full documentation.
Common Patterns
Express middleware
import { createVerifierClient } from '@slicekit/erc8128'
import type { NonceStore } from '@slicekit/erc8128'
import { createPublicClient, http } from 'viem'
import { mainnet } from 'viem/chains'
const publicClient = createPublicClient({ chain: mainnet, transport: http() })
const nonceStore: NonceStore = {
consume: async (key, ttlSeconds) => {
const result = await redis.set(key, '1', 'EX', ttlSeconds, 'NX')
return result === 'OK'
}
}
const verifier = createVerifierClient({
verifyMessage: publicClient.verifyMessage,
nonceStore
})
async function erc8128Auth(req, res, next) {
const fetchReq = toFetchRequest(req)
const result = await verifier.verifyRequest({ request: fetchReq })
if (!result.ok) {
return res.status(401).json({ error: result.reason })
}
req.auth = { address: result.address, chainId: result.chainId }
next()
}
Agent signing (with key file)
import { createSignerClient } from '@slicekit/erc8128'
import type { EthHttpSigner } from '@slicekit/erc8128'
import { privateKeyToAccount } from 'viem/accounts'
import { readFileSync } from 'fs'
const key = readFileSync(process.env.KEYFILE, 'utf8').trim()
const account = privateKeyToAccount(key as `0x${string}`)
const signer: EthHttpSigner = {
chainId: Number(process.env.CHAIN_ID) || 1,
address: account.address,
signMessage: async (msg) => account.signMessage({ message: { raw: msg } }),
}
const client = createSignerClient(signer)
Common verify failure reasons
| Reason | Meaning |
|---|
missing_headers | Required Signature / Signature-Input headers not found |
expired | Signature TTL has elapsed |
replay | Nonce already consumed (replay attempt) |
bad_keyid | keyid doesn't match erc8128:<chainId>:<address> format |
bad_signature_check | Signature doesn't match the claimed address |
digest_mismatch | Body was modified after signing |
📖 See VerifyFailReason for the full list of 23 failure reasons.
Key Management
For agents and automated systems:
| Method | Security | Use Case |
|---|
--keystore --interactive | High | Encrypted JSON keystore, password prompted interactively |
--keystore --password | High | Encrypted JSON keystore, password via flag or ETH_KEYSTORE_PASSWORD env |
--keyfile | Medium | Unencrypted key file, file permissions for protection |
ETH_PRIVATE_KEY | Low | Environment variable, avoid in production |
| Signing service | High | Delegate to external service (SIWA, AWAL) |
Documentation