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

@connectx-sdk/core

v0.4.0

Published

Agent-first, chain-agnostic identity/signing SDK for ANP-02. One operation (sign / register / login) over any wallet configuration — ANP native or web3 (EVM / arbitrary chain).

Readme

connectx

Agent-first, chain-agnostic identity/signing SDK for ANP-02. One operation — sign() / register() / login() — over any wallet configuration: an ANP-native identity, a browser extension, an agent's local CLI key, or an arbitrary-chain web3 wallet.

The key idea: an agent picks a wallet configuration, then does the same thing whether that wallet is EVM, Solana, a browser extension, or ANP native. The SDK doesn't just leave an interface seam for "ANP that happens to work with web3" — it actually implements the layer that frames any { public key, private key, signature } combination into an ANP-02 proof and verifies it back.

In the box: core (framework-agnostic) + react (hooks) + a scaffolding CLI (@connectx-sdk/cli, the connectx binary). Server-side (agent-world) compatibility is a future direction — see Roadmap.

On the spec names — vNext splits authentication from the DID method, so this repo uses both. ANP-02 is the method-agnostic authentication layer (RFC 9421 HTTP Message Signatures + content-digest + nonce challenge + access token) — that is what this SDK implements and what "ANP-02" means below. ANP-03 is the did:wba method rules (key-fingerprint binding, the DID document proof, the stable subject path) — still very much in play, just no longer the name for the auth flow. See ADR-0002 §2.1.


Install

npm install @connectx-sdk/core   # runtime deps: @noble/curves, @noble/hashes

react is an optional peer dependency; import the react layer via @connectx-sdk/core/react.

To scaffold a starter project or drive agent identity from the CLI, install the separate CLI package (see Scaffolding):

npm install -D @connectx-sdk/cli

Core concept

A connector is one wallet configuration. Everything about how that wallet signs is sealed inside the connector; the caller only ever sees getIdentity() and signRequest().

export interface AnpConnector {
  id: string
  name: string
  type: WalletType            // 'anp-extension' | 'anp-dev-shim' | 'anp-cli' | 'ethereum' | 'evm' | 'solana' | 'chain'
  isAvailable(): Promise<boolean>
  getIdentity(): Promise<AnpIdentity>
  signRequest(req: SignRequest): Promise<SignResult>   // pure, no network
}

The uniform wallet interface — the seam that most wallets already satisfy:

export interface IWallet {
  getPublicKey(): Promise<Uint8Array>
  signMessage(message: Uint8Array): Promise<Uint8Array>
  getKeypair?(): Promise<{ publicKey: Uint8Array; privateKey: Uint8Array }>  // agent CLI only
  curve?(): 'ed25519' | 'secp256k1'
}

A browser wallet implements getPublicKey + signMessage. An agent's local CLI key implements getKeypair. The SDK never cares which chain the key belongs to.

Two-layer identity (0.3.0)AnpIdentity carries both halves of an identity:

export interface AnpIdentity {
  subject: SubjectId   // STABLE — survives a key update
  did: string          // the wire credential — carries the key fingerprint
  keyid: string
  publicKeyMultibase: string
  didDocument: Record<string, unknown>
}

A DID embeds the public-key fingerprint, so it changes when the key is updated. The subject does not: subjectOf(did) derives it (did:wba:example.com:user:alice — the same method and path, minus the fingerprint — or a CAIP-10 account id like eip155:1:0x…, which is already a stable identity). Use the subject to store, address and attribute an agent; use the DID to sign. Never put a subject on the wire in place of the DID — vNext explicitly forbids reading "same stable subject path" as "same subject" (that needs the continuity chain, which is Part F).


Usage

1. Build a config with connectors

import {
  createConfig, selectWallet, signRequest, login,
  anpDevShim, anpExtension, anpCli,
  chainWalletConnector, keypairWallet, parsePrivateKey,
} from '@connectx-sdk/core'

const config = createConfig(
  [
    anpExtension(),                        // window.anp (browser)
    anpDevShim('localhost:3000'),          // dev identity (localStorage)
    chainWalletConnector(
      keypairWallet(myPrivateKey, { curve: 'ed25519' }),
      { host: 'localhost:3000', chainId: 'eip155:1' },
    ),
    anpCli(parsePrivateKey(process.env.ANP_PRIVATE_KEY!), { host: 'localhost:3000' }),
  ],
  'anp-dev-shim',                          // default
)

2. Sign a login request (pure, no network)

const connector = selectWallet(config, 'anp-dev-shim')
const result = await signRequest(config, 'anp-dev-shim', {
  url: 'https://host/api/login/anp/verify',
  method: 'POST',
  nonce,
  body: JSON.stringify({ nonce, didDocument: await connector.getIdentity().then(i => i.didDocument) }),
})
// result.headers → content-digest, signature-input, signature

3. Login (handles the 401 re-sign loop)

const outcome = await login(config, {
  connectorId: 'anp-dev-shim',
  nonce,
  targetUri: 'https://host/api/login/anp/verify',
})
// outcome.did, outcome.token (access_token from Authentication-Info or body)

login() does the whole dance: connector → getIdentity() → build body → signRequest() → POST. If the server responds 401 with a WWW-Authenticate: didwba nonce="..." challenge (nonce expired, or you started without one), it re-signs with the server's nonce and re-POSTs, then reads the access_token from Authentication-Info (or the body) into LoginOutcome.token.

4. Register a handle

await register(config, {
  connectorId: 'anp-dev-shim',
  handle: 'alice',
  targetUri: 'https://host/api/register',
  nonce,
})

5. Arbitrary-chain wallet → ANP proof (the real "ANP works with web3")

import { chainWalletConnector, keypairWallet } from '@connectx-sdk/core'
const connector = chainWalletConnector(
  keypairWallet(ed25519.utils.randomSecretKey(), { curve: 'ed25519' }),
  { host: 'localhost:3000' },
)

This frames the wallet's { publicKey, privateKey, signature } into a normal ANP-02 proof. A server verifying the request sees an ordinary ANP-02 signature — it never knows the key came from an EVM/Solana/CLI wallet. cryptoWalletProver is the bridge, and keypairWallet (or an EIP-1193 chainWallet) supplies the key material.


API surface

权威清单见 src/core/index.ts(主入口,显式列出)与 src/advanced.ts@connectx-sdk/core/advanced,底层字节/编码/签名原语)。下表是概览; 底层原语(signAnpLoginRequestbuildContentDigestValueutf8base58*encodeMultibase*、EVM 签名恢复等)从 @connectx-sdk/core/advanced 导入,不在主入口。

| Export | Purpose | |---|---| | createConfig(connectors, defaultId?) | Configure the SDK with connectors. | | selectWallet(config, id?) | Pick a connector (by id or the default). | | signRequest(config, id, req) | Pure signing; no network. | | register(config, opts) | Sign + POST a registration request. | | login(config, opts) | Full login incl. 401-challenge re-sign + token extraction. | | connectors.anpExtension() | window.anp wallet extension. | | connectors.anpDevShim(host, opts?) | Local dev identity; memory-only by default, opts.persist opts into localStorage. | | connectors.anpCli(source, opts) | Agent local-key path (source = IWallet or a parsed key), opts.host required. | | protocol.chainWalletConnector(wallet, opts) | Frame an IWallet as an ANP-02 connector. | | protocol.keypairWallet(priv, opts) | Local keypair wallet (agent CLI key). | | crypto.verifyHttpMessageSignature(...) | Verify a signed HTTP request (RFC 9421). | | crypto.buildContentDigestValue(...) | sha-256=:...: Content-Digest value. | | crypto.createAnpIdentity(host) | Deterministic Ed25519 identity + inline DID doc. | | subjectOf(did) | Stable identity of a DID — the half that survives a key update, in DID shape (did:wba:example.com:user:alice, the did:web:… DID itself, or a CAIP-10 account id). | | stableSubjectId(did) | The naked ANP-03 §2.2.3 stable subject path (example.com:user:alice) — exactly what a 409 did_superseded body reports. | | crypto.signAnpLoginRequest(...) | Sign an ANP-02 login request (official branch). | | crypto.resolveVerificationKey(keyid, doc?) | Resolve a DID doc's verification key. | | crypto.resolveDidDocument(did, opts?) | Resolve a DID to its whole document (+ optional documentStore cache). | | crypto.verifyDidDocument(doc, did, opts?) | Check a document you already hold: id binding + top-level proof. | | crypto.createDidDocumentStore() | In-memory DidDocumentStore for resolveDidDocument's opt-in cache. | | resolveDidChain(did, opts?) | Walk a did:wba update chain to the current DID, with a per-hop assurance grade (§2.5). Fails closed: a broken chain throws did_chain_broken rather than returning a shorter one. | | createDidChainEdgeStore() | In-memory verified-edge store for resolveDidChain — conflict detection and evidence, not a way to skip re-verification. | | login(opts.didChain, opts.onDidSuperseded) | Reactive 409 did_superseded retry (§3.2). Both must be set or no request is issued on a 409; the signer you hand back must be the DID our own chain walk verified, never the peer's currentDid hint. | | advanced.signDidDocumentProofSync(...) | Sign a whole DID document — the retirement statement (deactivated: true + successorDid) an update is made of, signed by the old key. /advanced entry; before 0.3.0 no entry exposed it, so no external client could produce a chain hop at all. | | advanced.verifyDidDocumentProofWithKey(doc, key) | Verify a document's proof against an explicitly supplied key. An update must use this (the key recorded at login), never verifyDidDocumentProof, which reads the key out of the document it is checking. | | defineConfig(config) | Declarative config helper for connectx project generate (see Scaffolding). |

The react.ts module (import via @connectx-sdk/core/react) exports AnpProvider, useStatus, useAccount, useConnect, useDisconnect, and useLogin — the connection store + hooks that wrap the core operations for React.


Scaffolding

The @connectx-sdk/cli package ships a single connectx binary that scaffolds starter projects and drives agent identity — a separate package from the @connectx-sdk/core SDK library, so the runtime library stays dependency-free for the browser. Commands:

| Command | What it does | |---|---| | connectx project init [name] | Interactive wizard (or --template flags) that scaffolds a working project. | | connectx project generate | Read connectx.config.ts and emit a typed connectx.generated.ts. | | connectx did … | The DID server half — create a key, show the stored identity, list, register a handle, resolve, update. The host and the DID arrive with did register — a did:wba DID names the server hosting it. DID protocol only, so no page there takes a wallet. | | connectx anp … | The ANP server half — show the identity, log in to a site, sign a request. Takes a stored key or an EIP-1193 provider. |

connectx project init

npx @connectx-sdk/cli project init         # interactive
npx @connectx-sdk/cli project init my-app --template node-agent --host localhost --connectors cli --no-install

Or with the binary installed globally (npm i -g @connectx-sdk/cli), drop the npx @connectx-sdk/cli prefix and run connectx project init.

Templates: react-vite (React + Vite), nextjs (App Router), node-agent (headless agent script), vanilla (plain TS). Non-interactive flags (--template --host --login-uri --connectors --pm --no-install) make it fully scriptable for CI / agents.

connectx project generate

connectx.config.ts is a declarative description of your connectors, typed by defineConfig:

// connectx.config.ts
import { defineConfig } from '@connectx-sdk/core'
export default defineConfig({
  host: 'example.com',
  connectors: { extension: true, devShim: { persist: true } },
  login: { targetUri: 'https://example.com/api/login/anp/verify' },
})

connectx project generate loads it (via jiti) and emits connectx.generated.ts — a typed config plus loginAs<Connector> / signRequestAs<Connector> / registerAs<Connector> wrappers keyed by connector id, so callers stop passing string connector ids:

import { config, loginAsExtension } from './connectx.generated'
const outcome = await loginAsExtension({ nonce })

Architecture

src/
  index.ts             # core barrel (agent-facing API)
  react.ts             # react layer: AnpProvider + hooks (useStatus/useAccount/useConnect/useLogin)
  core/
    types.ts           # AnpConnector · AnpConfig · SignRequest/Result · LoginOutcome · WalletType
    config.ts          # createConfig + selectWallet
    actions.ts         # signRequest() · register() · login() (uniform ops)
    crypto/            # isomorphic primitives (browser + Node 22+)
      signatureBase · multicodec · encoding · base58 · signer · verify · resolveDid
    wallet/            # chain-agnostic wallet interface
      wallet.ts        # IWallet: getPublicKey / signMessage / getKeypair?
      eip1193.ts       # reference connector: window.ethereum personal_sign
    protocol/          # protocol framing (proof abstraction + ANP-native + chain adapter)
      proof.ts         # Proof / ProofAdapter / WalletProver
      anp.ts           # official ANP-02 proof (RFC 9421) + buildWalletIdentity + cryptoWalletProver
      adapters/chain.ts# frame { public key + signature } into an ANP proof (chain-agnostic)
    connectors/
      anpExtension.ts  # reads window.anp
      anpDevShim.ts    # localStorage identity
      anpCli.ts        # agent local-key path (key file / mnemonic / keystore / PEM)

Design principles

  1. One operation, agent's view. sign() / register() / login() are uniform; no parameter or return branches on "is this ANP or web3". Wallet selection = one createConfig + selectWallet(), after which every wallet flows identically.
  2. Connector = one wallet configuration. AnpConnector is the abstraction; wallet-specific signing is sealed inside. anpExtension, anpDevShim, anpCli, and wallet.ethereum()-style connectors all expose the same three methods.
  3. Chain-agnostic wallet interface. Only public key + private key + signature are required. No chain-specific address recovery (ecrecover) — the SDK relies on generic signature verification.
  4. “ANP composes with web3” is real, not a seam. protocol/adapters/chain.ts genuinely frames any { public key + signature } into an ANP-02 proof and verifies it. It's not a reserved interface.
  5. Client-focused, wagmi-like. The SDK exports isomorphic verification primitives; session management is the app/server's job.

Verification

npm run typecheck      # tsc --noEmit, clean
npm run build          # tsup → dist/ + .d.ts / .d.cts (ESM + CJS)
npm test               # vitest, jsdom (full suite incl. react)
npm run test:node      # core tests under the `node` environment (proves isomorphism)
npm run test:types     # tsd — signature assertions on the public .d.ts
npm run check:types    # attw — package type-resolution correctness (node16 + bundler)
npm run check:browser  # scan dist ESM entries for Node-only APIs
npm run build:cli      # build @connectx-sdk/cli → packages/cli/dist/cli.js
npm run test:cli       # vitest — CLI codegen/validation unit tests
node scripts/cli-e2e.mjs   # CLI login vs a mock server (401 re-sign + token)
node scripts/demo-e2e.mjs  # scaffolded demo vs a live agent-on (login+register closure)

The SDK is self-consistent: signs with the official ANP-02 branch and verifies with its own verifyHttpMessageSignature. A smoke test signs a request with the official branch and verifies it back — the closed loop must PASS.

The dual ESM/CJS package ships correct types for both: exports nests types under import (.d.ts) and require (.d.cts), so CJS consumers no longer "masquerade as ESM". Consumers must use a modern module resolution — bundler, node16, or nodenext; legacy node10 (moduleResolution: "node") cannot resolve the @connectx-sdk/core/react / @connectx-sdk/core/advanced subpaths (an inherent limit of exports-based packages).


Roadmap

Future directions, not implemented here:

  • Signature proxy (A3.1). Let an agent never touch private-key bytes by delegating signing to a separate local process.
  • Identity registration (A3.2). Bind identity creation to a site's registration flow and record the registered state in the store.
  • MCP server (A1.4). Wrap the CLI as an MCP tool surface for agents that speak MCP instead of shell.
  • DID restore (restore()). The provider_asserted tier (the "lost my old key, restore via the provider" path, ADR-0002 §2.4) is already modelled and enforced by resolveDidChain; what's missing is the server-side endpoint, which vNext deliberately leaves to the server. restore() will be a thin adapter over resolveDidChain once that shape is agreed — writing one now would freeze a guessed protocol into the public contract.

create-connectx / npm create connectx is not planned — the first command stays npx @connectx-sdk/cli init. See todos/TODO2.md for the reasoning.


Reference server: agent-on

A working ANP-02 server lives outside this repo at ../agent-on (a Next.js app). It consumes the SDK's server-side verification (resolveVerificationKey + verifyHttpMessageSignature) and shares the token design the SDK CLI expects:

  • Agent login returns a token. After login()/register() the SDK reads the server's Authentication-Info: access_token="<jwt>" header (or { token } body) into LoginOutcome.token. It's an HS256 JWT (sub = the agent's full DID, TTL 7d) — the server stores only its SHA-256 hash.
  • Use that token as a Bearer credential. Subsequent agent requests carry Authorization: Bearer <token>; agent-on resolves it back to the identity (e.g. its /api/me returns { identity: { did } }). Unauthenticated or forged tokens are rejected with 401 + WWW-Authenticate: Bearer realm="agent-on".
  • agent-on is a separate project (not part of connectx); the demo is a connectx project init --template node-agent project pointed at it, so "agent login → token → Bearer" is exercised end to end.
  • The DID-server role is gone (TODO7 9.10). agent-on used to serve the did.json of every DID it onboarded — a hosted did:wba is only usable if resolvers can fetch its document, and a retired DID's document is what made an update chain walkable — and to publish chain hops at POST /api/did/rotate (a retirement document signed by the old key plus the successor's active document; no session needed, the retirement proof is the authorization, and the account followed the key so the stable subject survived the update). Both are removed, along with the 409 did_superseded branches that went with them: hosting DIDs is a DID server's job, not an application server's. The SDK consumes that contract (login(opts.didChain, opts.onDidSuperseded)); it does not require this server to implement it.

Not in scope

  • Modifying agent-world (its components, routes, server, or localStorage keys).
  • Server session management (mintNonce / cookie / consumeNonce store) — the SDK only exports verification primitives.
  • No ethers / chain-specific ecrecover; only @noble/curves.

License

MIT