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

@n.exchange/widget

v0.2.1

Published

Embeddable n.exchange widget for partner sites - pair/amount selection through order completion, attributed via a widget-scoped API key and referral code.

Readme

@n.exchange/widget

Embeddable n.exchange exchange widget for partner sites. Handles pair/amount selection through order completion inside an iframe pointed at https://n.exchange/embed, attributed to the partner via a widget-scoped API key and referral code (see the partner dashboard's "Widget" tab to create one).

Widget keys are deliberately restricted server-side: they can create and look up orders, but never list order history or be used with a referral code other than the one they were locked to at creation. A leaked key's blast radius is bounded to that.

Prerequisites

You need a widget-scoped API key and the referral code it's locked to. Get both from your n.exchange account:

  1. Log in at n.exchange with a business account.
  2. Go to Account → Widget (/user/profile, then the "Widget" tab in the sidebar - business accounts only).
  3. Pick one of your referral codes and create a widget key for it. The raw key is shown once - copy it immediately, it can't be viewed again (only re-created).

Keep the key and code together - the key only ever works with the one referral code it was created for; using a different code returns a 400.

Install

npm install @n.exchange/widget

Quick start (React)

Full working example - a page with nothing else on it:

import { NexchangeWidget } from '@n.exchange/widget/react'

export default function ExchangePage() {
    return (
        <NexchangeWidget
            apiKey={process.env.NEXT_PUBLIC_NEXCHANGE_API_KEY!}
            referralCode={process.env.NEXT_PUBLIC_NEXCHANGE_REFERRAL_CODE!}
            onReady={() => console.log('widget loaded')}
            onOrderCreated={orderId => console.log('order created:', orderId)}
            onStatusChanged={(orderId, status) => console.log(orderId, 'is now', status)}
            onError={message => console.error('widget error:', message)}
            style={{ width: '100%', maxWidth: 480 }}
        />
    )
}

That's the whole integration - no extra setup, no API calls of your own to wire up. The component fills its container width and reports its own height, so just constrain the width with style/className as shown above.

Quick start (plain HTML / any other framework)

Full working example - save as an .html file and open it directly:

<!doctype html>
<html>
  <body>
    <nexchange-widget
      api-key="YOUR_WIDGET_API_KEY"
      referral-code="YOUR_REFERRAL_CODE"
      style="display: block; max-width: 480px"
    ></nexchange-widget>

    <script type="module" src="https://unpkg.com/@n.exchange/widget"></script>
    <script>
      document.addEventListener('DOMContentLoaded', () => {
        const widget = document.querySelector('nexchange-widget')
        widget.addEventListener('ready', () => console.log('widget loaded'))
        widget.addEventListener('order-created', e =>
          console.log('order created:', e.detail.orderId),
        )
        widget.addEventListener('status-changed', e =>
          console.log(e.detail.orderId, 'is now', e.detail.status),
        )
        widget.addEventListener('error', e => console.error('widget error:', e.detail.message))
      })
    </script>
  </body>
</html>

<nexchange-widget> is a real custom element - it works with any framework (Vue, Svelte, Angular, plain HTML) exactly the same way, since customElements.define registers it globally once the script loads.

Props / attributes

| React prop | HTML attribute | Required | Description | | ---------------- | ----------------- | -------- | ------------------------------------------------ | | apiKey | api-key | yes | Widget-scoped API key, from the partner dashboard | | referralCode | referral-code | yes | The referral code the key is locked to | | address | address | no | Pre-fill the withdraw address | | baseUrl | base-url | no | Override the embed origin (defaults to https://n.exchange) | | showSupportChat| show-support-chat | no | Show n.exchange's own support chat bubble inside the widget (defaults to false - hidden unless you opt in) | | onReady | ready event | no | The widget finished loading | | onOrderCreated | order-created | no | (orderId: string) | | onStatusChanged| status-changed | no | (orderId: string, status: string) | | onError | error event | no | (message: string) |

status is one of the literal strings n.exchange's order model uses - note that some are multi-word with a space or hyphen, not SNAKE_CASE: INITIAL, UNCONFIRMED PAYMENT, PRE-RELEASE, PAID, RELEASED, COMPLETED, CANCELED, INITIATED REFUND, REFUNDED, REFUND FAILED, PENDING KYC. If you're switching on this value in your own UI, match these exactly.

Troubleshooting

  • Widget shows an error immediately on load - the API key and referral code don't match (a widget key only works with the one referral code it was locked to), the key was deactivated, or it was mistyped/truncated when copied. Re-check both values in the dashboard's Widget tab.
  • Nothing renders / blank space where the widget should be - open the browser console. If you see a CSP (frame-src) violation, your page's Content-Security-Policy needs to allow framing https://n.exchange (frame-src https://n.exchange or looser). If you see nothing, confirm the <script type="module"> tag actually loaded (network tab) and that <nexchange-widget> is on the page after it.
  • TypeScript can't find types for @n.exchange/widget/react - make sure your tsconfig.json's moduleResolution is bundler or node16/nodenext (not the legacy node), so subpath exports resolve correctly.
  • Widget doesn't resize when its content changes - this is handled automatically via a resize postMessage from the embed page; if you've wrapped the widget in a container with a fixed height, remove it and size only the width (see the Quick start examples above).

How it works

WidgetController (src/controller.ts) is the only place the actual protocol lives - both the <nexchange-widget> custom element (src/vanilla.ts) and the <NexchangeWidget> React component (src/react/) are thin wrappers over the same class:

  1. It creates an iframe pointed at {baseUrl}/embed (n.exchange's own order-flow UI, calling n.exchange's v2 API same-origin - not the partner's page).
  2. Once the iframe posts { type: 'ready' }, the controller replies once with { type: 'init', apiKey, referralCode, address? }, targeted at the embed origin specifically (never a '*' wildcard), so the API key can't leak to an unexpected origin even if the iframe's contentWindow somehow pointed elsewhere.
  3. It listens for orderCreated / statusChanged / error / resize messages from the iframe and translates them into the callbacks/events documented above, only accepting messages whose event.source is that same iframe's contentWindow.

This mirrors how Stripe Elements / Plaid Link isolate a sensitive credential inside a same-origin iframe rather than handing the partner's own page JS realm anything beyond the one-time handshake.

Development

npm install
npm test
npm run build

package.json's overrides.rollup pins rollup to @rollup/wasm-node instead of the default native-binary build. tsup's bundling depends on rollup internally, and rollup's native binary crashes with a Bus error in some sandboxed/containerized environments (a documented class of issue for @rollup/rollup-* native packages). The WASM build is slightly slower for large projects but functionally identical, and this package is small enough that the difference is negligible - remove the override if you confirm your CI/build environment doesn't need it.

License

MIT © n.exchange