Skip to content

Test ​

Everything here runs on your own computer or against the live service, with no real money. There is no separate test environment.

Test locally ​

bash
chaindaddy app dev        # in a project from a template, npm run dev is the same

This opens a coin page on your own computer, at http://127.0.0.1:5180. Your app runs the way it will on a real page: in the page's own sandbox, at the page's slot sizes (full width, a strip, a phone, and one you resize). You need no account.

  • A widget rebuilds and reloads when you save a file. A build error shows over the widget, and the last good build stays underneath.
  • An engine app (a web build, or a Godot or Unity export in engine/) is checked first: threads off, the files its entry needs, its size against the tiers. Then it starts the way a coin page starts it, except a web build, which has no address of its own here. chaindaddy app scan runs the same checks alone.

The page answers your app's actions from sample data, in the platform API's shapes: a store with one of each kind of item, the viewer's items, orders that go from paid to confirmed, spending a consumable (boost is one apps may spend, ticket is not), a poll, sign-in, signatures, transactions, uploads, notifications and fullscreen. It also refuses what a coin page refuses, with the same words: an action your capp.json doesn't declare the permission for, an api-call outside this token, a fetch to anywhere but the platform API, and a sign-in without a tap in the app. Every action and answer is listed beside the app, and refusals and sandbox violations are printed in your terminal too.

Controls at the top set the viewer (signed out, wallet connected, signed in, owns items), whether they hold the token or own it, how a purchase ends (paid then confirmed, confirmed, canceled, failed), whether they decline a sign-in or a signature, and the theme. The same switches work in the URL (?iap=signedout&holder=1&buy=failed, ?only=896,200 for one slot).

A game's fullscreen view draws the page's HUD at its real sizes, with HUD mark, HUD price and HUD Trade switches for what a page's plan and owner can turn off. See Keep the corners clear.

Files only buyers get are served from your project's paid/ folder; the owns paid files switch and the signed-out viewer show your app both refusals. Saves are kept the way a coin page keeps them.

OptionWhat it does
--bundle <file>Serve a bundle your own bundler writes, and reload when it changes, instead of building src/index.tsx
--fixtures <file>An ES module that replaces sample data: appData, catalog, api (answers by "METHOD /path") and actions (handlers by action type; return undefined to fall through)
--unreviewedApply an unreviewed build's limits: api-call may only read, and signatures and transactions are refused
--offlineAnswer from sample data only. Otherwise a public read no sample answers goes to the live API, signed out
--port, --open, --verbosePick the port, open your browser, print every action in the terminal
js
// fixtures.mjs: chaindaddy app dev --fixtures fixtures.mjs
export const appData = { symbol: 'MYTOKEN', tokenId: 42 };
export const api = {
  'GET /api/v2/registration/42/polls': [],
};
export const actions = {
  quote: () => ({ ok: true, data: { status: 'bought' } }),
};

What it can't give you: a real wallet, a real payment, and a real session token. The token session-token returns is a placeholder that nothing verifies, so point your own server's dev mode at it.

Before you submit, start the build the way our checks will: chaindaddy app scan --run (the boot check). After you submit, try the real build on your own token page, for you alone: Preview a build.

Without the CLI: open chaindaddy.io/_dev/widgets and choose Test your own bundle. Pick dist/bundle.js and your capp.json. It loads the widget in the same sandbox, with sample token data and both themes, and lists loader errors and sandbox violations. It shows the actions your widget sends but doesn't answer them.

Test your store with no money ​

Your store has a test store beside it: the same coin page, the same token and payout, and a copy of your catalog. Orders on it are paid with one API call instead of a wallet. No money moves and no fee is taken. Everything else is the store API you already use: the same orders, items, receipts, events and webhooks, marked as test.

Build your server against the test store, then go live by switching the key. A store key (cd_iap_…) is the password your server uses to talk to one store, and an entitlement is the store's record that a wallet owns an item.

1. Create the test store ​

You need your account credentials and manager role on the coin page, as for any store change (Authentication).

With buttons: in the Developer portal, open your store and switch Live to Test beside its name. The first switch creates the test store, and every tab then works on it.

From the command line or over HTTP, pass your live store's id:

bash
npm install -g @chaindaddy/cli@beta       # once; needs Node.js 20 or later
chaindaddy login                          # once: sign in through your browser

chaindaddy iap store test 3f0c… --json
#   → {"ok":true,"store":{"id":"7a21…","mode":"test","name":"MYTOKEN (test)","feeBps":0,…}}
bash
curl -X POST https://api.chaindaddy.io/api/v2/iap/stores/3f0c…/test-store \
  -H "Authorization: Bearer $CHAINDADDY_API_KEY"

MCP: iap_open_test_store. The first call creates the test store (201) and every call after that returns it (200). GET on the same path returns it, or 404 before it exists.

The test store starts with a copy of every item in your live catalog, drafts included, with nothing sold. From then on the two catalogs are separate: change a test item and the live one is untouched, and a new live item does not appear in the test store until you add it there (chaindaddy iap sku create 7a21… --file …).

2. Create a test key ​

Only trying a checkout session on your own machine, with no store yet? The guide prints a shared demo key that needs no account. For anything else, make your own:

Keys are made on the test store as on your live store (Store keys). In the Developer portal, with Test switched on, it is the Keys tab: New test key. A test store's keys start with cd_iap_test_:

bash
chaindaddy iap key create 7a21… --name app-server-test \
  --scope orders:read orders:write entitlements:read entitlements:write events:read
#   prints the cd_iap_test_… secret ONCE
export CHAINDADDY_IAP_KEY=cd_iap_test_…

A test key works only on its test store, and a live key only on its live store.

3. Create an order for a wallet ​

The same call as a live purchase, with the test key and the test store's id:

bash
curl -X POST https://api.chaindaddy.io/api/v2/iap/orders \
  -H "Authorization: Bearer $CHAINDADDY_IAP_KEY" \
  -H "Idempotency-Key: test-cart-1" \
  -H "Content-Type: application/json" \
  -d '{"storeId":"7a21…","sku":"credits.100","quantity":1,"wallet":"0xabc…","appAccountToken":"user-42"}'

The order locks its price and deadline as a live one does, with no fee, and its payment asks for nothing to be signed:

json
{
  "id": "0x51c0…",
  "storeId": "7a21…",
  "sku": "credits.100",
  "wallet": "0xabc…",
  "amount": "50000000000000000000",
  "feeAmount": "0",
  "feeBps": 0,
  "status": "pending",
  "payment": { "kind": "test" }
}

4. Pay it ​

bash
curl -X POST https://api.chaindaddy.io/api/v2/iap/orders/0x51c0…/test-pay \
  -H "Authorization: Bearer $CHAINDADDY_IAP_KEY"
#   → {"status":"confirmed","txHash":"test:4f0e…","receipt":"eyJhbGciOiJFZERTQSIs…",…}

CLI: chaindaddy iap orders test-pay 0x51c0…. MCP: iap_test_pay.

The order is confirmed in the response. It went through the same confirmation a real payment does: the items are granted, the receipt is signed, supply and per-wallet limits count it, and the order.confirmed event is written. Where a transaction hash would be, it carries test: and an id. Calling it again returns the same confirmed order and changes nothing.

It needs a key with orders:write, or your own session as a manager of the coin page. It answers 409 LIVE_ORDER for an order on a live store, which is paid on chain, and 409 ORDER_EXPIRED once the order's deadline has passed; create a new order then. POST …/submit answers 409 TEST_ORDER for a test order.

5. Check the receipt ​

The receipt is signed with the same key as live receipts and verifies the same way (Verifying receipts). Its payload names the test store and says it is a test:

json
{
  "orderId": "0x51c0…",
  "storeId": "7a21…",
  "feeAmount": "0",
  "payment": { "txHash": "test:4f0e…", "logIndex": 0, "blockNumber": 0, "payer": "0xabc…" },
  "livemode": false
}

Live receipts carry "livemode": true. In production, check both storeId (your live store) and livemode before you hand anything over.

6. Read and spend the items ​

bash
chaindaddy iap entitlements 7a21… --wallet 0xabc… --json
chaindaddy iap consume 7a21… --wallet 0xabc… --sku credits --qty 1 --idempotency-key job-1

These are the same calls as live (Read and spend items). Grants, revokes, refunds, drops you award from your server, the event feed and the store's stats all work on the test store and stay in it.

7. Receive the webhooks ​

Give the test store its own webhook, pointed at your test endpoint:

bash
chaindaddy iap webhook set 7a21… --url https://staging.example.com/hooks/chaindaddy

That command runs as your account, not as a store key. In the portal it is Test, then Settings, then Webhook. A store key sets a webhook over HTTP, with the webhooks:write scope, which none of those keys carry: The test store's webhook. The URL must be a public https address, so to receive on your own machine point it at a tunnel.

Every delivery carries livemode:

json
{
  "id": "iap_evt_2291",
  "type": "iap.order.confirmed",
  "data": {
    "eventId": 2291,
    "type": "order.confirmed",
    "storeId": "7a21…",
    "chainId": "eip155:8453",
    "crownId": 8,
    "livemode": false,
    "wallet": "0xabc…",
    "orderId": "0x51c0…",
    "payload": { "sku": "credits.100", "status": "confirmed", "txHash": "test:4f0e…", "receipt": "eyJ…", "appAccountToken": "user-42" }
  }
}

A test store's events go to the test store's webhook, not to your live store's webhook or the iap.* webhooks on your account. iap.entitlement.consumed and the rest arrive the same way.

8. Go live ​

Switch your server to the live store's key. Nothing else in your code changes if you read the store id from the key instead of configuring it: a store key's GET /api/v2/iap/stores returns its own store.

bash
curl https://api.chaindaddy.io/api/v2/iap/stores -H "Authorization: Bearer $CHAINDADDY_IAP_KEY"
#   → {"entries":[{"id":"3f0c…","mode":"live",…}]}

The test store stays where it is for your next change.

What is different in test mode ​

Test storeLive store
Paying an orderPOST /api/v2/iap/orders/{id}/test-payThe buyer's wallet, on chain
payment on a pending order{"kind":"test"}The EVM or Solana payment
FeeNone (feeBps: 0)The fee
Receipts and webhookslivemode: falselivemode: true
Notifications to you and the buyerNoneAs listed
Visible on your coin page, in the Item showcase and to buyersNo. Only people who manage the coin page can read itYes
Claim links of its dropsDo not openOpen
Token prizesRefused (409 LIVE_STORE_ONLY): they pay real tokensYes
Messages to customersNone: a test store has no customersYes
Checkout sessionsOpened with the test key, and may return to http://localhost. Completed with the test key (POST /api/v2/iap/checkout/sessions/{id}/test-pay), or on the pay page with no payment by a wallet that manages the coin pagehttps on a verified site. Paid by the buyer's wallet, on chain
Checking a payment on chainNothing to check: a test order has no verification blockYes

An order that is never paid expires at its deadline, as a live one does. An item behind a hold rule still checks the wallet's real balance, and a dollar-priced item converts at the live rate.

Test checkout on your site ​

A checkout session opened with a test store's key is a test session: the buyer's page says Test mode and no money moves. Your server's code is the same as live.

A whole site that does these steps on http://localhost:3000, one server file and two pages: Run the example.

1. Open it with the test key. The same call, with the cd_iap_test_… key from step 2 and the sku of one of your own items:

bash
curl -X POST https://api.chaindaddy.io/api/v2/iap/checkout/sessions \
  -H "Authorization: Bearer $CHAINDADDY_IAP_KEY" \
  -H "Idempotency-Key: test-checkout-1" \
  -H "Content-Type: application/json" \
  -d '{
    "lineItems": [{ "sku": "credits.100", "quantity": 1 }],
    "successUrl": "http://localhost:3000/thanks",
    "cancelUrl": "http://localhost:3000/cart",
    "clientReferenceId": "test-cart-1"
  }'
#   → {"id":"cs_…","url":"https://chaindaddy.io/_pay/cs_…","status":"open","livemode":false,…}

successUrl and cancelUrl may be on your own machine: http://localhost or http://127.0.0.1, on any port. That is for a test store's key only; a live key is refused them (RETURN_URL_NOT_VERIFIED). An https URL follows the live rule on a test store too: a page on one of your coin's verified sites, or a subdomain of one (unless the site is set to This host only).

2. Complete it. From your terminal, with the same test key and no wallet or sign-in:

bash
curl -X POST https://api.chaindaddy.io/api/v2/iap/checkout/sessions/cs_…/test-pay \
  -H "Authorization: Bearer $CHAINDADDY_IAP_KEY" \
  -H "Idempotency-Key: test-pay-1" \
  -H "Content-Type: application/json" \
  -d '{"wallet": "0x00000000000000000000000000000000000000d4"}'
#   → {"id":"cs_…","status":"complete","livemode":false,"order":{"status":"confirmed",…},"receipt":"eyJ…"}

wallet is the wallet the test items go to: any address on your store's chain. That one works as it is for a store on an EVM chain; for a Solana store send any Solana address. Leave the body out when the session names a wallet. The session's page, if you have it open, returns to your successUrl with session_id added within a few seconds (or tells the page that opened it in a popup). test-pay completes a test store's sessions only: a live store's key answers 409 LIVE_ORDER, and a live session is paid by the buyer on chaindaddy.io (403 SESSION_REQUIRED on the order call).

Or pay it in a browser, as a buyer would: open the session's url, sign in with a wallet that manages your coin page (only those wallets can see a test store), and press Buy. No wallet prompt appears, the order is confirmed at once, and Done returns you to your successUrl. An item of an installed app is completed this way only.

3. Confirm it the way you will live. The test store's webhook gets iap.order.confirmed with livemode: false, and its payload carries your session's checkoutSessionId. Reading the session with the test key shows status: "complete", the order and its receipt:

bash
curl https://api.chaindaddy.io/api/v2/iap/checkout/sessions/cs_… \
  -H "Authorization: Bearer $CHAINDADDY_IAP_KEY"

Opening the checkout in a popup works the same in test mode. The page that opens it is on one of your verified sites or, for a test session, on http://localhost or http://127.0.0.1.

What a test session cannot show you: the wallet's payment prompt, and checking the payment on chain.

Test sign-in ​

Sign in with Chain Daddy has no test mode: test it against the live service with your own account. No wallet? Sign in on chaindaddy.io with email, Google or Apple, and one is made for you.

  • Nothing to register. Host a client.json on your test site and use its URL as the client id, as in the quickstart. To see the consent screen and what an ID token carries first, sign in on the demo page; it has no server, so it shows the token without verifying it.

  • On localhost, use the built-in development client, chaindaddy-dev: nothing to host and nothing to register (Developing locally). It asks every time and answers nothing about holdings. To test your own client.json from your machine, serve the site through a tunnel that gives it a public https address, described in the same section. A registered client may also use http://localhost and http://127.0.0.1 redirects; registering needs a Developer Beta Program membership, which takes any paid plan.

  • sub is per site. The same person gets a different sub at your test site than at your live one, and a client with only localhost redirects gets a sub of its own (Subject identifiers). Do not carry test users over by sub.

  • Holdings and crowns are real. The wallets, holdings and crowns a person approves are what their wallets hold on chain. To test the "holds your coin" path, sign in with a wallet that holds it, and with one that does not. With no coin to test with, a wallet that holds nothing gets held: false, and gate.test.mjs tests both answers with no wallet at all: Test your gate.

  • Switch accounts. A site you approved signs you in again without asking. To sign in as someone else, disconnect the site under Settings → Connected sites on chaindaddy.io, press the button, and choose Use another account at the top of the sign-in window. The quickstart's server has no sign-out, so add one that drops the session and the cookie, and call it with fetch('/signout', { method: 'POST' }):

    js
    app.post('/signout', (req, res) => {
      sessions.delete(sessionId(req));
      res.clearCookie('session');
      res.json({ ok: true });
    });
  • Check the token on your server in testing too. The checks are the same as live (Verifying an ID token yourself).

Test apps on your site ​

What you can test depends on what the app or card does.

  • Your coin's card and badge only display. They work on any https page, so paste the snippet into a test page and look at it (Embeds, Apps on your own site).
  • A read-only app runs on any https site too. Paste its snippet into a test page.
  • On your own computer, serve the test page at http://localhost or http://127.0.0.1 (any port): the card and read-only apps show there as they do on an https site. A page opened as a file, or served from another http:// address, shows only the link, and the browser's console names the reason (frame-ancestors).
  • An app that uses a wallet runs only on your coin's verified sites, or a subdomain of one (unless the site is set to This host only). On any other page its link stays and sign-in is refused. It does not run on http://localhost. To test it before it is on your main site, verify the site and use a page or a subdomain of it you keep for testing. A signature it asks for is a real request to your wallet.

What each of these looks like on the page and in the browser's console, and how long the link takes to settle: You should see. With no access to the coin manager, the three values of a snippet come from keyless reads: Without the coin manager.

If you are the app's developer, your app runs on other sites only once a build with "embed": true is approved and published (Apps on other sites). Before that:

  1. Run it on your own computer: Test locally.
  2. After you submit, try the real build on your own coin page, for you alone: Preview a build.
  3. Your Builds tab says whether the build can run on other sites, and where: any https site, the coin's verified sites only, or not at all, with the reason.