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

@yo-protocol/widget-sdk

v0.1.2

Published

Embed the YO Protocol widget in any website. Host-side loader + postMessage bridge.

Readme

@yo-protocol/widget-sdk

Embed the YO Protocol widget in any website. The widget renders inside an iframe loaded from sdk-widget.vercel.app; your page owns the wallet and signs transactions the widget prepares, over a versioned postMessage bridge.

npm install @yo-protocol/widget-sdk

LLM-friendly: this README contains everything needed to ship a working integration in one pass — props, events, adapters, protocol, security, embed modes. Paste it verbatim into an LLM along with "integrate the YO widget into my app using wagmi + RainbowKit" and it should produce working code on the first try.


Table of contents


Peer dependencies

Install whatever matches your stack — peers are optional:

  • react@>=18, react-dom@>=18 — for <YoWidget /> and <YoWidgetLauncher />
  • wagmi@>=2 + @wagmi/core@>=2.14 — for createWagmiAdapter
  • viem@>=2 — for createViemAdapter

No peers required if you use vanilla mount().


Two embed modes

Pick the one that fits your layout. Both share the same signer + SDK plumbing — switching is a one-component swap.

| Component | Use when | How it looks | |---|---|---| | <YoWidget /> | Widget is a permanent, prominent feature of a page (e.g. a dashboard section, an "Earn" tab). | Inline iframe that grows/shrinks with content. | | <YoWidgetLauncher /> | Widget is ambient / secondary (e.g. bottom-right helper, always-available entry point). | Floating round "YO" bubble anchored to a page corner; click to open a panel containing the widget, click × or outside to minimize. |

Both mount the same iframe. The launcher keeps it mounted across open/close toggles so the handshake, signer state, and cached data survive minimizing.


60-second React quickstart (wagmi + RainbowKit)

Inline embed

'use client'
import { ConnectButton, useConnectModal } from '@rainbow-me/rainbowkit'
import { YoWidget } from '@yo-protocol/widget-sdk'
import { createWagmiAdapter } from '@yo-protocol/widget-sdk/adapters/wagmi'
import { useMemo } from 'react'
import { useConfig } from 'wagmi'

export default function Page() {
  const config = useConfig()
  const { openConnectModal } = useConnectModal()

  const signer = useMemo(
    () => createWagmiAdapter({ config, onConnectRequest: openConnectModal }),
    [config, openConnectModal],
  )

  return (
    <>
      <ConnectButton />
      <YoWidget signer={signer} />
    </>
  )
}

Floating launcher

import { YoWidgetLauncher } from '@yo-protocol/widget-sdk'
// … same signer as above …

<YoWidgetLauncher
  signer={signer}
  position="bottom-right"
/>

That's it. The widget picks up the connected address from wagmi, renders vaults and positions, and prompts the user's wallet for deposits/withdraws via the adapter. You do not need onEvent unless you want analytics — see Events.


Vanilla JS quickstart (any framework or plain HTML)

<div id="yo-widget"></div>
<script type="module">
  import { mount } from 'https://esm.sh/@yo-protocol/widget-sdk'
  import { createViemAdapter } from 'https://esm.sh/@yo-protocol/widget-sdk/adapters/viem'

  const adapter = createViemAdapter({
    getWalletClient: () => window.yourViemWalletClient,
    subscribe: (notify) => {
      const unsub = yourWallet.on('change', (state) =>
        notify({ address: state.address, chainId: state.chainId }),
      )
      return unsub
    },
    onConnectRequest: () => yourWallet.openModal(),
  })

  mount(document.getElementById('yo-widget'), { signer: adapter })
</script>

mount() produces the inline embed. For a launcher in vanilla JS, wrap your own floating button around the iframe container — or open a PR for a mountLauncher() helper.


<YoWidget /> props (inline)

| Prop | Type | Default | Description | |---|---|---|---| | signer | SignerAdapter | required | Bridges your wallet to the widget. See Signer adapters. | | partnerId | number | 9999 | Attribution id assigned by the YO team. | | chains | number[] | [1, 8453, 42161] | Restrict vaults to these chains. | | rpcUrls | Record<number, string \| string[]> | — | Optional host-provided RPCs per chain. Tried first; SDK's curated public fallbacks cover any gaps. Use this to pass Alchemy/Infura keys. | | widgetOrigin | string | https://sdk-widget.vercel.app | Where to load the iframe from. Override for staging/local. | | theme | { primary?, background?, card?, radius? } | dark palette | Narrow colour overrides forwarded via URL query. See Theming. | | locale | string | — | Reserved for i18n (v1 is English). | | onEvent | (e: WidgetEvent) => void | — | Optional. See Events. | | className / style / width / minHeight | — | — | Pass-through to the iframe element. |

Vanilla mount(el, options) accepts the same option set minus the styling props; it returns { iframe, destroy() }.


<YoWidgetLauncher /> props (floating)

Extends all <YoWidget /> props (signer, partnerId, chains, rpcUrls, widgetOrigin, theme, locale, onEvent) plus:

| Prop | Type | Default | Description | |---|---|---|---| | position | 'bottom-right' \| 'bottom-left' \| 'top-right' \| 'top-left' | 'bottom-right' | Corner to anchor the bubble + panel. | | label | string | 'Open YO' | Aria-label / tooltip for the bubble. | | panelWidth | number (px) | 420 | Panel width when open. | | panelHeight | number (px) | 640 | Panel height when open. | | defaultOpen | boolean | false | Start open. | | dismissOnOutsideClick | boolean | true | Close when the user clicks outside the panel or hits Escape. | | bubbleStyle | CSSProperties | — | Override the floating button's inline style (size, color, shadow, etc.). | | bubbleContent | ReactNode | 'YO' | Replace the bubble's text with a custom node (emoji, SVG, icon). |

The panel automatically clamps to viewport bounds (max-width: calc(100vw - 32px), max-height: calc(100vh - 120px)) so it never overflows on mobile.


Events — optional

You do not need to handle events to connect the widget to your wallet. The bridge is established by the signer adapter alone. Events are a one-way telemetry stream from the widget to your host page — useful for analytics, toast notifications, or post-deposit redirects, but entirely optional. Skip the onEvent prop if you don't need them.

Subscribe via onEvent={(e) => ...}. e.name is one of the strings below; e.data is typed per-event.

| name | data | When | |---|---|---| | session-ready | { address?, chainId?, partnerId? } | First successful handshake; only fires when address/chain actually change. | | deposit-started | { vault, asset } | User clicked Deposit, widget is preparing the txs. | | approve-submitted | { vault, asset?, hash } | ERC-20 approval tx broadcast. Only fires when allowance was insufficient. | | deposit-submitted | { vault, asset, hash } | Deposit tx broadcast. | | deposit-confirmed | { vault, asset, hash } | Deposit tx mined successfully. | | redeem-started | { vault } | User clicked Withdraw, widget is preparing. | | redeem-submitted | { vault, hash } | Redeem tx broadcast. | | redeem-confirmed | { vault, hash } | Redeem tx mined successfully. | | error | { kind: 'deposit' \| 'redeem', vault, message } | Any user-visible error after reset. |

vault is a short label like yoETH, yoUSD. hash is a 0x… tx hash.

<YoWidget
  signer={signer}
  onEvent={(e) => {
    if (e.name === 'deposit-confirmed') {
      analytics.track('yo_deposit', e.data)
      toast.success(`Deposited into ${e.data.vault}`)
    }
  }}
/>

Signer adapters

The SDK talks to your wallet through a SignerAdapter. This is the only required wiring between your app and the widget. Pre-built adapters cover the common stacks:

createWagmiAdapter({ config, onConnectRequest })

Best if your app already uses wagmi v2.

import { createWagmiAdapter } from '@yo-protocol/widget-sdk/adapters/wagmi'

const signer = createWagmiAdapter({
  config: myWagmiConfig,                  // same Config passed to <WagmiProvider>
  onConnectRequest: openConnectModal,     // e.g. from useConnectModal()
})

createViemAdapter({ getWalletClient, subscribe, onConnectRequest })

For apps using raw viem (no wagmi).

import { createViemAdapter } from '@yo-protocol/widget-sdk/adapters/viem'

const signer = createViemAdapter({
  getWalletClient: () => currentWalletClient,  // viem WalletClient | undefined
  subscribe: (notify) => yourStore.subscribe(() => notify({ address, chainId })),
  onConnectRequest: () => openWalletModal(),
})

Custom adapter

import type { SignerAdapter } from '@yo-protocol/widget-sdk'

const signer: SignerAdapter = {
  getAccount: () => '0x…',                      // current address or undefined
  getChainId: () => 8453,                        // current chainId or undefined
  sendTransaction: async (tx, meta) => {
    // tx = { to, data, value: string (decimal), chainId }
    // meta = { kind: 'approve'|'deposit'|'redeem', vault?, amount?, asset? }
    return await wallet.send({
      to: tx.to,
      data: tx.data,
      value: BigInt(tx.value),
      chainId: tx.chainId,
    }) as `0x${string}`
  },
  switchChain: async (chainId) => wallet.switchChain(chainId),
  subscribe: (notify) => {
    const unsub = wallet.on('change', (s) =>
      notify({ address: s.address, chainId: s.chainId }),
    )
    return unsub                                 // cleanup
  },
  onConnectRequest: () => wallet.openModal(),
}

Important: memoize adapters with useMemo so they don't re-create on every render — otherwise the widget bridge reattaches each time.


RPC configuration

The widget reads on-chain state (balances, vault stats) from its own PublicClients — it never uses your wallet's RPC. Defaults:

  • Ethereum: llamarpc → publicnode → cloudflare-eth
  • Base: mainnet.base.org → publicnode → llamarpc
  • Arbitrum: arb1.arbitrum.io → publicnode → llamarpc

All wrapped in viem's fallback() so one failing endpoint doesn't zero out reads (critical for multicall — errors on a chain silently return 0 shares).

Host override:

<YoWidget
  signer={signer}
  rpcUrls={{
    1: 'https://eth-mainnet.g.alchemy.com/v2/<key>',
    8453: [
      'https://base-mainnet.g.alchemy.com/v2/<key>',
      'https://base-mainnet.infura.io/v3/<key>',
    ],
  }}
/>

Host RPCs are stacked in front of the SDK defaults, not instead of them — so partner endpoints are tried first, public RPCs remain as safety nets.


Theming

The widget is cross-origin so host CSS can't reach inside. We expose a narrow, validated theme contract forwarded via the iframe URL:

<YoWidget
  theme={{
    primary: '#ff00aa',      // CTA color (default #ccff00 lime)
    background: '#07070a',   // widget body bg (default #000)
    card: '#151520',         // card bg (default #0d0d0d)
    radius: 1.5,             // rem, used for card/button rounding
  }}
/>

Rules:

  • Colors must match /^#(RGB|RRGGBB|RGBA|RRGGBBAA)$/ — rejected otherwise (CSS-injection safe).
  • Radius must be a number; passed as rem.
  • Unrecognized keys are ignored.

What IS overridable

| Layer | How | |---|---| | Iframe wrapper, launcher bubble, panel shell | Host CSS / className / style / bubbleStyle | | Accent color, background, card bg, corner radius | theme prop |

What IS NOT overridable (by design)

  • Widget internal fonts, spacing, button shape, deposit-flow layout
  • Per-vault or per-screen styling

This is intentional: the iframe boundary is the security + update-control boundary. If you need a deeper hook (e.g. brand font, secondary color, compact mode), open an issue — these become new theme fields in a few lines.


Security model

  1. Origin check — every postMessage must come from widgetOrigin. Anything else is silently dropped.
  2. Contract allowlist — the host-side adapter refuses to sign any tx whose to is not a known YO contract (vaults, gateway, redeemer, or whitelisted underlying tokens). Returns TO_NOT_ALLOWLISTED.
  3. Chain match — widget must request the target chain via switch-chain-request before the adapter will sign on it. Mismatch → WRONG_CHAIN.
  4. Sandboxed iframesandbox="allow-scripts allow-same-origin allow-forms allow-popups". Cannot navigate the host, read its cookies, or touch its DOM.
  5. Protocol versioning — host + widget both assert PROTOCOL_VERSION. Mismatch shows an in-iframe upgrade message.
  6. 10-min tx timeout — any pending sign request resolves or times out within 10 minutes; the widget resets and lets the user retry.

Protocol reference

For adapter authors and debugging. Every message: { v: 1, id, type, payload }.

Host → Widget

| type | payload | Meaning | |---|---|---| | init | { chains, address?, chainId?, partnerId?, locale?, rpcUrls? } | Sent once on widget ready. | | account-changed | { address? } | Wallet connected/disconnected/switched account. | | chain-changed | { chainId? } | Wallet switched networks. | | tx-result | { id, hash? \| error? } | Response to send-transaction-request. | | switch-chain-result | { id, ok, error? } | Response to switch-chain-request. |

Widget → Host

| type | payload | Meaning | |---|---|---| | ready | { version } | Widget booted. | | connect-request | {} | User clicked connect; host opens its wallet modal. | | switch-chain-request | { id, chainId } | Before signing on a different chain. | | send-transaction-request | { id, tx, meta } | Sign + send. tx = { to, data, value (string), chainId }. | | resize | { height } | Host adjusts iframe height. | | event | { name, data? } | Telemetry (see Events). | | navigate | { href } | Open external link in new tab (iframe can't). |

The pre-built adapters handle every message above. You only touch the protocol directly if you're writing a custom adapter for a non-EVM stack or debugging the bridge.


Troubleshooting

| Symptom | Likely cause | Fix | |---|---|---| | Widget stuck loading / black iframe | Bridge handshake didn't complete | Confirm widgetOrigin matches the URL your iframe actually loads. Check devtools for postMessage drops. | | "Out of date" banner inside widget | Host-side SDK is older than the deployed widget protocol | npm update @yo-protocol/widget-sdk | | "Refusing to sign" wallet error | Tx to not on the YO allowlist | Upgrade @yo-protocol/widget-sdk — allowlist ships with the package. | | yoGOLD / Ethereum-only vaults invisible in positions | Default public Ethereum RPC rate-limited | Pass your own rpcUrls={{ 1: 'https://eth-mainnet.g.alchemy.com/v2/<key>' }} | | Events fire multiple times in dev | React StrictMode double-mount | Harmless — session-ready dedupes internally; ignore in production. | | Deposit tx rejected with WRONG_CHAIN | Wallet on different chain than vault | Widget will auto-request a chain switch before retrying; accept the wallet prompt. | | Launcher panel stretches off screen on mobile | Viewport too small | The panel is already clamped to calc(100vw - 32px) / calc(100vh - 120px). For tiny screens, pass smaller panelWidth / panelHeight. |


One-shot LLM prompt template

Paste this to an LLM along with your app's framework + wallet stack and it should produce a working integration:

You are integrating the YO Protocol widget into a {FRAMEWORK} app that
uses {WALLET_STACK} for wallet connection.

Goals:
- Install @yo-protocol/widget-sdk
- Render either <YoWidget /> (inline) or <YoWidgetLauncher /> (floating)
  with a signer adapter matching our wallet stack. Choose based on whether
  the widget is a permanent page feature or ambient helper.
- Wire the connect button to openConnectModal (or equivalent)
- Pass our Alchemy RPC URLs via the rpcUrls prop
- (Optional) Log lifecycle events via onEvent for analytics — events are
  not required for the bridge to work.

Constraints:
- The widget runs inside an iframe; we never import anything from the
  widget app itself, only from @yo-protocol/widget-sdk.
- The SignerAdapter returned by createWagmiAdapter / createViemAdapter
  must be stable across renders (wrap in useMemo).
- rpcUrls is stacked in front of the SDK's fallback list, not instead
  of it — we only need to provide chains we care to speed up.
- Use the `theme` prop (primary, background, card, radius) if we need
  to match our brand; deeper style overrides are not possible because
  the iframe is cross-origin.

Produce:
- The provider file (wagmi/query setup) if needed
- The page/component that renders <YoWidget /> or <YoWidgetLauncher />
- (Optional) A small events handler

Reference docs follow:
<paste the rest of this README here>

License

MIT