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

@yieldfabric/wallet

v0.1.0

Published

Reusable YieldFabric wallet — login, signing, profile, and key management

Readme

@yieldfabric/wallet

Reusable YieldFabric wallet for service developers — login + onboarding, signing (manual MQ + generic API), profile, and key management. Drop <WalletProvider> into your React app and the rest of the surface (hooks, UIs, services) becomes available.

Install

Published to the public npm registry — no scope .npmrc, no token:

npm install @yieldfabric/wallet

Peer deps: react ^18, react-dom ^18 (and optionally @stripe/stripe-js for the Stripe-backed KYC provider). All other runtime deps (@avatune/*, @averer/averer-websdk, @heroicons/react, js-sha3, zod) install automatically from the public npm registry. The SDK bundles a Tailwind-styled UI; consumers must add it to their Tailwind content glob (see below).

Quick start

// app/layout.tsx (Next.js App Router)
'use client';
import { WalletProvider, SignatureWorkflow } from '@yieldfabric/wallet';
import { useRouter } from 'next/navigation';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  const router = useRouter();
  return (
    <html>
      <body>
        <WalletProvider
          config={{
            // Imperative navigation hook so the SDK can route between
            // login / onboarding screens. Wire to your router.
            onNavigate: (href) => router.push(href),
            // (Optional) Called after a session is established.
            // onSessionBootstrapped: (userId) => { /* invalidate caches */ },
          }}
        >
          {children}
          {/* Zero-config global mount — wires the wallet's signature
              queue, drawer, toast notifier, and (optionally) the
              Signature Center launcher. This is the component every
              host wants at the root; <SignatureDrawer /> by itself is
              a *controlled inner* that needs `message` / `onClose` /
              `userId` props and is rendered by <SignatureWorkflow>
              for each pending message. */}
          <SignatureWorkflow />
        </WalletProvider>
      </body>
    </html>
  );
}
// app/login/page.tsx
'use client';
import { LoginForm, useAuth } from '@yieldfabric/wallet';

export default function LoginPage() {
  const { isAuthenticated } = useAuth();
  if (isAuthenticated) /* redirect */;
  return <LoginForm />;
}

Configuring service URLs

The SDK reads service base URLs from process.env.REACT_APP_* at build time (see src/core/config.ts):

| Var | Default | Use | |--|--|--| | REACT_APP_AUTH_SERVICE_URL | http://localhost:3000 | auth REST | | REACT_APP_PAYMENTS_SERVICE_URL | http://localhost:3002 | payments + WebSocket | | REACT_APP_API_URL | http://localhost:3002 | shared API base (WS derivation) | | REACT_APP_CORE_SERVICE_URL | http://localhost:3002 | core service URL | | REACT_APP_CHAIN_ID | 31337 | default chain id |

How to expose them to the browser depends on your bundler:

  • CRA / craco: REACT_APP_* works natively. Put them in .env.
  • Vite: define them with define: { 'process.env.REACT_APP_*': ... } in vite.config.ts, or fork core/config.ts to read import.meta.env.VITE_*.
  • Next.js: prefix with NEXT_PUBLIC_ in .env.local AND map them in next.config.js's env block so process.env.REACT_APP_* resolves in client bundles.

Future work: a follow-up will move URL resolution onto the WalletRuntimeConfig so consumers can pass URLs at mount time and not rely on env-var plumbing. See the comment at the top of core/config.ts.

The full public surface — providers, hooks, UI components, core REST/crypto services, types — is enumerated in src/index.ts.

Login surface API

The login screen is built from three independently-mountable pieces:

| Component | Use when | |---|---| | <LoginAltMethods /> | You want the wallet / passkey chip row (Averer, MetaMask, Dynamic, Passkey). No props. Reads auth.providers.enabled from /auth/providers/config. | | <LoginForm /> | You want the bundled email/password form with sensible defaults (services: ['vault', 'payments'], autoRedirect: false). | | <LoginComponent> | You want full control. Pass options for hook config; pass render={...} or children={...} to take over the JSX while keeping the form state machine. |

Custom-JSX login via render prop

<LoginComponent render={...}> receives the full form state — both the submission half (login, clearError, isLoading, isSubmitting, error) and the useForm machine (values, errors, touched, handleChange, handleBlur, handleSubmit). This means you can render any layout you want without re-implementing form state:

<LoginComponent
  options={{ services: ['vault', 'payments'], autoRedirect: false }}
  render={({ values, errors, touched, handleChange, handleSubmit, isSubmitting, error }) => (
    <form onSubmit={handleSubmit} className="space-y-5">
      <input
        type="email"
        value={values.email}
        onChange={(e) => handleChange('email', e.target.value)}
        className="my-custom-field"
      />
      {/* …password, error, submit… */}
    </form>
  )}
/>

Incoming payments + obligations inbox

Embeddable surface for letting an authenticated user view and accept pending payments (instant sends, swap legs) and obligations (contracts whose status is ACTIVE and whose counterpart is the active entity).

Three drop-in components, ordered most opinionated → least:

| Component | Use when | |---|---| | <IncomingPaymentsLauncher /> | You want a nav-chip with a badge count. Click opens an internal drawer with the full inbox. Mirrors <SignatureCenterLauncher> in shape. | | <IncomingPaymentsCenter /> | You want a dedicated /payments/inbox-style page. Adds a header, refresh button, and summary tiles on top of the inbox. | | <IncomingPaymentsInbox /> | You want just the card list — embed in your own chrome (drawer, dashboard card, sidebar). | | useIncomingPayments() | You want full UI control. Returns { items, groups, accept, acceptState, loading, error, refetch, activeEntityId, … }. |

import {
  IncomingPaymentsLauncher,
  IncomingPaymentsCenter,
  IncomingPaymentsInbox,
  useIncomingPayments,
} from '@yieldfabric/wallet';

// Launcher in your nav.
<IncomingPaymentsLauncher
  variant="icon-label"
  onReview={(item) => router.push(`/contracts/${item.contract?.id ?? ''}`)}
  getAccountLabel={(id) => entityNameCache.get(id) ?? id}
/>

// Or a full page.
<Route path="/payments/inbox" element={<IncomingPaymentsCenter />} />

// Or build your own layout, SDK does the data.
function MyInbox() {
  const { items, accept, acceptState } = useIncomingPayments();
  return items.map((item) => (
    <MyRow
      key={item.key}
      item={item}
      onAccept={() => accept(item)}
      busy={acceptState.itemKey === item.key}
    />
  ));
}

Acting-as / delegation is automatic

When a host mints a delegation token via useAuthContext().createDelegationToken(group), the inbox automatically scopes every query and mutation to the delegated group. No prop threading required. The resolution order for the active entity is:

  1. Explicit entityId prop on the hook / component (if passed).
  2. localStorage.delegation_group_id — set by <AppAuthProvider> when delegation is active.
  3. useAuth().user.id — the authenticated user's own entity.

Hosts that maintain their own actingAs model can pass entityId explicitly to override the auto-detection.

Signature requirement

Accepting a payment or obligation enqueues an on-chain transaction through the MQ. When the user is in Manual signature mode the SDK's signature drawer prompts them to sign before the executor fires. Consumers using this inbox must mount <SignatureWorkflow /> once at the app root — that's the zero-prop component that wires the queue + drawer + toast + Signature Center launcher together. <SignatureDrawer /> by itself is a controlled inner that takes message / onClose / userId props; you don't mount it directly.

// Once at the app root.
import { WalletProvider, SignatureWorkflow } from '@yieldfabric/wallet';

<WalletProvider config={…}>
  {children}
  <SignatureWorkflow />
</WalletProvider>

Without this mount, manual-mode users will see the inbox's Accept mutation succeed at the resolver but the on-chain settlement will never run.

Live refresh

The hook subscribes to the same entity-events SSE stream the signature center uses (Notification, MessageStatusChange, WorkflowUpdate, PositionChange). When any of those fire for the active entity, the inbox refetches automatically. The cost of a refetch is small enough that a coarse "refresh on any relevant event" strategy is correct — no manual cache invalidation is required.

Pass disableLiveRefresh: true to opt out (e.g. for tests).

Send payment (confidential YF "instant send")

Embeddable surface for the outbound side of the wallet. Submits a YF confidential payment via the instant GraphQL mutation — amounts are encrypted on chain, queued through the MQ, gated by <SignatureWorkflow /> when the user is in Manual mode.

Three drop-in surfaces:

| Component | Use when | |---|---| | <SendPaymentDrawer /> | You want a "Send" button somewhere that opens a drawer. Zero-config. | | <SendPaymentForm /> | You want the form embedded in your own chrome (page, modal, sidebar). | | useSendPayment() | You want full UI control. Returns { send, sending, error, lastResponse, reset }. |

import {
  SendPaymentDrawer,
  SendPaymentForm,
  useSendPayment,
  convertAmountToRaw,
} from '@yieldfabric/wallet';

// 1. Drawer — open from a button in your nav / wallet panel.
const [open, setOpen] = useState(false);

<button onClick={() => setOpen(true)}>Send</button>

<SendPaymentDrawer
  isOpen={open}
  onClose={() => setOpen(false)}
  assets={[
    { id: 'aud-token-asset', currency: 'AUD', decimals: '18' },
    { id: 'usdc-token-asset', currency: 'USDC', decimals: '6' },
  ]}
  onSent={(response) => {
    // Resolver accepted — the recipient's inbox will now show the
    // payment as PENDING. `response.messageId` is the MQ id for
    // optional `messagesService.pollMessageCompletion` polling.
  }}
/>

// 2. Embed the form directly (no drawer).
<SendPaymentForm
  assets={assets}
  defaultAssetId="aud-token-asset"
  onSuccess={(response) => navigate(`/payments/${response.paymentId}`)}
/>

// 3. Build your own form, SDK handles the submit + state.
function MySendForm() {
  const { send, sending, error } = useSendPayment();
  return (
    <form onSubmit={async (e) => {
      e.preventDefault();
      await send({
        assetId: 'aud-token-asset',
        amount: convertAmountToRaw('100.50', 18),
        destinationId: '[email protected]',
      });
    }}>
      … your fields …
      <button disabled={sending}>{sending ? 'Sending…' : 'Send'}</button>
      {error && <p>{error}</p>}
    </form>
  );
}

Asset list is host-supplied

The SDK doesn't bundle an asset-lookup query — that's host-specific (GraphQL assets.all for some hosts, a hardcoded enum for others). Pass the asset list as a prop to <SendPaymentForm> / <SendPaymentDrawer>. The shape is minimal:

interface SendPaymentAsset {
  id: string;                       // token asset id (e.g. "aud-token-asset")
  currency: string;                 // display label ("AUD", "USDC")
  decimals?: string | number;       // defaults to 18 when omitted
  name?: string;                    // optional long name for the picker
}

Amount conversion

The resolver wants a raw on-chain integer string ("100500000000000000000" for 100.50 AUD with 18 decimals). The form does this conversion internally via convertAmountToRaw(humanAmount, decimals). If you're calling the service / hook directly, do the same:

import { convertAmountToRaw } from '@yieldfabric/wallet';
const raw = convertAmountToRaw('100.50', 18); // → "100500000000000000000"

convertAmountToRaw rejects negatives, NaN, scientific notation, and over-precision (more fractional digits than the token's decimals), so you can use it as your validator too.

Recipient resolution

The form's "Recipient" field accepts an entity name or id ([email protected], a UUID, …). Behind the scenes the resolver looks up the entity's default wallet. For multi-wallet entities where the sender wants to target a specific wallet, set enableWalletIdMode to expose a small toggle that switches the field to wallet-UUID input.

Direct calls have all three options:

await send({ assetId, amount, destinationId: '[email protected]' });
await send({ assetId, amount, destinationWalletId: 'd1b3…wallet-uuid' });
await send({ assetId, amount, contractId: 'CONTRACT-OBLIGATION-…' });

Pass exactly one. The resolver throws when zero or multiple are provided.

Acting-as is automatic

Same story as the inbox: when a delegation token is installed (useAuthContext().createDelegationToken(group)), the send originates from the delegated group instead of the user. No prop threading needed — tokenManager picks the right token, the resolver reads the acting_as claim and signs against the group's wallet.

Signature requirement

For Manual-mode users (external wallets — passkey, MetaMask, Dynamic), the on-chain send is gated by <SignatureWorkflow />. Mount it once at the app root. Without it, send() resolves successfully but the executor never runs.

Block-explorer links

Transaction hashes rendered by the SDK's signature drawer + signature center row + EOA-send status panel are automatically wrapped in explorer links when the SDK has a registered explorer for the message's chain id. Click the hash → opens the chain's explorer (Etherscan, Polygonscan, BscScan, SnowTrace, Redbelly Explorer, …) in a new tab.

Built-in coverage:

| Chain | Explorer | |---|---| | Ethereum Mainnet (1) | etherscan.io | | Goerli (5) / Sepolia (11155111) | (goerli|sepolia).etherscan.io | | Polygon (137) / Mumbai (80001) | polygonscan.com / mumbai.polygonscan.com | | BSC (56) / Testnet (97) | bscscan.com / testnet.bscscan.com | | Avalanche (43114) / Fuji (43113) | snowtrace.io / testnet.snowtrace.io | | Redbelly Mainnet (151) | redbelly.routescan.io | | Redbelly Testnet (153) | redbelly.testnet.routescan.io |

Local-dev chains (31337, 33117, 1337) intentionally have no explorer registered — the hash renders as plain text.

Adding or overriding an explorer

import { registerBlockExplorer } from '@yieldfabric/wallet';

// Add a chain not in the defaults:
registerBlockExplorer(8453, {
  name: 'Basescan',
  txUrl:      (hash) => `https://basescan.org/tx/${hash}`,
  addressUrl: (addr) => `https://basescan.org/address/${addr}`,
});

// Override one path while inheriting the rest (e.g. prefer
// Blockscout for addresses but keep Etherscan for txs on mainnet):
registerBlockExplorer(1, {
  addressUrl: (addr) => `https://eth.blockscout.com/address/${addr}`,
});

Direct helpers if you need to render your own link:

import {
  getBlockExplorerTxUrl,
  getBlockExplorerAddressUrl,
} from '@yieldfabric/wallet';

const txUrl   = getBlockExplorerTxUrl(chainId, txHash);     // string | null
const addrUrl = getBlockExplorerAddressUrl(chainId, addr);  // string | null

Both helpers return null when the chain has no registered explorer or the input fails validation (empty / not 0x…-hex / wrong length for addresses), so the calling component can fall back to plain text without an if-cascade.

Passkey sign-in: no chain id needed by default

signInWithPasskey() and signUpWithPasskey() both accept an optional chainId. When omitted, the SDK falls back to Number(getDefaultChainId()) (which reads REACT_APP_CHAIN_ID, defaulting to 31337):

// All three call the same default chain
const r1 = await signInWithPasskey();
const r2 = await signInWithPasskey({});
const r3 = await signInWithPasskey({ chainId: Number(getDefaultChainId()) });

// Multi-chain consumers can still pin the chain explicitly
const r4 = await signInWithPasskey({ chainId: 1 });

Tailwind setup

The SDK ships UI components styled with Tailwind utility classes. Tailwind needs to scan the SDK source so it picks up which utilities to keep.

// tailwind.config.js
module.exports = {
  content: [
    './src/**/*.{ts,tsx}',
    // Scan the published bundles — className strings are intact (tsup
    // does not minify), so the utilities survive purge.
    './node_modules/@yieldfabric/wallet/dist/**/*.{js,cjs}',
  ],
  // …
};

The package ships only dist/ (compiled, source closed), so scan the bundles — the class strings are preserved there verbatim.

Theming for consumer apps

The SDK is theme-portable: every surface (login, onboarding, signature drawer / center, keys manager, profile editor, primitives, identity card) renders against a fixed set of semantic Tailwind token names. Map them in your tailwind.config.js to re-skin the SDK to any palette — dark, light, brand-coloured, anything. No dark: prefix is required; no runtime theme switch; no SDK code change.

The token contract

The SDK uses these names. Define them under theme.colors (or theme.extend.colors) in your tailwind config.

// tailwind.config.js
module.exports = {
  // …content paths…
  theme: {
    extend: {
      colors: {
        // ── Surfaces ────────────────────────────────────────────
        page:           '#ffffff',  // viewport background
        surface:        '#ffffff',  // card / sheet background
        'surface-alt':  '#f7f8fa',  // muted card / striped row
        raised:         '#ffffff',  // floating elements (toasts, drawers)
        'raised-hover': '#f7f8fa',
        overlay:        'rgba(255,255,255,0.92)',
        background:     '#ffffff',  // legacy alias, still used by some SDK chrome

        // ── Borders ─────────────────────────────────────────────
        'border-default': '#e3e8f0',
        'border-light':   '#eef1f6',
        'border-strong':  '#c7d0dc',

        // ── Text ────────────────────────────────────────────────
        'text-primary':   '#0a1b30',  // body text
        'text-secondary': '#32373c',  // secondary copy
        'text-muted':     '#5f6b7a',  // labels, captions
        'text-inverse':   '#ffffff',  // text on dark surfaces

        // ── Brand primary (accent / glow / focus ring) ─────────
        primary:         '#054e92',
        'primary-muted': 'rgba(5,78,146,0.10)',
        accent:          '#0b6acf',

        // ── CTA (high-emphasis button) ─────────────────────────
        // In a dark theme this is intentionally near-text-color so the
        // button reads as inverted; in a light theme it's typically the
        // brand color. The two tokens are kept distinct so each consumer
        // can choose what "CTA" should look like vs. the brand accent.
        cta:        '#054e92',
        'cta-hover':'#054075',
        'on-cta':   '#ffffff',

        // ── Chip (elevated small surface: sign-in chips,
        //          segmented controls, selected pills) ──────────
        chip:          '#f7f8fa',
        'chip-hover':  '#eef2f8',
        'chip-active': '#e3e8f0',

        // ── Input (subtle filled input / inline card bg) ───────
        'input-bg':    '#ffffff',

        // ── Status (used by signature drawer / center / toasts) ─
        'status-success-bg':   '#dcf0e6',
        'status-success-text': '#1a6b42',
        'status-warning-bg':   '#fbf3d4',
        'status-warning-text': '#7a6518',
        'status-error-bg':     '#fde2e7',
        'status-error-text':   '#9b2c3c',
        'status-info-bg':      '#dceaf8',
        'status-info-text':    '#1a5090',

        // ── Pastels (soft badge fills) ──────────────────────────
        'pastel-peach':    '#fde6d5',
        'pastel-lavender': '#e9e2f5',
        'pastel-mint':     '#d4f0e4',
        'pastel-sky':      '#dceaf8',
        'pastel-rose':     '#fbe2e6',
        'pastel-lemon':    '#fbf3d4',
        'pastel-lilac':    '#ecdef6',
        'pastel-coral':    '#fde4dd',
      },
    },
  },
};

The example values above are a light theme. Drop the same token names into your own palette and the SDK picks up your colours.

Backing tokens with CSS variables (optional)

If you run a single app with both light and dark modes, back each token with a CSS variable in :root and .dark, then point Tailwind at the variable. See the YieldFabric host's index.css for the canonical pattern; the values to define are the same as the table above, prefixed with --color-.

Autofill (Chrome / Safari)

The login + signup inputs use a .autofill-fix class to keep autofilled text legible. Add this CSS once in your global stylesheet (light theme shown; flip the box-shadow + fill-color for dark):

.autofill-fix:-webkit-autofill,
.autofill-fix:-webkit-autofill:hover,
.autofill-fix:-webkit-autofill:focus {
  -webkit-box-shadow: 0 0 0 1000px #ffffff inset !important;
  -webkit-text-fill-color: #0a1b30 !important;
  caret-color: #0a1b30;
  transition: background-color 5000s ease-in-out 0s;
}

What you don't have to do

  • No theme="light" | "dark" prop — the SDK has no theme switch; the same JSX renders in either palette.
  • No CSS-in-JS — Tailwind classes only, so your build pipeline is unchanged.
  • No code forks per consumer — every SDK surface is portable today; a future consumer needs only the token map above.

Package layout

src/
├── core/         no React, no DOM — REST clients, WebAuthn, crypto, token store
├── providers/    auth provider impls (email, passkey, MetaMask, Averer) + config
├── react/        hooks + headless controllers
└── ui/           styled components (LoginWidget, SignatureDrawer, KeysManager, …)

Import graph: ui → react → providers → core. Forbid backward edges.

Build

npm install
npm run build      # one-shot — emits dist/index.{js,cjs,d.ts}
npm run dev        # watch mode — recompiles on save
npm run typecheck  # tsc --noEmit
npm run clean      # rm -rf dist

tsup (esbuild + dts) produces:

  • dist/index.js — ESM bundle (consumed by modern bundlers / Next.js)
  • dist/index.cjs — CommonJS bundle (Node fallback)
  • dist/index.d.ts / .d.cts — type definitions

Both bundles are prepended with "use client"; so Next.js App Router treats the SDK as client-side by default (the surface is hook + DOM-heavy).

Externals: react, react-dom, and the runtime deps (@averer/averer-websdk, @heroicons/react, js-sha3, zod) are NOT bundled — consumers install them via the dependencies / peerDependencies declarations.

Consuming from another monorepo package

If your app lives in the same repo and uses file: reference, the consumer reads dist/ via the main / module / exports fields:

{
  "dependencies": {
    "@yieldfabric/wallet": "file:../yieldfabric-wallet-sdk"
  }
}

Workflow:

# In the SDK
cd yieldfabric-wallet-sdk
npm install
npm run build           # one-shot, or `npm run dev` for watch mode

# In the consumer
cd ../my-app
npm install             # picks up the freshly-built dist
npm run dev

If you're iterating on the SDK while running the consumer, leave npm run dev running in the SDK directory; the consumer's bundler will pick up changes on the next reload.

Publishing

Ships to the public npm registry as a public scoped package (publishConfig.access: public). Publishing @yieldfabric/* requires the yieldfabric scope on npm — i.e. an npm org (or user) named yieldfabric. Authenticate once with npm login, then:

# bump "version" first — re-publishing an existing version is rejected
npm publish             # prepublishOnly runs clean && build automatically

prepublishOnly runs clean && build, so a stale dist/ can't leak into the tarball. The published tarball ships dist/ + README.md only — no src/ and no source maps, so the TypeScript source stays closed (only compiled output ships). Verify with npm pack --dry-run.

License

Proprietary. See repo root for terms.