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

@chaindaddy/apps

v2.14.1

Published

Beta. Crown Apps SDK and runner framework — build React widgets and webhook-driven runners for Chain Daddy token pages.

Readme

Crown Apps

Beta. The SDK, the CLI and the developer program are in beta: interfaces may change between releases. Install with the beta tag.

Crown Apps are third-party extensions for Chain Daddy token pages. This repository is the SDK and runner framework you build them with: a TypeScript contract for widgets (React components rendered on a token page) and a zero-dependency helper library for runners (headless services driven by webhooks).

It is for external developers who want to ship something onto a token page — a chart, a swap box, a poll — or run a backend that reacts to platform and on-chain events.

| Flavor | What it is | Where it runs | |--------|------------|---------------| | Widget | A React component rendered on a token page, fed structured data through props | The visitor's browser, inside the host page | | Runner | A headless service that receives platform events by webhook and calls back through the Action API | Your own infrastructure |

The step-by-step walkthrough is Getting started on the docs site. This README is the reference.

Install

npm install @chaindaddy/apps@beta

Requires Node.js 20+. React 18 is a peer dependency for widget development.

import type { AppWidgetProps } from '@chaindaddy/apps/types/widget';  // widget contract
import { createWebhookHandler } from '@chaindaddy/apps/runner';       // runner SDK

You do not need this package to ship an app. A project made by chaindaddy app init carries its own copy of the widget types (src/widget-types.ts), and its runner checks webhook signatures with node:crypto.

Quick Start: Widget

A widget is one React component plus a capp.json manifest. It receives the token's data through props. The quickest start is a template: chaindaddy app init --template game|interactive|community copies a complete app from templates/.

import type { AppWidgetProps } from '@chaindaddy/apps/types/widget';

interface MySettings {
  showVolume?: boolean;
}

export function MyWidget({
  appData,
  settings,
  theme,
  onAction,
}: AppWidgetProps<MySettings>) {
  return (
    <div style={{ background: theme.cardBackground, color: theme.textPrimary }}>
      <h3>{appData.symbol} - {appData.tokenName}</h3>
      <p>Price: ${appData.market.price?.toFixed(6) ?? 'N/A'}</p>
      {settings.showVolume && (
        <p>24h Vol: ${appData.market.volume24h?.toLocaleString() ?? 'N/A'}</p>
      )}
      <button onClick={() => onAction({ type: 'toast', message: 'Hello!', variant: 'success' })}>
        Toast
      </button>
    </div>
  );
}

Export a plain named function, as above. The host loads the export named by entryComponent (or the default export if there is none) and calls it as a function component. A component wrapped in React.memo, forwardRef or lazy does not load, and neither does export default defineWidget({...}).

Pair it with a manifest that names the export:

{
  "schemaVersion": "1",
  "id": "your-developer-id/my-widget",
  "name": "My Widget",
  "version": "1.0.0",
  "author": { "name": "Your Name", "developerId": "your-developer-id" },
  "description": "One-line description of what the widget does.",
  "type": "widget",
  "entryComponent": "MyWidget",
  "bundleUrl": "",
  "bundleHash": "",
  "bundleSize": 0,
  "category": "utility",
  "requiredData": ["profile:basic", "market:price"],
  "permissions": [],
  "minPlatformVersion": "1.0.0",
  "license": "MIT"
}
  • schemaVersion is required.
  • Always set type. Without it the schema applies the widget and the runner rules together.
  • author.developerId must be the developer ID shown on your portal Account page, the one you submit with.
  • Leave the three bundle fields empty. chaindaddy app pack fills in the hash and size, and the operator sets the URL when it hosts the bundle.

Then build a single-file bundle and submit it; see Publishing.

Full working example: examples/hello-widget/.

Quick Start: Runner

A runner is an HTTP endpoint that verifies a webhook signature, handles the event, and returns a result. The SDK supplies signature verification, idempotency, and an Action API client.

import {
  createWebhookHandler,
  createActionClient,
  withIdempotency,
  MemoryStore,
} from '@chaindaddy/apps/runner';

const client = createActionClient({
  apiBaseUrl: process.env.API_BASE_URL!,
  appId: process.env.APP_ID!,
  secret: process.env.WEBHOOK_SECRET!,
});

export const handle = createWebhookHandler({
  secret: process.env.WEBHOOK_SECRET!,
  onEvent: withIdempotency(async (event) => {
    switch (event.eventType) {
      case 'token.claimed':
        await client.storage.set(event.crownId, 'claimedAt', event.timestamp);
        break;
      case 'market.price.threshold':
        await client.storage.set(event.crownId, 'lastAlert', Date.now().toString());
        break;
    }
    return { status: 'ok' };
  }, new MemoryStore()),
});

MemoryStore is for development. In production, back idempotency with durable storage — the examples use DynamoDB and Cloudflare KV.

Deployable templates: examples/express-runner/, examples/lambda-runner/, examples/cloudflare-runner/.

Working in This Repository

Clone it to build the bundled widgets, run the SDK tests, or develop a widget against the source types.

npm install            # install dependencies
npm run build          # build all widget bundles + the runner SDK
npm test               # run the runner SDK test suite
npm run typecheck      # type-check the widget sources
npm run lint           # eslint, zero warnings tolerated
npm run new:widget     # scaffold a new widget under apps/
npm run validate:examples  # validate every capp.json against the schema

npm run build writes widget bundles to dist/{appId}/, the compiled runner SDK to dist/runner/, and a manifest index to dist/index.json. It reports each bundle's size against its tier cap, so an oversized bundle fails loudly.

Manifest Format (capp.json)

Every app must include a capp.json in its directory root, validated against schema/capp.schema.json. The server validates every submission against the same schema and refuses one that fails (PACKAGE_MANIFEST_INVALID).

| Field | Type | Required | Description | |-------|------|----------|-------------| | schemaVersion | string | yes | Always "1" | | id | string | yes | name, or <developerId>/<name> where developerId is your developer ID. Each part is lowercase letters, digits and hyphens, 3-64 characters. chaindaddy app init uses <developerId>/<dir> | | name | string | yes | Human-readable display name | | version | string | yes | Semantic version (major.minor.patch) | | author | object | yes | { name, url?, developerId }. developerId must equal the developer ID you submit with, or the server refuses the package (PACKAGE_DEVELOPER_MISMATCH) | | description | string | yes | Short description of widget functionality | | type | string | set it | widget, headless or full. Without it the schema requires both the widget fields and runner | | entryComponent | string | widget, full | Named export from the bundle that is the root React component | | bundleUrl | string | widget, full | URL of the JS bundle. The operator rewrites it to its hosted copy; leave it empty in a package | | bundleHash | string | widget, full | SHA-256 hex hash of the bundle. Rewritten to the hosted copy's hash | | bundleSize | number | widget, full | Bundle size in bytes, at most 2,000,000. Rewritten to the hosted copy's size | | runner | object | headless, full | Webhook endpoint, auth type, events and actions (see Runner SDK) | | icon | string | no | URL to widget icon for the dashboard | | category | string | yes | Where the widget picker lists it. One of: content (text, images, links, embeds), market (prices, charts, trades, swaps), media (video, streams, music, 3D), games, interactive (interactive media and experiences that are not games), art (visual art, illustration, generative work, collections), social (feeds, tips, community), governance (polls, votes), analytics (holder and on-chain stats), utility (tools that do a job), custom (anything else, listed as Other) | | requiredData | array | yes | Data scopes the widget needs (see below) | | configSchema | object | no | JSON Schema (draft-07) for widget-specific settings | | permissions | array | yes | Platform permissions requested (see below) | | minPlatformVersion | string | yes | Minimum platform version (semver) | | supportedChains | array | no | Chain keys supported. Omit for all-chain support | | tags | array | no | Freeform tags for search/filtering | | repositoryUrl | string | no | URL to widget source repository | | license | string | yes | SPDX license identifier |

Data Scopes

Declare only what your widget reads. requiredData is shown to the creator when they install the app and is checked in review. The host does not filter appData by it today.

| Scope | Description | |-------|-------------| | profile:basic | Symbol, name, description, tags | | profile:social | Social links, verifications | | market:price | Price, volume, market cap | | market:holders | Holder count, holder stats | | token:ownership | Owner address, status, verification | | token:metadata | Metadata extensions, highlights URI | | token:identity | ENS/SNS, social identities | | chain:info | Chain ID, chain name, explorer URL |

Permissions

| Permission | Description | |------------|-------------| | fetch:self-api | Fetch to the platform's own API | | storage:local | Scoped localStorage for the widget | | clipboard:write | Write to system clipboard | | navigate:internal | Client-side navigation within the platform | | ui:fullscreen | Ask the host to show the app fullscreen (fullscreen action) | | iap:purchase | Sell items from the token's store (iap-purchase action); see In-app purchases | | iap:consume | Spend the signed-in viewer's own consumable, with no server (iap-consume action): on their tap, and only items the store's owner lets apps spend. See In-app purchases | | notify:user | Send the signed-in user a notification, in-app and push (notify action): at most 5 a day, the user can mute the app, never by text or email | | net:connect | Connect to your own server: the wss:///https:// origins in network.origins (at most 8, exact hostnames), nothing else. Requests carry Origin: null; authenticate the visitor with the session-token action. See docs.chaindaddy.io › Your own server | | wallet:sign | Ask the viewer's wallet to sign EIP-712 typed data (sign-typed-data action); the host shows its own confirmation sheet first |

Widget API

Props

Every widget receives AppWidgetProps:

interface AppWidgetProps<TSettings = Record<string, unknown>> {
  appData: AppDataContext;         // Token profile data (symbol, market, social, etc.)
  settings: TSettings;             // Widget-specific settings from configSchema
  widgetId: string;                // Widget identifier matching manifest id
  isOwner: boolean;                // Whether connected wallet owns this crown
  connectedAddress: string | null; // Connected wallet address
  chainKey: string;                // Chain key for the current profile (e.g., "arb")
  theme: AppWidgetTheme;           // Host page theme colors
  onAction: (action: AppWidgetAction) => void; // Dispatch actions
  breakpoint?: 'compact' | 'standard' | 'wide'; // Declared; the host does not pass it yet
}

appData

  • symbol, tokenName, description, tags — basic profile info
  • chainKey, chainId, chainName — chain info
  • tokenAddress, owner, status, tokenId — on-chain state (owner and status also arrive as crownOwner and crownStatus)
  • market — { price, volume24h, marketCap, holders, totalSupply, circulatingSupply }
  • socialLinks — { website, xdotcom, telegram, discord, github, whitepaper }
  • verifications — per-platform verification records
  • metadata — extensible key-value store

Theme

interface AppWidgetTheme {
  mode: 'light' | 'dark';
  accentColor: string;
  backgroundColor: string;
  cardBackground: string;
  textPrimary: string;
  textSecondary: string;
  textMuted: string;
  borderColor: string;
}

Actions

Widgets dispatch actions to the host via onAction():

type AppWidgetAction =
  | { type: 'navigate'; path: string }        // Client-side navigation
  | { type: 'clipboard'; text: string }        // Copy to clipboard (needs clipboard:write)
  | { type: 'track'; event: string; data?: Record<string, unknown> }  // Analytics
  | { type: 'toast'; message: string; variant: 'success' | 'error' | 'info' };  // Notification

The full union also covers api-call, connect-wallet, sign-typed-data, transaction, quote, open-settings, fullscreen, iap-purchase, iap-consume and paid-file. Every shape is declared in types/widget.ts.

iap-purchase sells an item from the token's store: the host opens its own purchase sheet and resolves { status: 'confirmed' | 'paid' | 'cancelled' | 'failed', orderId?, receipt? }. The app never touches the wallet or the amounts. Reading the catalog, the viewer's items and verifying receipts on a game server are in In-app purchases.

Lifecycle Hooks

Widgets can optionally implement lifecycle hooks:

interface AppWidgetLifecycleHooks {
  onWidgetMount?(context: WidgetLifecycleContext): void | Promise<void>;
  onWidgetUnmount?(context: WidgetLifecycleContext): void;
  onDataUpdate?(prevData: AppDataContext, nextData: AppDataContext): void;
  onSettingsChange?(prevSettings: unknown, nextSettings: unknown): void;
  onVisibilityChange?(visible: boolean): void;
}

The lifecycle context provides widgetId, appData, scopedStorage, and an abortController for async cleanup.

Widgets can also export renderStatic() to support server-side rendering.

Settings

Widgets declare configurable settings with configSchema in the manifest — a standard JSON Schema (draft-07) object. Crown managers adjust these through the widget dashboard, and the values arrive as the settings prop.

{
  "configSchema": {
    "type": "object",
    "properties": {
      "greeting": {
        "type": "string",
        "description": "Custom greeting prefix",
        "default": "Hello"
      },
      "showMarketData": {
        "type": "boolean",
        "description": "Whether to display market data",
        "default": true
      }
    }
  }
}

In your component, read settings.greeting and settings.showMarketData, typed against your own settings interface.

Runner SDK

Exports

| Export | Description | |--------|-------------| | verifyWebhook(secret) | Express middleware for signature verification | | verifySignature(secret, body, sig) | Sync HMAC verification (Node.js) | | verifySignatureAsync(secret, body, sig) | Async HMAC verification (all runtimes) | | createWebhookHandler(options) | Framework-agnostic webhook handler | | createActionClient(options) | Action API client factory | | withIdempotency(handler, store) | Idempotency wrapper for event handlers | | MemoryStore | In-memory idempotency store (development) | | validateManifest(manifest) | Runtime manifest validation | | signRequest(secret, body) | Sign outgoing Action API requests | | isRateLimited(response), getWaitTime(response) | Rate-limit helpers for Action API responses |

Event and action payload types ship alongside them: AppEventType, AppEventEnvelope, AppEvent, ActionScope, and the per-event interfaces.

Event Names and Delivery

The full event list (and payload types) is exported as APP_EVENT_TYPES / AppEventType. App-lifecycle events use the wire names token.app.enabled, token.app.disabled, and token.app.configured; the short forms app.enabled, app.disabled, and app.configured are also accepted when declaring runner.events in a manifest and map to the same wire events.

The backend delivers an event only to runners whose manifest subscribes to it. An empty or absent subscription list receives everything, and test.ping from runner verification is always delivered regardless of the list. Filtering on event.eventType in your handler, as the switch above does, remains good defensive practice.

Apps

The apps in apps/ are the ones Chain Daddy serves to every token page, free on every plan. Each is a normal Crown App, built and checked the same way as yours, so they double as working references.

| App | Folder | What it is | |-----|--------|------------| | Gallery | core-gallery | Images, video and audio the creator hosts, with optional holders-only pieces | | Community Polls | core-poll | Token-weighted polls; each vote is an EIP-712 signature from the voter's wallet | | Audio Player | core-anthem | A compact player for an uploaded track or any audio URL, falling back to the token's anthem | | Video Player | core-intro-video | An uploaded video or any video URL, inline or full screen | | Activity Feed | core-broadcasts | The token's recent broadcasts, highlights, media and polls | | HTML | core-html | Rich text, images and embeds, with a sanitizer between the owner and the page | | Price History | core-lightweight-charts | A self-hosted chart on TradingView's open-source Lightweight Charts | | TradingView Chart | core-tradingview | TradingView's chart widgets: advanced chart, mini chart, ticker tape and more | | 3D Viewer | 3d-viewer | Animated 3D scenes in the token's colour, from a set of presets | | CHAP Catch | chap-catch | A Canvas 2D arcade game by og_foundry: catch coins, dodge rugs | | Neon Breaker | neon-breaker | A WebGL brick breaker by og_foundry whose colours follow the token | | Engine | core-engine | Boots Godot and Unity games; not an app anyone adds |

FLUX GP, our multiplayer racing game, lives in its own repository, flux-gp-kit, because its client ships with the game server that runs its races, store and prizes.

Examples

Widgets

  • hello-widget: minimal reference widget covering appData, theme, settings, breakpoint, and onAction
  • price-alert-widget: storage:local permission and the onDataUpdate lifecycle hook
  • iap-store: a white-label item store, in your own game (a server holding a store key) and as a Crown App (iap:purchase), with a copyable iap-client.ts helper
  • 3d-viewer: bundling a third-party rendering library (Three.js) inside a widget
  • godot-game: a Godot engine app that saves the player's progress, signs them in and sells the token's store items, through window.chaindaddy
  • unity-bridge: the same for a Unity WebGL game: a .jslib and a C# script

Runners

Each runner example has its own README with deployment steps and a .env.example.

Publishing

The walkthrough is Getting started. In short:

  1. Join the developer program at https://chaindaddy.io/_developer with the wallet you will submit from. Your developer ID is on the portal's Account page.
  2. Test the bundle locally.
  3. Submit it, with the CLI or as an open-source app from the portal.
  4. When the automatic checks pass, send it for review.

Ship with the CLI

Your source stays on your machine; the operator hosts the built bundle. Install the CLI (beta):

npm install -g @chaindaddy/cli@beta
chaindaddy app login                          # sign in with the wallet that joined the developer program
chaindaddy app init widget yield-tracker      # id defaults to <your developer id>/yield-tracker
cd yield-tracker
npm install
npm run build && npm run pack && npm run sign && npm run submit

npm run submit waits for the automatic checks. When they pass it prints chaindaddy app request-publish <id>; run that to send the build for review. chaindaddy app submit --request-review does both in one step.

The individual steps:

chaindaddy app validate
chaindaddy app build
chaindaddy app scan                  # the checks the server's automated scan runs
chaindaddy app pack                  # dist/<name>-<version>.capp
chaindaddy app sign --in-place       # signs the newest package in dist/
chaindaddy app submit --watch
chaindaddy app request-publish <id>

The operator extracts the package, runs the automated scan, hosts the bundle on its apps CDN with immutable cache headers, and rewrites bundleUrl, bundleHash and bundleSize to the hosted copy.

Submit an open-source app

On the developer portal: Apps → New app → Open-source app. Give the manifest, a public bundle URL and a public repository URL. The server fetches the bundle, checks it against bundleHash, and hosts its own copy. Token pages load bundles only from the apps CDN, so the URL you give is only where the server fetches from.

Review and going live

A submission is pending while the automatic checks run, and auto_failed if they fail. request-publish moves a passing build to in_review, and a reviewer sets it to approved or rejected.

Approval makes that build the live version, and the app appears in the app list on every token page, served under its slug. The previous approved build stays live until the next one is approved. Creators install apps from the Apps tab on their token page; a third-party app needs the token owner on Pro+ or above.

Building a Bundle Yourself

chaindaddy app build does this for you. With your own bundler, the bundle must:

  1. Be one ESM file, with no code splitting
  2. Export the component named by entryComponent as a plain named function
  3. Leave react, react/jsx-runtime and react-dom external. The host provides them, and they are the only bare imports allowed
  4. Be at most 2,000,000 bytes
  5. Not use eval(), new Function(), sessionStorage or document.cookie
import { build } from 'esbuild';
import { createHash } from 'crypto';
import { readFileSync } from 'fs';

await build({
  entryPoints: ['src/index.tsx'],
  bundle: true,
  format: 'esm',
  outfile: 'dist/bundle.js',
  external: ['react', 'react-dom', 'react/jsx-runtime'],
  jsx: 'automatic',
  minify: true,
});

// Generate integrity hash
const bundle = readFileSync('dist/bundle.js');
console.log(`Bundle hash: ${createHash('sha256').update(bundle).digest('hex')}`);
console.log(`Bundle size: ${bundle.length} bytes`);

Testing locally

Open https://chaindaddy.io/_dev/widgets and use "Test your own bundle": pick dist/bundle.js and capp.json. It renders the widget through the real loader and sandbox with sample data, and nothing is uploaded. Check both theme modes and your actions there. More in Test locally.

Security Model

Widgets are real React components rendered in the host page, not in iframes. The host checks each bundle's SHA-256 against the manifest before it runs it, and the token page's CSP loads bundles only from the apps CDN.

These are the sandbox rules. Today the DOM rule is enforced live on the page; the others are checked by the automated scan at submission and in review. The name in parentheses is what a breach is reported as.

| Area | Rule | |------|------| | DOM | An app may not leave a node in <body> or <head> that it added with a direct DOM call. One still there when the task ends is removed and reported (dom_injection). A node added and removed in the same task, such as a measuring probe, is fine. replaceChild / replaceChildren on <body> or <head> are refused. <style> and <link rel="stylesheet"> in <head> are allowed, for CSS-in-JS. For floating UI, render a React portal (createPortal) from your own tree; no marker attribute is honoured. | | Network | With fetch:self-api, only the platform API: /api/ on the page origin or the API origin (unauthorized_fetch). credentials: 'include' is refused (credentialed_request); calls go with credentials: 'omit' and carry X-App-ID. Authorization, Cookie, Proxy-Authorization, X-Forwarded-For, X-Forwarded-Host and X-Real-IP are stripped (blocked_header). | | Storage | localStorage is scoped to app-<id>- (every instance of the app) and app-<id>@<instanceId>- (one instance). | | Globals | sessionStorage, eval and Function are blocked (blocked_global). document.cookie reads '' and writes are dropped (cookie_access). window.location is read-only (location_set). |

A report names exactly one app: the one whose own code, or a library it bundled, made the call. Nodes from the host, a browser extension or another app are never reported. First-party apps run under the same rules, but their reports never count toward disabling. Reports reach you on the Violations tab of your app's page on the portal. An app reported by many distinct visitors can be disabled automatically, and then drops off token pages.

The docs site has the same rules: Sandbox rules. Read SECURITY.md for runner authentication, permission scopes, approval gates, rate limits, storage isolation and audit logging.

Repository Structure

chaindaddy-apps/
  types/widget.ts          # Widget contract — props, theme, actions, lifecycle
  runner/                  # Runner SDK (headless app helpers)
    src/
      types/               # Event, action, manifest types
      crypto/              # HMAC signing and verification
      client/              # Action API client
      idempotency/         # Event deduplication
      middleware/          # Express + generic webhook handlers
      validation/          # Manifest validator
      __tests__/           # SDK tests
  apps/                    # Our apps, served to every token page (core-engine boots engine apps)
  templates/               # Starter apps for `chaindaddy app init --template` (shared/ + sync script)
  examples/                # Reference widgets and deployable runner templates
  schema/
    capp.schema.json       # JSON Schema for capp.json validation
  docs/                    # CLI reference, package format, agentic usage, verifying tokens (+ vectors/)
  scripts/                 # Build, scaffold, and validation tooling

Contributing

Bug reports and changes to the SDK, schema, examples and docs are welcome — see CONTRIBUTING.md and CODE_OF_CONDUCT.md. Apps are submitted with the CLI or from the portal, not by merge request. To report a security issue, follow the disclosure process in SECURITY.md.

License

Apache-2.0