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

@hazbase/auth

v0.6.1

Published

SDK helper for email OTP sessions, passkey-native account bootstrap, sponsor signing, wallet-signature authentication, and hazBase-integrated ZK proofs.

Readme

@hazbase/auth

npm version License

Overview

@hazbase/auth is an SDK for hazBase application sessions, passkey flows, account bootstrap, and sponsor signing.
It wraps the backend APIs used by first-party or allowlisted partner apps so web clients can move through email OTP -> passkey binding -> account bootstrap -> owner authorization -> sponsored user operation with a consistent TypeScript interface.

  • Scope: email OTP sessions, passkey registration/assertion, account descriptor/bootstrap, owner userOp authorization, sponsor signing
  • Extension helpers: passkey bridge controller and runtime request helper for wallet extensions
  • Design: ESM-first, browser/backend friendly fetch wrappers, minimal runtime assumptions
  • Goal: Let apps integrate hazBase auth and smart-wallet flows without rewriting request/response plumbing

Requirements

  • Node.js >= 18.18 (or a modern browser/runtime with fetch)
  • TypeScript >= 5.2
  • A hazBase backend that exposes the auth/account/sponsor endpoints
  • For passkey flows: a browser/runtime that supports WebAuthn

package.json (example)

{
  "type": "module",
  "engines": { "node": ">=18.18" }
}

Installation

pnpm add @hazbase/auth
# or
npm i @hazbase/auth

Environment / backend assumptions

@hazbase/auth is a thin client SDK. It does not create email OTP, passkey, or sponsor policies by itself.
You are expected to provide a backend that exposes the current hazBase auth routes.

Default route family:

  • /api/auth/email/request-otp
  • /api/auth/email/verify-otp
  • /api/auth/step-up/email/request
  • /api/auth/step-up/email/complete
  • /api/auth/step-up/passkey/complete
  • /api/auth/step-up/verify
  • /api/auth/passkey/register/challenge
  • /api/auth/passkey/register/complete
  • /api/auth/passkey/assert/challenge
  • /api/auth/passkey/assert/complete
  • /api/auth/account/descriptor
  • /api/auth/account/bootstrap
  • /api/auth/account/lookup
  • /api/auth/account/authorize-userop
  • /api/auth/account/devices
  • /api/auth/account/sessions
  • /api/auth/account/revoke-device
  • /api/auth/account/revoke-session
  • /api/wallet/session/start
  • /api/wallet/session/grant
  • /api/wallet/session/execute
  • /api/wallet/session/end
  • /api/wallet/sponsor-action

Important model assumptions:

  • email OTP starts an application session. It is not wallet ownership on its own.
  • Web owner approval is passkey-centered.
  • Embedded session issuance requires a fresh purpose=session high-trust token.
  • Listing devices and embedded sessions uses only app-session authentication.
  • Revoking a device or embedded session requires a fresh purpose=reauth high-trust token.
  • Device revoke cascades to active embedded sessions on that device.
  • sponsorUserOp only succeeds for actions that match the embedded-session snapshot and on-chain session policy.
  • Session-mode sponsorship returns an accountSignature only for the exact sponsored payload it just approved.
  • Embedded-session execution can be driven either as a sponsor-only flow or as a backend-executed session flow with grantEmbeddedSession() and executeEmbeddedSession().

Deployment portability notes:

  • @hazbase/auth stays backend-contract based. It does not expose cloud-specific SDK modes.
  • The same client code should work with any hazBase-compatible backend as long as the backend API contract stays the same.
  • The default API endpoint is https://api.hazbase.com. Call setApiEndpoint() only for local, staging, or self-hosted APIs.

Quick start: extension passkey bridge

Wallet extensions can keep passkey ceremonies on the canonical hazBase origin while handling the popup/window lifecycle in the extension background script.

import { createPasskeyBridgeController } from '@hazbase/auth/extension';

const passkeyBridge = createPasskeyBridgeController({
  extensionId: chrome.runtime.id,
  openBridgeWindow: async (url) => {
    const created = await chrome.windows.create({ url, type: 'popup' });
    return { windowId: created.id, tabId: created.tabs?.[0]?.id };
  },
});

The bridge uses https://api.hazbase.com by default. Pass fallbackApiEndpoint only for local, staging, or self-hosted APIs.

The UI surface can request the bridge through the extension runtime:

import { requestExtensionPasskeyBridge } from '@hazbase/auth/extension';

const assertion = await requestExtensionPasskeyBridge({
  mode: 'assertion',
  emailSession,
  purpose: 'reauth',
  deviceBindingId,
}, {
  messageType: 'example:passkeyBridge',
});

Quick start: purpose-bound step-up authentication

Sensitive application actions can require a short-lived assurance token bound to the current app session, exact web origin, and action purpose. Prefer a passkey and offer the browser-bound email flow only where the product policy allows it.

import {
  completePasskeyStepUp,
  createStepUpBrowserBinding,
  requestEmailStepUp,
} from '@hazbase/auth';
import { requestPasskeyBridgePopup } from '@hazbase/auth/extension';

const bridge = await requestPasskeyBridgePopup({
  mode: 'assertion',
  emailSession,
  purpose: 'reauth',
});
if (!bridge.ok || bridge.mode !== 'assertion' || !bridge.assertion.highTrustToken) {
  throw new Error('Passkey confirmation failed');
}

const assurance = await completePasskeyStepUp({
  emailSession,
  purpose: 'example_sensitive_action',
  highTrustToken: bridge.assertion.highTrustToken,
});

For an email fallback, generate a new browser binding for each request and keep its secret only in the browser that started the flow. The emailed link or code cannot complete without that secret.

const binding = await createStepUpBrowserBinding();
const challenge = await requestEmailStepUp({
  emailSession,
  purpose: 'example_sensitive_action',
  browserBindingHash: binding.hash,
  returnUrl: location.href.split('#')[0],
});

The canonical passkey bridge only posts results to backend-allowlisted exact origins. Production backends must configure each application origin before the popup helper is used.


Quick start: email OTP -> passkey binding -> account bootstrap -> sponsor

scripts/passkey-account.ts

import {
  assertPasskey,
  authorizeOwnerUserOp,
  bootstrapPasskeyAccount,
  getCurrentPasskeyPartnerOrigin,
  requestEmailOtp,
  refreshEmailSession,
  requestPasskeyAccountDescriptor,
  registerPasskey,
  sponsorUserOp,
  startEmbeddedSession,
  verifyEmailOtp,
} from '@hazbase/auth';

async function main() {
  const email = '[email protected]';

  await requestEmailOtp({
    email,
    purpose: 'smart_wallet_sign_in',
  });

  const session = await verifyEmailOtp({
    email,
    code: '123456',
    purpose: 'smart_wallet_sign_in',
  });

  // Persist the refresh token securely and rotate the short-lived access token
  // without asking the user to enter another OTP.
  const renewedSession = await refreshEmailSession({
    refreshToken: session.refreshToken!,
  });

  const partnerOrigin = getCurrentPasskeyPartnerOrigin({
    clientKey: 'partner_client_key',
  });

  const device = await registerPasskey({
    emailSession: session.accessToken,
    deviceLabel: 'Chrome on MacBook',
    partnerOrigin,
  });

  const bootstrapAssertion = await assertPasskey({
    emailSession: session.accessToken,
    purpose: 'bootstrap',
    deviceBindingId: device.deviceBindingId,
    partnerOrigin,
  });

  const descriptor = await requestPasskeyAccountDescriptor({
    emailSession: session.accessToken,
    deviceBindingId: device.deviceBindingId,
    chainId: 11155111,
    accountSalt: 'user-owned-account',
  });

  const account = await bootstrapPasskeyAccount({
    emailSession: session.accessToken,
    deviceBindingId: device.deviceBindingId,
    highTrustToken: bootstrapAssertion.highTrustToken!,
    chainId: descriptor.chainId,
    accountSalt: descriptor.accountSalt,
  });

  const ownerAuth = await authorizeOwnerUserOp({
    emailSession: session.accessToken,
    deviceBindingId: device.deviceBindingId,
    highTrustToken: bootstrapAssertion.highTrustToken!,
    smartAccountAddress: account.smartAccountAddress,
    userOpHash: '0x0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef',
  });

  const sessionChallenge = await requestPasskeyAssertionChallenge({
    emailSession: session.accessToken,
    purpose: 'session',
    deviceBindingId: device.deviceBindingId,
  });

  const sessionAssertion = await completePasskeyAssertion({
    emailSession: session.accessToken,
    challengeId: sessionChallenge.challengeId,
    credential: authenticationCredential,
    purpose: 'session',
    deviceBindingId: device.deviceBindingId,
  });

  const embedded = await startEmbeddedSession({
    emailSession: session.accessToken,
    smartAccountAddress: account.smartAccountAddress,
    deviceBindingId: device.deviceBindingId,
    actionProfileKey: 'first_party_l2',
    highTrustToken: sessionAssertion.highTrustToken!,
  });

  const sponsored = await sponsorUserOp({
    emailSession: session.accessToken,
    embeddedSessionId: embedded.sessionId!,
    sender: account.smartAccountAddress,
    nonce: '0',
    callData: '0x12345678',
    callGasLimit: '150000',
    verificationGasLimit: '120000',
    target: '0x1111111111111111111111111111111111111111',
    data: '0x12345678',
    value: '0',
    signingMode: 'session',
  });

  console.log({
    deviceBindingId: device.deviceBindingId,
    smartAccountAddress: account.smartAccountAddress,
    ownerValidator: ownerAuth.ownerValidator,
    paymasterAddress: sponsored.paymasterAddress,
    sessionKeyAddress: sponsored.sessionKeyAddress,
    sponsoredUserOpHash: sponsored.sponsoredUserOpHash,
  });
}

main().catch((error) => {
  console.error(error);
  process.exit(1);
});

Common operations (snippets)

1) Start an application session with email OTP

await requestEmailOtp({
  email: '[email protected]',
  purpose: 'smart_wallet_sign_in',
});

const session = await verifyEmailOtp({
  email: '[email protected]',
  code: '123456',
  purpose: 'smart_wallet_sign_in',
});

2) Step up with passkey before bootstrap or owner approval

const assertion = await assertPasskey({
  emailSession: session.accessToken,
  purpose: 'reauth',
  deviceBindingId: 'devb_demo',
  partnerOrigin,
});

3) Fetch or bootstrap the current account descriptor

const descriptor = await requestPasskeyAccountDescriptor({
  emailSession: session.accessToken,
  deviceBindingId: 'devb_demo',
  chainId: 11155111,
  accountSalt: 'user-owned-account',
});

const account = await bootstrapPasskeyAccount({
  emailSession: session.accessToken,
  deviceBindingId: 'devb_demo',
  highTrustToken: assertion.highTrustToken!,
  chainId: descriptor.chainId,
  accountSalt: descriptor.accountSalt,
});

4) Authorize an owner userOp or request sponsor signing

const ownerAuth = await authorizeOwnerUserOp({
  emailSession: session.accessToken,
  deviceBindingId: 'devb_demo',
  highTrustToken: assertion.highTrustToken!,
  smartAccountAddress: account.smartAccountAddress,
  userOpHash,
});

const sessionChallenge = await requestPasskeyAssertionChallenge({
  emailSession: session.accessToken,
  purpose: 'session',
  deviceBindingId: 'devb_demo',
});

const sessionAssertion = await completePasskeyAssertion({
  emailSession: session.accessToken,
  challengeId: sessionChallenge.challengeId,
  credential: authenticationCredential,
  purpose: 'session',
  deviceBindingId: 'devb_demo',
});

const embedded = await startEmbeddedSession({
  emailSession: session.accessToken,
  smartAccountAddress: account.smartAccountAddress,
  deviceBindingId: 'devb_demo',
  actionProfileKey: 'first_party_l2',
  highTrustToken: sessionAssertion.highTrustToken!,
});

const sponsored = await sponsorUserOp({
  emailSession: session.accessToken,
  embeddedSessionId: embedded.sessionId!,
  sender: account.smartAccountAddress,
  nonce: '0',
  callData: '0x12345678',
  callGasLimit: '150000',
  verificationGasLimit: '120000',
  target: '0x1111111111111111111111111111111111111111',
  data: '0x12345678',
  value: '0',
  signingMode: 'session',
});

5) Pay an x402 resource as a buyer

Supported buyer networks are base, base-sepolia, polygon, and polygon-amoy for USDC EIP-3009 payments.

import {
  buildX402PaymentHeader,
  decodeXPaymentResponseHeader,
  getX402PaymentRequestId,
  parseX402ResponseBody,
  payX402WithHazbaseWallet,
  selectX402PaymentRequirement,
} from '@hazbase/auth';

const first = await fetch('https://seller.example/reports/001', {
  headers: { accept: 'application/x-x402+json, application/json' },
});
if (first.status !== 402) throw new Error('resource did not request x402 payment');

const x402 = parseX402ResponseBody(await first.text());
const requirement = selectX402PaymentRequirement(x402);

// EOA buyer: sign EIP-3009 transferWithAuthorization locally.
const payment = await buildX402PaymentHeader({
  requirement,
  privateKey: process.env.BUYER_PK!,
});

const paid = await fetch(requirement.resource, {
  headers: { 'x-payment': payment.header },
});
console.log(decodeXPaymentResponseHeader(paid.headers.get('x-payment-response')));

// hazBase Smart Wallet buyer: use a fresh purpose=reauth highTrustToken.
const walletPayment = await payX402WithHazbaseWallet({
  emailSession: session.accessToken,
  paymentRequestId: getX402PaymentRequestId(x402, requirement),
  deviceBindingId: 'devb_demo',
  highTrustToken: assertion.highTrustToken!,
  smartAccountAddress: account.smartAccountAddress,
  waitForReceipt: true,
});

Helper names

  • requestEmailOtp
  • verifyEmailOtp
  • refreshEmailSession
  • createStepUpBrowserBinding
  • requestEmailStepUp
  • completeEmailStepUp
  • completePasskeyStepUp
  • verifyStepUpAssurance
  • readEmailStepUpHandoff
  • consumeEmailStepUpHandoffFromLocation
  • registerPasskey
  • assertPasskey
  • getCurrentPasskeyPartnerOrigin
  • canUseDirectPartnerPasskey
  • requestPasskeyRegistrationChallenge
  • completePasskeyRegistration
  • requestPasskeyAssertionChallenge
  • completePasskeyAssertion
  • requestPasskeyAccountDescriptor
  • bootstrapPasskeyAccount
  • lookupPasskeyAccount
  • authorizeOwnerUserOp
  • sponsorUserOp
  • signInWithWallet
  • listSupportedPayments
  • buildX402Requirements
  • verifyX402Payment
  • settleX402Payment
  • parseX402ResponseBody
  • selectX402PaymentRequirement
  • buildX402PaymentHeader
  • decodeXPaymentResponseHeader
  • payX402WithHazbaseWallet

Notes

  • email OTP is an application-session primitive, not a wallet-ownership proof.
  • Web owner approval is passkey-centered; browser-held secp256k1 owner storage is no longer part of the public flow.
  • authorizeOwnerUserOp returns a backend-issued owner authorization payload, not a raw local private-key signature.
  • Embedded session issuance requires a fresh purpose=session high-trust token. Existing active sessions can be reused until revoked or expired.
  • sponsorUserOp evaluates the embedded-session snapshot taken at issuance time, so later action-profile broadening does not widen already-issued sessions.
  • sponsorUserOp({ signingMode: 'session' }) returns paymasterAndData, sponsoredUserOpHash, and the final session-mode accountSignature for that exact sponsored payload.

Troubleshooting (FAQ)

  • 401 during sponsorUserOp — the action likely falls outside the embedded session profile, gas budget, or paymaster validity window.
  • 404 during account bootstrap/lookup — the current deviceBindingId is missing, revoked, or not bound to the requested account.
  • Passkey assertion succeeds but owner action fails — confirm you are using the returned highTrustToken for the same device binding and account flow.
  • email OTP works but wallet flow does not — OTP only creates an app session; you still need passkey binding plus account bootstrap.

Account security inventory and revoke

These low-level helpers are meant for account-security surfaces such as “active devices” and “active sessions”.

Listing endpoints use only the app session:

  • listPasskeyDevices()
  • listEmbeddedSessions()

Revoke endpoints require a fresh purpose=reauth high-trust token:

  • revokePasskeyDevice()
  • revokeEmbeddedSession()

Device revoke cascades to the linked passkey credential and all active embedded sessions on that device.


Security: recommended overrides

ethers currently pins a ws version with a known advisory, and npm ignores overrides declared inside a dependency. To protect your own dependency tree, add this to your application's package.json and reinstall:

{
  "overrides": {
    "ws": "^8.21.0"
  }
}

(yarn: use resolutions; pnpm: use pnpm.overrides.) Workaround until ethers ships a fixed ws range upstream.


License

Apache-2.0