Skip to content

JavaScript SDK ​

@chaindaddy/js puts Chain Daddy on any website. Today it does Sign in with Chain Daddy: a button, a popup, and a callback page. Every approval happens on chaindaddy.io, in the popup; your page only receives the result.

From a script tag ​

html
<script src="https://js.chaindaddy.io/v1/cd.js"></script>

It defines window.ChainDaddy and the <cd-signin-button> element.

URLCachingUse
https://js.chaindaddy.io/v1/cd.js5 minutesthe current 1.x build: fixes reach your site without a change
https://js.chaindaddy.io/v<version>/cd.jsforeverone exact version, for Subresource Integrity
https://js.chaindaddy.io/v<version>/cd.js.sriforeverits sha384-… integrity value
https://js.chaindaddy.io/v1/cd.json5 minutes{"version","integrity"} of the current build, to print a pinned tag

To pin a version with Subresource Integrity, read the .sri file next to it and add both attributes:

html
<script src="https://js.chaindaddy.io/v0.1.0/cd.js"
        integrity="sha384-…" crossorigin="anonymous"></script>

Every file is served with Access-Control-Allow-Origin: * and Cross-Origin-Resource-Policy: cross-origin, so it loads under SRI and on pages that send COEP.

From npm ​

sh
npm install @chaindaddy/js
ts
import { signIn, handleCallback, exchangeCode } from '@chaindaddy/js/signin';
import { defineSignInButton } from '@chaindaddy/js';

The package is ESM with type declarations, and safe to import where there is no DOM (a server render). Each release is published with npm provenance from gitlab.com/opencrown/chaindaddy-js.

The API ​

signIn(options) opens the Chain Daddy popup and resolves with { code, codeVerifier, state, nonce, issuer, clientId, redirectUri } once the person approves. Call it from a click handler, so the browser lets the popup open. If the popup is blocked, the page goes to Chain Daddy instead and signIn resolves null; your callback page then finishes.

Option
clientIdyour client id: the URL of your client.json, or a registered cdc_ id
redirectUriyour callback page, exactly as your client lists it, on the same origin as the page with the button
scopedefault openid; add wallets, profile
claimsan OpenID Connect claims request (holdings of your coin, crowns), as an object
signalan AbortSignal to stop waiting. Closing the popup does not cancel: a browser can report a popup closed when it is not

handleCallback() runs on your callback page. In the popup it passes the answer back to your page and closes the popup. After a redirect it returns the same result signIn would have.

exchangeCode(result) redeems the code at the token endpoint from the browser (a public client: no secret) and returns { id_token, … }.

Your server must verify the ID token before it trusts anything in it: ES256, keys at https://api.chaindaddy.io/api/v2/oauth/jwks, and check iss, aud (your client id), typ (signin+v1), exp and the nonce. See Verifying an ID token yourself. A token checked only in the browser proves nothing.

API types ​

The package also carries TypeScript types for the public API contract: every stable operation, with its parameters and responses. They are types only, so they add nothing to your bundle. Call the API with fetch.

ts
import type { paths, components } from '@chaindaddy/js/api';

type Order = components['schemas']['IapOrder'];
type Sites = paths['/api/v2/public/crowns/{chain}/{crownId}/sites']['get']['responses'][200]['content']['application/json'];

The button ​

html
<cd-signin-button
  client-id="https://you.example/client.json"
  redirect-uri="https://you.example/callback"
  scope="openid wallets"
  theme="light"
  text="signin"></cd-signin-button>

<script>
  document.querySelector('cd-signin-button').addEventListener('cd-signin', async (e) => {
    const tokens = await ChainDaddy.exchangeCode(e.detail);
    await fetch('/session', { method: 'POST', body: tokens.id_token }); // your server verifies it
  });
</script>

It fires cd-signin with the result as detail, or cd-signin-error with { message } (access_denied when the person cancels). theme is light or dark; text is signin ("Sign in with Chain Daddy") or continue ("Continue with Chain Daddy"). The text and the crown are fixed, and the button is at least 40 pixels tall: please don't restyle it into something that reads differently.

The callback page ​

html
<script src="https://js.chaindaddy.io/v1/cd.js"></script>
<script>
  ChainDaddy.handleCallback().then(async (result) => {
    if (!result) return; // the popup handed it back and closed
    const tokens = await ChainDaddy.exchangeCode(result); // after a redirect: finish here
    await fetch('/session', { method: 'POST', body: tokens.id_token });
    location.replace('/');
  });
</script>

A coin's apps on its own site ​

The same script defines three elements. Put a link inside each one: it shows until the app is ready, and stays if the app can't load, so the page never has a hole in it.

html
<script async src="https://js.chaindaddy.io/v1/cd.js"></script>

<!-- One of the coin's apps -->
<cd-app crown="123" chain="base" app="slug"><a href="https://chaindaddy.io/SYM/base">$SYM on Chain Daddy</a></cd-app>

<!-- The coin's card: name, price, market cap -->
<cd-coin crown="123" chain="base"><a href="https://chaindaddy.io/SYM/base">$SYM on Chain Daddy</a></cd-coin>

<!-- A button that opens the coin's page in a small window -->
<cd-buy><a href="https://chaindaddy.io/SYM/base">Buy $SYM</a></cd-buy>
AttributeOnMeaning
crowncd-app, cd-coinThe coin's id on Chain Daddy
chaincd-app, cd-coinIts chain: a key (base) or a CAIP-2 id (eip155:8453)
appcd-appThe app's slug. It must be installed on the coin and allowed on other sites
modecd-app, cd-coinlight or dark. Left out, it follows the visitor's system setting
  • Where it works. cd-coin, cd-buy and read-only apps work on any https site. An app that uses a wallet works only on the coin's verified sites; anywhere else the link stays.
  • Events. Each element fires cd:ready when its app is showing and cd:unavailable when it falls back to the link. Nothing else comes back to your page: no wallet address, no sign-in and no result.
  • Colours. Set --cd-accent, --cd-surface, --cd-text, --cd-text-muted and --cd-border on the element or any parent. An app takes the ones its developer chose to take; cd-buy uses them all.
  • Size. The element is a block as wide as its container. An app is as tall as its tile on a coin page (from its default size), or the minimum height it declares if that is more; the coin's card is as tall as its content.
  • Content Security Policy. If your site sends one, allow script-src https://js.chaindaddy.io and frame-src https://embed.chaindaddy.io.
  • From npm. import { defineEmbedElements } from '@chaindaddy/js/embed', then call defineEmbedElements() once in the browser.

Check a store payment on your server ​

@chaindaddy/js/verify is the one part of the package made for a server, not a page. It checks on chain that a store order was paid, without asking Chain Daddy: it reads the checkout contract through a JSON-RPC endpoint you choose. It is not in the script-tag build.

ts
import { verifyPayment, VerifyError, NATIVE_TOKEN } from '@chaindaddy/js/verify';

const result = await verifyPayment(order.verification, {
  rpcUrl: process.env.RPC_URL,
  expect: { orderId, chainId: 'eip155:8453', token: COIN_ADDRESS, payee: PAYOUT_ADDRESS, amount: 975000n },
});
// { paid, payer, depth, checkout, blockNumber, orderHash, chainId }
Option
expectRequired. What you sold, from your own records: orderId, chainId, token (NATIVE_TOKEN for the chain's own coin), payee (your payout address) and amount (the least it must receive; a bigint or a decimal string)
rpcUrlYour JSON-RPC endpoint for the order's chain
requestIn place of rpcUrl: a function shaped like ({ method, params }) => result, such as a viem client's request
fetchUsed with rpcUrl in place of the global fetch
confirmationsWait deeper. The helper already reads at the deeper of what the order asks and what our own server waits on that chain (MIN_CONFIRMATIONS); this can only raise it
signalAn AbortSignal for the requests made through rpcUrl

It resolves paid: true only when the contract says that exact order was paid, depth blocks deep, and paid: false when it was not (yet). Anything it cannot vouch for is a VerifyError with a code: a block that is not what expect says, a checkout that is not one of ours on that chain, an RPC endpoint on another chain or one that fails. It checks EVM orders; for a Solana order it throws unsupported. Verify a payment has a full example, every code, and the same check by hand.