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

@antseed/web-sdk

v0.1.6

Published

Browser buyer SDK for AntSeed: connect to sellers over WebRTC via a signaling relay, pay with in-browser EIP-712 signatures

Readme

@antseed/web-sdk

Browser buyer SDK for AntSeed. Connects to unmodified sellers over WebRTC DataChannels (signaled through an @antseed/relay) and pays per request with in-browser EIP-712 signatures. No native modules; the only runtime dependency is ethers.

import { AntseedWebClient } from '@antseed/web-sdk';

const client = await AntseedWebClient.create({
  relayUrl: 'https://relay.example.com',
  privateKey: buyerHotWalletKey,        // peerId = its EVM address
  connection: {
    iceServers: [
      { urls: 'stun:stun.example.com' },
      { urls: 'turns:turn.example.com', username, credential },
    ],
    onConnectionInfo: ({ path }) => showPath(path), // direct | relay | unknown
  },
});

const sellers = await client.sellers();
const session = await client.connect(sellers[0].peerId);

const response = await session.request(
  {
    path: '/v1/chat/completions',
    provider: 'openai',
    headers: { accept: 'text/event-stream' },
    body: JSON.stringify({ model: 'gpt-oss-120b', stream: true, messages: [...] }),
  },
  { onChunk: (data, done) => render(new TextDecoder().decode(data)) },
);

Example page

A working single-page example (seller discovery, connect, streaming chat) lives in examples/example.html:

pnpm --filter @antseed/web-sdk run example
# open http://127.0.0.1:8974/example.html

It expects a reachable relay (node apps/relay/dist/index.js, default port 8917 — use RELAY_STATIC_SELLERS=<peerId>@<host>:<port> to pin a local seller) and works unfunded against sellers that accept free requests.

Durable channel state and multiple tabs

By default the client uses an in-memory channel store: on reload the latest signed SpendingAuth state is gone and reserved funds stay locked until the session deadline passes. Production apps should inject a durable store:

const client = new AntseedWebClient({ relayUrl, wallet, channelStore: myStore });

channelStore takes any BuyerChannelStore (re-exported here). For crash-safety implement the optional flush(): it is awaited after each newly signed authorization is persisted and before it is transmitted, so it must not resolve until the write is durably committed (e.g. IndexedDB behind a synchronous in-memory cache). If several tabs can share the store, the implementation must also serialize signing (e.g. navigator.locks) — SpendingAuth amounts are cumulative per channel, and two tabs signing concurrently corrupt the sequence. Without a shared store, each tab opens its own channel and reserves its own budget.

ICE / TURN configuration

connection.iceServers accepts bare URL strings or full RTCIceServer entries (for TURN credentials); connection.iceTransportPolicy: 'relay' forces TURN-only paths. connection.onConnectionInfo reports the selected path ('direct' | 'relay' | 'unknown') once connected, without exposing addresses or raw candidates.

Architecture

This package contains only the browser-specific pieces: relay signaling (hello envelope, SDP offer, trickle ICE), the RTCPeerConnection transport, keepalive, channel-store adapters, and cross-tab coordination. Everything else is shared code, not a reimplementation. Production clients use an IndexedDB store; the in-memory store remains available only through the explicit ephemeral() API:

  • @antseed/protocol — wire format: message types, frame/HTTP/payment codecs, EIP-712 payment signatures, connection auth. Same modules @antseed/node runs.
  • @antseed/buyer-core — the buyer machinery: BuyerRequestHandler, BuyerPaymentManager, BuyerPaymentNegotiator, ProxyMux/PaymentMux. The exact classes behind the node/CLI buyer proxy, wired here to a WebRTC DataChannel and the configured channel store (in-memory by default).

So 402 negotiation, ReserveAuth/AuthAck, NeedAuth cost validation, cumulative SpendingAuth metadata, streaming, and chunked uploads behave identically to the node buyer. One browser-specific override: uploads chunk above 192 KiB (not 512 KiB) to stay under the ~256 KiB SCTP message ceiling browsers negotiate with libdatachannel sellers. Defaults target Base mainnet; override payment.* for other chains.

The relay bridge is closed as soon as the DataChannel opens — it is only needed for signaling, and holding it would consume the relay's per-IP bridge slots. There is no re-signaling path; a dropped DataChannel means a fresh connect().

Persistence and multiple tabs

AntseedWebClient.create() is asynchronous because it acquires an exclusive Web Lock for the buyer identity and hydrates IndexedDB before the payment manager may sign. Only one tab can use a buyer on the same chain/channels contract at a time. A second tab fails with BuyerAlreadyActiveError by default; set persistence.waitForLock to wait instead. A crashed tab releases its Web Lock automatically, and its successor recovers the last durably committed channel state.

Every ReserveAuth and SpendingAuth is committed to IndexedDB before it is sent. The database contains channel identifiers, signatures, cumulative amounts, metadata, and recovery context, but never the buyer private key. Use onPersistenceError to surface background failures from ordinary status/accounting writes immediately; awaited authorization commits and close() also reject when persistence is unhealthy. Call await client.close() when the application is done to flush storage and release the lock. Page-unload handlers are not a channel recovery mechanism.

AntseedWebClient.ephemeral() retains in-memory behavior for tests and free interoperability experiments. It must not be used for paid traffic because a reload loses its channel state and it provides no multi-tab exclusion.

SellerSession.close() closes only the WebRTC transport. Use await session.closeChannel() to request cooperative settlement from a supporting seller. client.listActiveChannels() exposes locally unresolved channels for recovery UI. If the seller is unavailable, call session.requestOnChainClose(operatorSigner) and, after the contract grace period, session.withdrawTimedOutChannel(operatorSigner). The signer must be the operator configured for the buyer in AntseedDeposits; the SDK does not assume that the browser wallet or any particular backend owns that role.

The relay verifies DHT metadata before caching it, and the SDK rejects a non-empty seller snapshot older than maxSellerSnapshotAgeMs (15 minutes by default) before normalizing it into the shared buyer pricing/capability view. The relay remains a trusted discovery-availability boundary; connection auth, seller response authentication, and payment signatures remain end to end.

Known limitation: large non-streamed responses

Chunking currently protects the upload direction only. A seller sends a non-streamed response body as a single DataChannel message, and browsers cap received SCTP messages at ~256 KiB — a larger response (e.g. base64 image output) will kill the DataChannel. Until seller-side response chunking exists, prefer streaming (accept: text/event-stream) for potentially large responses.

Prerequisites for paying sellers

The buyer address must have USDC deposited in AntseedDeposits (directly or via the gasless EIP-3009 sweep). Deposit UX is out of scope for this SDK.

Testing

vitest run covers byte-for-byte parity with @antseed/node (hello envelope, frame codec, EIP-712 signatures, metadata hashing) and an end-to-end suite that drives the real @antseed/node seller listener through a real relay instance, with node-datachannel/polyfill standing in for the browser's RTCPeerConnection. pnpm run test:browser additionally launches real Chromium and Firefox runtimes, connects their native WebRTC stacks to an unchanged seller through the relay, and verifies multi-tab exclusion plus crash takeover. The browser suite also completes paid 402 negotiation, verifies the EIP-712 authorizations, reloads IndexedDB, simulates lost seller state, and recovers the same on-chain channel without issuing a second reserve.