npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@slicekit/erc8128

v0.4.1

Published

Minimal ERC-8128 implementation for signing and verifying HTTP requests with Ethereum accounts

Readme

@slicekit/erc8128

Sign and verify HTTP requests with Ethereum accounts using ERC-8128. The package implements the base profile and supports the companion delegated-authentication draft on top of RFC 9421, RFC 9530, and RFC 9651.

Features

  • Fetch-native — Works with standard Request, Response, and fetch APIs in browsers, workers, Node.js, Bun, and Deno.
  • Secure defaults — Request-bound, non-replayable signatures with a generated nonce and a 60-second validity window.
  • Universal accounts — Supports EOAs, deployed ERC-1271 accounts, and counterfactual ERC-6492 accounts.
  • Delegated principals — Supports the draft recursive, attenuated EIP-712 delegation chain with audience, validity, permission, and revocation constraints.
  • Standards-compliant HTTP — Uses HTTP Message Signatures, Content-Digest, and Structured Fields.

Installation

npm install @slicekit/erc8128

Quick start

Sign a request

Create an EthHttpSigner from an Ethereum account and use the signer client to sign or send requests.

import { createSignerClient } from "@slicekit/erc8128"
import { privateKeyToAccount } from "viem/accounts"

const account = privateKeyToAccount("0x...")
const signer = {
  address: account.address,
  chainId: 1,
  signMessage: (message: Uint8Array) =>
    account.signMessage({ message: { raw: message } })
}

const client = createSignerClient(signer)

const signed = await client.signRequest("https://api.example.com/orders", {
  method: "POST",
  headers: { "content-type": "application/json" },
  body: JSON.stringify({ amount: "100" })
})

// Or sign and send in one operation:
const response = await client.fetch("https://api.example.com/orders", {
  method: "POST",
  headers: { "content-type": "application/json" },
  body: JSON.stringify({ amount: "100" })
})

The signer adds Signature-Input, Signature, and, when the request has content, a verified Content-Digest header.

Verify a request

Bind a message verifier and an atomic nonce store once, then verify each incoming request.

import {
  BoundedMemoryNonceStore,
  createVerifierClient
} from "@slicekit/erc8128"
import { createPublicClient, http } from "viem"
import { mainnet } from "viem/chains"

const publicClient = createPublicClient({
  chain: mainnet,
  transport: http()
})

const verifier = createVerifierClient({
  nonceStore: new BoundedMemoryNonceStore(),
  verifyMessage: ({ address, message, signature }) =>
    publicClient.verifyMessage({ address, message, signature })
})

const result = await verifier.verifyRequest({ request })

if (result.ok) {
  console.log(result.principal.address, result.principal.chainId)
} else {
  console.error(result.reason)
}

BoundedMemoryNonceStore is intended for bounded single-process use. Use createRedisNonceStore, createUniqueInsertNonceStore, or another atomic persistent implementation in distributed production deployments.

Core API

createSignerClient(signer, options?)

Creates a reusable signing client.

const client = createSignerClient(signer, {
  ttlSeconds: 30,
  contentDigest: "auto"
})

await client.signRequest(input, init, options)
await client.fetch(input, init, options)
client.setServerConfig(origin, discoveryDocument)

createVerifierClient(config)

Creates a reusable verifier with bound cryptographic and replay-protection dependencies.

const verifier = createVerifierClient({
  verifyMessage,
  verifyDigest,
  nonceStore,
  defaults: {
    maxValiditySec: 120,
    replayable: false
  }
})

verifyDigest is required when accepting delegated EIP-712 grant proofs.

verifyRequest(args)

Verifies one signed request. A successful direct result identifies the same account as principal and signer; a delegated result identifies the initial issuer as principal, the leaf delegate as signer, and includes the ordered delegationIds.

type VerifyResult =
  | {
      ok: true
      principal: { address: Address; chainId: number }
      signer: { address: Address; chainId: number }
      delegated: boolean
      replay: "non-replayable" | "replayable"
      binding: "request-bound" | "class-bound"
    }
  | { ok: false; reason: VerifyFailReason; detail?: string }

Universal account verification

createUniversalAccountVerifier and createUniversalAccountDigestVerifier classify an account by its onchain code:

  1. ERC-6492 signatures are sent to the supplied smart-account verifier.
  2. Code-bearing accounts are verified through ERC-1271.
  3. Code-free accounts use strict EOA recovery.

The built-in EOA path requires canonical secp256k1 public-key recovery and Keccak-256, which are not available through WebCrypto's SubtleCrypto API.

Delegated requests

Build and sign an EIP-712 grant, create a chain, then give the leaf signer a delegated client.

import {
  buildDelegationGrant,
  completeDelegationGrant,
  createDelegatedSignerClient,
  createDelegationChain
} from "@slicekit/erc8128"

const prepared = buildDelegationGrant({
  issuer,
  delegate,
  audiences: ["https://api.example.com"],
  id,
  epoch: 0,
  validUntil,
  maxRequestValiditySeconds: 60,
  delegateIsEOA: true,
  requireNonReplayable: true,
  permissions: ["orders:read"]
})

const link = completeDelegationGrant(
  prepared,
  await issuerSigner.signTypedData(prepared.typedData)
)
const chain = createDelegationChain([link])
const delegated = createDelegatedSignerClient(delegateSigner, chain)

const request = await delegated.signRequest(
  "https://api.example.com/orders"
)

Delegated support tracks the companion draft, whose canonical registry address and runtime code hash are still unassigned. This package pins the Slice reference-deployment candidate and must not be presented as conforming to the delegated extension until the draft assigns those values.

The delegated draft currently says clients must reconstruct and re-sign redirects. This package intentionally does not automatically sign a redirect target because a server-controlled redirect must not authorize a new wallet signature over a different origin, method, path, or body. The signer signs only the caller-supplied request; callers may explicitly inspect a redirect and construct a separate signed request.

Verifiers opt in through policy.delegation, supply a batch verifyStatuses callback, and may require permissions. HTTP loopback audiences are rejected by default; local development must opt in at grant creation and at the parsing, signing, or verification boundary that consumes the grant. Deterministic EIP-712 hashing and CBOR serialization are independent of that runtime transport policy.

Options

Signing options

| Option | Type | Default | Description | | --- | --- | --- | --- | | binding | "request-bound" \| "class-bound" | "request-bound" | Select the component coverage posture. | | nonce | string \| function \| null | generated | A nonce makes the request non-replayable; null deliberately omits it. | | ttlSeconds | number | 60 | Maximum interval between created and expires. | | created / expires | number | current time / TTL | Explicit Unix timestamps. | | label | string | "request" | HTTP Message Signature label. | | contentDigest | "auto" \| "recompute" \| "require" \| "off" | "auto" | Content-Digest handling. | | components | CoveredComponent[] | profile floor | Additional or explicit covered components. |

The request-bound floor always covers @scheme, @authority, @method, @path, and @query; @query has the value ? when the request has no query. Non-empty content requires Content-Digest; whenever Content-Digest or Content-Type is present, it must be covered, and a received Content-Digest must be verified.

Verification policy

| Option | Type | Default | Description | | --- | --- | --- | --- | | principal | "direct" \| "delegated" \| "either" | "either" | Accepted principal class. | | accountVerification | "universal" \| "eoa-only" | "universal" | Base-profile account verification mode. | | maxValiditySec | number | 300 | Maximum accepted signature validity window. | | clockSkewSec | number | 30 | Allowed clock uncertainty in seconds. | | replayable | boolean | false | Whether nonce-less signatures are accepted. | | additionalRequestBoundComponents | CoveredComponent[] | none | Unconditional coverage requirements added to request-bound and class-bound policies. | | requiredCoveredHeadersWhenPresent | CoveredComponent[] | none | Request headers that must be covered whenever they are present. | | classBoundPolicies | CoveredComponent[] \| CoveredComponent[][] | disabled | Accepted class-bound component policies. | | delegation | DelegationPolicy | disabled | Delegation, permissions, proof-cache, and revocation policy. |

Nonce stores

Non-replayable verification requires an atomic consume operation:

interface NonceStore {
  consume(key: string, ttlSeconds: number): Promise<boolean>
}

It must return true exactly once for a new key and false for every reuse until the TTL expires.

Documentation

Full guides, API reference, protocol details, and the CLI are available at erc8128.org.

License

MIT