@slicekit/id
v0.1.2
Published
Self-custodial Slice ID sessions, the Slice Wagmi connector, and ERC-8128 request verification
Downloads
647
Readme
@slicekit/id
Self-custodial passkey wallet authentication with draft ERC-8128 EIP-712 delegated sessions, exact audiences, registry-backed revocation, and standard permissions. Sessions replace bearer tokens: every API request is signed, the wallet signs exactly the delegation it displays, and servers verify against finalized onchain state.
Installation
npm install @slicekit/id wagmi @wagmi/core react @tanstack/react-queryQuick Start
import { createAuth } from "@slicekit/id/next"
export const auth = createAuth({ secret: process.env.AUTH_SECRET! })Production authorization defaults to Ethereum mainnet, including in the
Slice-owned apps. Integrators can explicitly select Base with
chainId: 8453.
Local application development
Applications served from an exact http://localhost[:port] or
http://127.0.0.1[:port] origin can use the hosted https://id.slice.so
wallet ceremony and production authorization infrastructure. Keep a supported
production wallet chain in the connector; local application development does
not require Anvil or a locally hosted Slice ID service.
export const idClient = createAuthClient()
export const authenticatedConnector = sliceId({
auth: idClient,
chainIds: [8453]
})The server derives the exact loopback application origin from the request and
the resulting ERC-8128 delegation remains bound to that origin. The
development option on createAuth is reserved for a fully local Slice API,
ID registry, and Anvil authorization chain.
When running that full local stack, pass the same explicit ID origin to both the connector and React wallet layer rather than relying on a fixed port:
const idOrigin = "http://localhost:4103"
sliceId({
auth: idClient,
chainIds: [31337],
idOrigin,
transports: {
31337: {
bundlerUrl: "http://localhost:4101/api/bundler",
rpcUrl: "http://localhost:8545"
}
}
})
<SliceIdProvider client={idClient} wallet={{ idOrigin }}>
{children}
</SliceIdProvider>Entry Points
@slicekit/id:createAuthClientfor the browser sign-in flow.@slicekit/id/react: the client provider, Wallet React bridge and lifecycle, session hooks, and Wagmi permission hooks. This is the only Slice package that publishes the Wallet React integration.@slicekit/id/next: the framework lifecycle (createAuth).@slicekit/id/wagmi: the single Slice Wagmi connector and permission actions.@slicekit/id/typesand@slicekit/id-primitives/types: type-only entry points with no runtime module.
Server-side verification, middleware, and session primitives live in
@slicekit/id-primitives/server, and the delegation
primitives in @slicekit/id-primitives.
Wagmi connector
sliceId() is the only app-facing Slice connector. Without auth, it is a
wallet-only connection: account discovery, chain switching, signing, EIP-1193,
EIP-5792 calls, and optional onchain permission grants do not call application
authentication endpoints.
import { createConfig, http } from "@wagmi/core"
import { sliceId } from "@slicekit/id/wagmi"
import { base } from "wagmi/chains"
export const walletConfig = createConfig({
chains: [base],
connectors: [sliceId({ chainIds: [base.id] })],
transports: { [base.id]: http() }
})Pass a SliceIdClient explicitly to add the app session lifecycle to the same
wallet connection. A fresh connection prepares the server session, carries it
through the trusted wallet ceremony, completes it, and commits the client
session. Wagmi reconnection reads the existing wallet account without starting
a new ceremony; SliceIdProvider hydrates the app session separately.
import { createAuthClient } from "@slicekit/id/react"
import { sliceId } from "@slicekit/id/wagmi"
export const idClient = createAuthClient()
export const authenticatedConnector = sliceId({
auth: idClient,
chainIds: [8453]
})Both modes use the same connector and the same low-level Wallet provider. Authentication is a Slice ID concern: ID prepares an opaque ceremony extension, validates that the returned delegation matches that preparation, then completes and commits the application session. Wallet has no auth option, auth RPC method, React peer dependency, or Wagmi export.
Authenticated mode requires the application's server lifecycle. The default
client calls /api/auth/prepare, /api/auth/complete, /api/auth/session,
/api/auth/revocation, and /api/auth/sign-out; applications may instead pass
their server actions through createAuthClient({ actions }). External wallet
connectors continue to authenticate with client.signInWithWallet(...).
Wagmi-bound permission actions are exported from @slicekit/id/wagmi, and the
matching TanStack Query hooks are exported from @slicekit/id/react. The
provider/Viem permission builders and actions remain in
@slicekit/wallet/permissions.
Sessions and Delegation
The sealed session contains exactly { delegation, privateKey }. Its one-link
delegation fixes the issuer, EOA delegate, exact audiences, random revocation
ID, registry epoch, validity interval, maximum request validity, single-use
posture, component floor, and standard permissions. The wallet shows and signs
this exact EIP-712 value. Signed audiences are the destination authorization.
A local signer refuses destinations outside the audience set and sends only
one delegated request signature.
The delegated ERC draft has not yet assigned its canonical registry address
and runtime code hash. Slice pins a candidate CREATE2 deployment for its
reference implementation; every grant selects that deployment on the EIP-155
chain encoded in its signed issuer, verifiers check the runtime hash before
reading status, and verification fails closed when the deployment or chain
RPC is unavailable. Ethereum mainnet is preferred, Base is supported, and
local development uses the same runtime on Anvil. This candidate profile must
not be presented as final conformance to the delegated extension until the
draft assigns the canonical values.
Verification
Verification defaults to principal: "delegated". Middleware can explicitly
select principal: "direct" | "delegated" | "either"; here, direct is the
application policy name for a base ERC-8128 principal. Routes can require the
standard Slice ID permissions with exact AND semantics. Missing permission
returns 403; invalid authentication returns 401; unavailable root or
revocation verification returns 503. Requesting-origin metadata is available
for display but never authorizes a route.
Caching
Successful delegation grant proofs use a bounded in-memory cache with a
60-second default TTL. Configure caches.delegationProofTtlSeconds to shorten
that window or set it to 0 to bypass proof-cache reads and writes. The
configured TTL bounds how long an SCA state change can remain hidden by a
cached positive proof; registry revocation and epoch status remain subject to
their separate bounded cache policy.
Revocation and Finality
Revocation checks use finalized chain reads so short reorgs cannot turn a
provisional observation into authorization. A revocation or epoch advance
therefore takes effect for verification only after the containing block is
finalized. Issuance reads the latest epoch before signing; immediately after
an epoch advance, a new grant can carry that latest epoch while finalized
verification still observes the preceding epoch and rejects the grant until
finality catches up. status: "fresh" bypasses the short application cache
but still reads finalized chain state, so it does not bypass finality lag.
Status lists and revocation finalization use the same finalized observation
as request verification. Local Anvil uses one slot per epoch, making this
delay two blocks (about two seconds at its configured one-second block time).
Pending checks are coalesced into one tolerant multicall per configured RPC endpoint and chain; quorum reads use one such batch per endpoint. Valid and denied observations are cached only for the configured bounded status TTL. Request verification also has a bounded account verification budget, defaulting to two calls plus the allowed delegation-chain depth, so a request cannot trigger unbounded RPC work.
