New:Introducing Socket Scanning for VS Code Marketplace Extensions.Learn more →
Get Started

@chaindaddy/apps

Package Overview
Dependencies
Maintainers
1
Versions
31
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@chaindaddy/apps

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

beta
latest
Source
npmnpm
Version
2.21.0
Version published
Weekly downloads
0
Maintainers
1
Weekly downloads
 
Created
Source

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.

FlavorWhat it isWhere it runs
WidgetA React component rendered on a token page, fed structured data through propsThe visitor's browser, inside the host page
RunnerA headless service that receives platform events by webhook and calls back through the Action APIYour 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).

FieldTypeRequiredDescription
schemaVersionstringyesAlways "1"
idstringyesname, 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>
namestringyesHuman-readable display name
versionstringyesSemantic version (major.minor.patch)
authorobjectyes{ name, url?, developerId }. developerId must equal the developer ID you submit with, or the server refuses the package (PACKAGE_DEVELOPER_MISMATCH)
descriptionstringyesShort description of widget functionality
typestringset itwidget, headless or full. Without it the schema requires both the widget fields and runner
entryComponentstringwidget, fullNamed export from the bundle that is the root React component
bundleUrlstringwidget, fullURL of the JS bundle. The operator rewrites it to its hosted copy; leave it empty in a package
bundleHashstringwidget, fullSHA-256 hex hash of the bundle. Rewritten to the hosted copy's hash
bundleSizenumberwidget, fullBundle size in bytes, at most 2,000,000. Rewritten to the hosted copy's size
runnerobjectheadless, fullWebhook endpoint, auth type, events and actions (see Runner SDK)
iconstringnoURL to widget icon for the dashboard
categorystringyesWhere 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)
requiredDataarrayyesData scopes the widget needs (see below)
configSchemaobjectnoJSON Schema (draft-07) for widget-specific settings
permissionsarrayyesPlatform permissions requested (see below)
minPlatformVersionstringyesMinimum platform version (semver)
supportedChainsarraynoChain keys supported. Omit for all-chain support
tagsarraynoFreeform tags for search/filtering
repositoryUrlstringnoURL to widget source repository
licensestringyesSPDX 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.

ScopeDescription
profile:basicSymbol, name, description, tags
profile:socialSocial links, verifications
market:pricePrice, volume, market cap
market:holdersHolder count, holder stats
token:ownershipOwner address, status, verification
token:metadataMetadata extensions, highlights URI
token:identityENS/SNS, social identities
chain:infoChain ID, chain name, explorer URL

Permissions

PermissionDescription
fetch:self-apiFetch to the platform's own API
storage:localScoped localStorage for the widget
clipboard:writeWrite to system clipboard
navigate:internalClient-side navigation within the platform
ui:fullscreenAsk the host to show the app fullscreen (fullscreen action)
iap:purchaseSell items from the token's store (iap-purchase action); see In-app purchases
iap:consumeSpend 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:userSend 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:connectConnect 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:signAsk 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

ExportDescription
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
MemoryStoreIn-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.

AppFolderWhat it is
Gallerycore-galleryImages, video and audio the creator hosts, with optional holders-only pieces
Community Pollscore-pollToken-weighted polls; each vote is an EIP-712 signature from the voter's wallet
Audio Playercore-anthemA compact player for an uploaded track or any audio URL, falling back to the token's anthem
Video Playercore-intro-videoAn uploaded video or any video URL, inline or full screen
Activity Feedcore-broadcastsThe token's recent broadcasts, highlights, media and polls
Community Wallcommunity-wallA coin's wall of posts, run by its creator: the setup, then the coin page frames the wall itself
Item Storeitem-storeThe coin's own items with a buy button, on its coin page or its own website; each sale is the coin's own
Sign a messagesign-a-messageA visitor signs a short message in the app's own name, on a coin page or the coin's own website
HTMLcore-htmlRich text, images and embeds, with a sanitizer between the owner and the page
Price Historycore-lightweight-chartsA self-hosted chart on TradingView's open-source Lightweight Charts
TradingView Chartcore-tradingviewTradingView's chart widgets: advanced chart, mini chart, ticker tape and more
3D Viewer3d-viewerAnimated 3D scenes in the token's colour, from a set of presets
CHAP Catchchap-catchA Canvas 2D arcade game by og_foundry: catch coins, dodge rugs
Neon Breakerneon-breakerA WebGL brick breaker by og_foundry whose colours follow the token
Enginecore-engineBoots 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.

The Community Wall's server, the wall of posts a coin's creator runs on their own hosting, with one-click deploys to Render and Vercel, is in servers/community-wall.

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
  • sign-message: wallet:sign and "embed": true: a signature in the app's own name, on a token page and on the coin's own website (our own build is apps/sign-a-message)
  • 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 (our own Item Store is apps/item-store)
  • 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:

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:

  • Be one ESM file, with no code splitting
  • Export the component named by entryComponent as a plain named function
  • Leave react, react/jsx-runtime and react-dom external. The host provides them, and they are the only bare imports allowed
  • Be at most 2,000,000 bytes
  • 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.

AreaRule
DOMAn 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.
NetworkWith 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).
StoragelocalStorage is scoped to app-<id>- (every instance of the app) and app-<id>@<instanceId>- (one instance).
GlobalssessionStorage, 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
  servers/community-wall/  # The Community Wall server a creator runs on their own hosting (its own package)
  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, except servers/community-wall, which is MIT under its own LICENSE.

Keywords

chaindaddy

FAQs

Package last updated on 24 Oct 2026

Related posts