Skill: jacopo-eth/erc8128 (version 1.0.0)

--- 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](https://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 ```typescript 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) // Sign and send const response = await client.fetch('https://api.example.com/orders', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ amount: '100' }), }) // Sign only (returns new Request with signature headers) const signedRequest = await client.signRequest('https://api.example.com/orders') ``` ### Verify requests ```typescript import { createVerifierClient } from '@slicekit/erc8128' import type { NonceStore } from '@slicekit/erc8128' import { createPublicClient, http } from 'viem' import { mainnet } from 'viem/chains' // NonceStore interface for replay protection const nonceStore: NonceStore = { consume: async (key: string, ttlSeconds: number): Promise<boolean> => { // Return true if nonce was successfully consumed (first use) // Return false if nonce was already used (replay attempt) } } const publicClient = createPublicClient({ chain: mainnet, transport: http() }) const verifier = createVerifierClient(publicClient.verifyMessage, nonceStore) const result = await verifier.verifyRequest(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](https://erc8128.slice.so/concepts/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](https://erc8128.slice.so/guides/verifying-requests) and [VerifyPolicy](https://erc8128.slice.so/api/types#verifypolicy) for full options. ## CLI: erc8128 curl For CLI usage, see [references/cli.md](references/cli.md). Quick examples: ```bash # GET with keystore erc8128 curl --keystore ./key.json https://api.example.com/data # POST with JSON erc8128 curl -X POST \ -H "Content-Type: application/json" \ -d '{"foo":"bar"}' \ --keyfile ~/.keys/bot.key \ https://api.example.com/submit # Dry run (sign only) erc8128 curl --dry-run -d @body.json --keyfile ~/.keys/bot.key https://api.example.com ``` 📖 See [CLI Guide](https://erc8128.slice.so/guides/cli) for full documentation. ## Common Patterns ### Express middleware ```typescript import { verifyRequest } 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() }) // Implement NonceStore (Redis example) const nonceStore: NonceStore = { consume: async (key, ttlSeconds) => { const result = await redis.set(key, '1', 'EX', ttlSeconds, 'NX') return result === 'OK' } } async function erc8128Auth(req, res, next) { const result = await verifyRequest( toFetchRequest(req), // Convert Express req to Fetch Request publicClient.verifyMessage, nonceStore ) 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) ```typescript 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) // Use client.fetch() for all authenticated requests ``` ### Verify failure reasons ```typescript type VerifyFailReason = | 'missing_headers' | 'label_not_found' | 'bad_signature_input' | 'bad_signature' | 'bad_keyid' | 'bad_time' | 'not_yet_valid' | 'expired' | 'validity_too_long' | 'nonce_required' | 'replayable_not_allowed' | [REDACTED_SECRET] | 'replayable_not_before' | 'replayable_invalidated' | 'class_bound_not_allowed' | 'not_request_bound' | 'nonce_window_too_long' | 'replay' | 'digest_mismatch' | 'digest_required' | 'alg_not_allowed' | 'bad_signature_bytes' | 'bad_signature_check' ``` 📖 See [VerifyFailReason](https://erc8128.slice.so/api/types#verifyfailreason) for descriptions. ## Key Management For agents and automated systems: | Method | Security | Use Case | |--------|----------|----------| | `--keyfile` | Medium | Unencrypted key file, file permissions for protection | | `--keystore` | High | Encrypted JSON keystore, password required | | `ETH_PRIVATE_KEY` | Low | Environment variable, avoid in production | | Signing service | High | Delegate to external service (SIWA, AWAL) | ## Documentation - **Full docs:** [erc8128.slice.so](https://erc8128.slice.so) - **Quick Start:** [erc8128.slice.so/getting-started/quick-start](https://erc8128.slice.so/getting-started/quick-start) - **Concepts:** [erc8128.slice.so/concepts/overview](https://erc8128.slice.so/concepts/overview) - **API Reference:** [erc8128.slice.so/api/signRequest](https://erc8128.slice.so/api/signRequest) - **ERC-8128 Spec:** [GitHub](https://github.com/slice-so/ERCs/blob/d9c6f41183008285a0e9f1af1d2aeac72e7a8fdc/ERCS/erc-8128.md)


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: clean | SkillSpector: clean (LOW)