@chaindaddy/mcp 0.4.2 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/build/login.js ADDED
@@ -0,0 +1,203 @@
1
+ // The Chain Daddy CLI's stored login, used by this server when no API key is
2
+ // set: run `chaindaddy login` once and the MCP server needs no key either.
3
+ //
4
+ // cli/src/lib/login.ts OWNS THE FORMAT (where it is stored, the record's
5
+ // fields, the refresh lock). This is a compact copy of the parts a server
6
+ // needs: read the record, refresh it under the same lock, and sign each
7
+ // request with the login's DPoP key (RFC 9449). Change both together.
8
+ import { createHash, createPrivateKey, randomBytes, sign } from "node:crypto";
9
+ import { spawnSync } from "node:child_process";
10
+ import { chmodSync, closeSync, existsSync, mkdirSync, openSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
11
+ import { homedir } from "node:os";
12
+ import { join } from "node:path";
13
+ /** Stands in for the login wherever a bearer is chosen; loginFetch replaces it. */
14
+ export const CLI_LOGIN = "chaindaddy-login";
15
+ const SERVICE = "chaindaddy-cli";
16
+ const dir = () => join(homedir(), ".chaindaddy");
17
+ const loginFile = () => join(dir(), "login.json");
18
+ const lockFile = () => join(dir(), "login.lock");
19
+ const forceFile = () => process.env.CHAINDADDY_LOGIN_STORE === "file";
20
+ const onPath = (bin) => (process.env.PATH ?? "").split(":").some((d) => d && existsSync(join(d, bin)));
21
+ const keychain = () => !forceFile() && process.platform === "darwin" && existsSync("/usr/bin/security");
22
+ const secretTool = () => !forceFile() && process.platform === "linux" && onPath("secret-tool");
23
+ function run(cmd, args, input) {
24
+ const r = spawnSync(cmd, args, { input, encoding: "utf-8", timeout: 10_000, stdio: ["pipe", "pipe", "pipe"] });
25
+ return { ok: r.status === 0 && !r.error, out: (r.stdout ?? "").trim() };
26
+ }
27
+ function fileStore() {
28
+ try {
29
+ return JSON.parse(readFileSync(loginFile(), "utf-8"));
30
+ }
31
+ catch {
32
+ return {};
33
+ }
34
+ }
35
+ function decode(raw) {
36
+ try {
37
+ return JSON.parse(Buffer.from(raw, "base64").toString("utf-8"));
38
+ }
39
+ catch {
40
+ return undefined;
41
+ }
42
+ }
43
+ function load(rawApiUrl) {
44
+ // Keyed by the API URL exactly as the CLI resolves it: no trailing slash.
45
+ const apiUrl = rawApiUrl.replace(/\/+$/, "");
46
+ if (keychain()) {
47
+ const r = run("/usr/bin/security", ["find-generic-password", "-s", SERVICE, "-a", apiUrl, "-w"]);
48
+ if (r.ok && r.out)
49
+ return decode(r.out);
50
+ }
51
+ else if (secretTool()) {
52
+ const r = run("secret-tool", ["lookup", "service", SERVICE, "account", apiUrl]);
53
+ if (r.ok && r.out)
54
+ return decode(r.out);
55
+ }
56
+ return fileStore()[apiUrl];
57
+ }
58
+ function writeFileStore(all) {
59
+ mkdirSync(dir(), { recursive: true, mode: 0o700 });
60
+ const tmp = `${loginFile()}.${process.pid}.tmp`;
61
+ writeFileSync(tmp, JSON.stringify(all, null, 2), { mode: 0o600 });
62
+ chmodSync(tmp, 0o600);
63
+ renameSync(tmp, loginFile());
64
+ }
65
+ function save(rec) {
66
+ const value = Buffer.from(JSON.stringify(rec)).toString("base64");
67
+ let wrote = false;
68
+ if (keychain() && !/["\\\s]/.test(rec.apiUrl)) {
69
+ run("/usr/bin/security", ["-i"], `add-generic-password -U -s ${SERVICE} -a "${rec.apiUrl}" -l "Chain Daddy CLI" -w ${value}\n`);
70
+ wrote = load(rec.apiUrl)?.refreshToken === rec.refreshToken;
71
+ }
72
+ else if (secretTool()) {
73
+ wrote = run("secret-tool", ["store", "--label=Chain Daddy CLI", "service", SERVICE, "account", rec.apiUrl], value).ok;
74
+ }
75
+ const all = fileStore();
76
+ if (wrote) {
77
+ // The keyring copy is the one read first; a file copy would only be stale.
78
+ if (all[rec.apiUrl]) {
79
+ delete all[rec.apiUrl];
80
+ writeFileStore(all);
81
+ }
82
+ return;
83
+ }
84
+ all[rec.apiUrl] = rec;
85
+ writeFileStore(all);
86
+ }
87
+ /** Whether `chaindaddy login` is stored for this API. */
88
+ export function hasCliLogin(apiUrl) {
89
+ return !!load(apiUrl);
90
+ }
91
+ function proof(jwk, method, url, accessToken) {
92
+ const u = new URL(url);
93
+ const b64 = (s) => Buffer.from(s).toString("base64url");
94
+ const header = { typ: "dpop+jwt", alg: "ES256", jwk: { kty: "EC", crv: "P-256", x: jwk.x, y: jwk.y } };
95
+ const payload = {
96
+ jti: randomBytes(16).toString("base64url"),
97
+ htm: method.toUpperCase(),
98
+ htu: `${u.protocol}//${u.host}${u.pathname}`,
99
+ iat: Math.floor(Date.now() / 1000),
100
+ };
101
+ if (accessToken)
102
+ payload.ath = createHash("sha256").update(accessToken).digest("base64url");
103
+ const input = `${b64(JSON.stringify(header))}.${b64(JSON.stringify(payload))}`;
104
+ const sig = sign("sha256", Buffer.from(input), { key: createPrivateKey({ key: jwk, format: "jwk" }), dsaEncoding: "ieee-p1363" });
105
+ return `${input}.${sig.toString("base64url")}`;
106
+ }
107
+ async function locked(fn) {
108
+ mkdirSync(dir(), { recursive: true, mode: 0o700 });
109
+ const deadline = Date.now() + 70_000;
110
+ for (;;) {
111
+ try {
112
+ closeSync(openSync(lockFile(), "wx", 0o600));
113
+ break;
114
+ }
115
+ catch (e) {
116
+ if (e.code !== "EEXIST")
117
+ throw e;
118
+ try {
119
+ if (Date.now() - statSync(lockFile()).mtimeMs > 60_000)
120
+ unlinkSync(lockFile());
121
+ }
122
+ catch {
123
+ /* gone already */
124
+ }
125
+ if (Date.now() > deadline)
126
+ throw new Error("another process is refreshing the Chain Daddy login; try again");
127
+ await new Promise((r) => setTimeout(r, 100));
128
+ }
129
+ }
130
+ try {
131
+ return await fn();
132
+ }
133
+ finally {
134
+ try {
135
+ unlinkSync(lockFile());
136
+ }
137
+ catch { /* already gone */ }
138
+ }
139
+ }
140
+ /** The login with a live access token, refreshed (once, under the lock) when needed. */
141
+ async function current(apiUrl, stale) {
142
+ const fresh = (r) => (stale ? r.accessToken !== stale : Date.parse(r.accessExpiresAt) - Date.now() > 60_000);
143
+ const first = load(apiUrl);
144
+ if (!first)
145
+ throw new Error("NO_CREDENTIALS: not signed in; run `chaindaddy login` or set CHAINDADDY_API_KEY");
146
+ if (fresh(first))
147
+ return first;
148
+ return locked(async () => {
149
+ const rec = load(apiUrl);
150
+ if (!rec)
151
+ throw new Error("NO_CREDENTIALS: not signed in; run `chaindaddy login` or set CHAINDADDY_API_KEY");
152
+ if (fresh(rec))
153
+ return rec;
154
+ const url = `${rec.apiUrl}/api/v2/oauth/token`;
155
+ const resp = await fetch(url, {
156
+ method: "POST",
157
+ headers: { "Content-Type": "application/x-www-form-urlencoded", Accept: "application/json", DPoP: proof(rec.dpopJwk, "POST", url) },
158
+ body: new URLSearchParams({ grant_type: "refresh_token", refresh_token: rec.refreshToken, client_id: rec.clientId || "chaindaddy-cli" }).toString(),
159
+ });
160
+ const t = (await resp.json().catch(() => ({})));
161
+ if (!resp.ok) {
162
+ throw new Error(t.error === "invalid_grant"
163
+ ? "NO_CREDENTIALS: the Chain Daddy login has ended; run `chaindaddy login` again"
164
+ : `could not refresh the Chain Daddy login (HTTP ${resp.status})`);
165
+ }
166
+ const next = {
167
+ ...rec,
168
+ accessToken: String(t.access_token),
169
+ refreshToken: String(t.refresh_token),
170
+ accessExpiresAt: new Date(Date.now() + Number(t.expires_in ?? 3600) * 1000).toISOString(),
171
+ scope: typeof t.scope === "string" ? t.scope : rec.scope,
172
+ };
173
+ save(next);
174
+ return next;
175
+ });
176
+ }
177
+ /**
178
+ * fetch for a request whose bearer is CLI_LOGIN: sends it as the login
179
+ * (`Authorization: DPoP` + a proof), refreshing and retrying once when the API
180
+ * says the access token is dead. Any other request goes out unchanged.
181
+ */
182
+ export async function loginFetch(apiUrl, url, init = {}) {
183
+ const headers = new Headers(init.headers);
184
+ if (headers.get("authorization") !== `Bearer ${CLI_LOGIN}`)
185
+ return fetch(url, init);
186
+ headers.delete("x-api-key");
187
+ const method = (init.method ?? "GET").toUpperCase();
188
+ const send = (rec) => {
189
+ const h = new Headers(headers);
190
+ h.set("Authorization", `DPoP ${rec.accessToken}`);
191
+ h.set("DPoP", proof(rec.dpopJwk, method, url, rec.accessToken));
192
+ return fetch(url, { ...init, headers: h });
193
+ };
194
+ const rec = await current(apiUrl);
195
+ const resp = await send(rec);
196
+ if (resp.status !== 401)
197
+ return resp;
198
+ const body = (await resp.clone().json().catch(() => ({})));
199
+ if (body.error !== "invalid_token" && !(resp.headers.get("www-authenticate") ?? "").includes("invalid_token"))
200
+ return resp;
201
+ return send(await current(apiUrl, rec.accessToken));
202
+ }
203
+ //# sourceMappingURL=login.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login.js","sourceRoot":"","sources":["../src/login.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ export interface Skill {
2
+ name: string;
3
+ title: string;
4
+ description: string;
5
+ body: string;
6
+ }
7
+ export declare function parseSkill(dir: string, text: string): Skill;
8
+ /** Every skill, sorted by name. Throws when a SKILL.md is malformed. */
9
+ export declare function loadSkills(root?: string): Skill[];
10
+ //# sourceMappingURL=skills.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skills.d.ts","sourceRoot":"","sources":["../src/skills.ts"],"names":[],"mappings":"AAWA,MAAM,WAAW,KAAK;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;CACd;AAKD,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,KAAK,CAkB3D;AAED,wEAAwE;AACxE,wBAAgB,UAAU,CAAC,IAAI,GAAE,MAAmB,GAAG,KAAK,EAAE,CAK7D"}
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Chain Daddy's Agent Skills, read from the package's skills/ directory: one
3
+ * directory per skill holding a SKILL.md (frontmatter with name and
4
+ * description, then the Markdown body). In the source tree that is mcp/skills,
5
+ * the single source of truth the remote server embeds too (mcp/skills/skills.go
6
+ * parses them the same way); in the installed package it sits beside build/.
7
+ */
8
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
9
+ import { join } from "node:path";
10
+ import { fileURLToPath } from "node:url";
11
+ const SKILLS_DIR = fileURLToPath(new URL("../skills/", import.meta.url));
12
+ // Frontmatter is flat "key: value" lines; a quoted value loses its quotes.
13
+ export function parseSkill(dir, text) {
14
+ if (!text.startsWith("---\n"))
15
+ throw new Error(`${dir}: SKILL.md does not start with a --- frontmatter line`);
16
+ const end = text.indexOf("\n---\n", 3);
17
+ if (end < 0)
18
+ throw new Error(`${dir}: frontmatter is not closed by a --- line`);
19
+ const fields = {};
20
+ for (const line of text.slice(4, end).split("\n")) {
21
+ const i = line.indexOf(":");
22
+ if (i < 0)
23
+ throw new Error(`${dir}: frontmatter line ${JSON.stringify(line)} is not key: value`);
24
+ let v = line.slice(i + 1).trim();
25
+ if (v.length >= 2 && (v[0] === '"' || v[0] === "'") && v[v.length - 1] === v[0])
26
+ v = v.slice(1, -1);
27
+ fields[line.slice(0, i).trim()] = v;
28
+ }
29
+ const body = text.slice(end + 5).replace(/^\n+/, "");
30
+ const title = body.split("\n").find((l) => l.startsWith("# "))?.slice(2).trim() ?? "";
31
+ if (fields.name !== dir)
32
+ throw new Error(`${dir}: name ${JSON.stringify(fields.name)} does not match its directory`);
33
+ if (!fields.description)
34
+ throw new Error(`${dir}: no description`);
35
+ if (!title)
36
+ throw new Error(`${dir}: body has no # heading to title it`);
37
+ return { name: fields.name, title, description: fields.description, body };
38
+ }
39
+ /** Every skill, sorted by name. Throws when a SKILL.md is malformed. */
40
+ export function loadSkills(root = SKILLS_DIR) {
41
+ return readdirSync(root, { withFileTypes: true })
42
+ .filter((d) => d.isDirectory() && existsSync(join(root, d.name, "SKILL.md")))
43
+ .map((d) => parseSkill(d.name, readFileSync(join(root, d.name, "SKILL.md"), "utf8")))
44
+ .sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
45
+ }
46
+ //# sourceMappingURL=skills.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skills.js","sourceRoot":"","sources":["../src/skills.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chaindaddy/mcp",
3
- "version": "0.4.2",
3
+ "version": "0.6.0",
4
4
  "description": "MCP server for Chain Daddy: search tickers, launch or import a token, and run its creator store",
5
5
  "mcpName": "io.chaindaddy/mcp",
6
6
  "homepage": "https://docs.chaindaddy.io/developer/store-and-rewards#tools-for-agents",
@@ -56,6 +56,7 @@
56
56
  },
57
57
  "files": [
58
58
  "build",
59
+ "skills/*/SKILL.md",
59
60
  "README.md"
60
61
  ]
61
62
  }
@@ -0,0 +1,114 @@
1
+ ---
2
+ name: add-crypto-to-your-game
3
+ description: The end-to-end path for putting a game, app or site on crypto with Chain Daddy - pick or launch a token, sell items for it, sign players in with their wallet, verify purchases on your server, and run it all on a no-money test store first. Use when someone wants to add a token, sell in-game items for crypto, give holders perks, or "add crypto" to a game, app or dApp.
4
+ license: MIT
5
+ ---
6
+
7
+ # Add crypto to your game or app
8
+
9
+ Chain Daddy gives a token a verified page and a **store**: items you sell for that token, a record of what
10
+ each wallet owns, and a signed receipt for every purchase. Your game reads what a player owns; it never
11
+ moves money.
12
+
13
+ ## How it fits together
14
+
15
+ ```
16
+ your token ── its page on chaindaddy.io ── its store (items, what each wallet owns, receipts)
17
+ │
18
+ your game client ── asks to buy ─────┤ the buyer's wallet pays your payout wallet on chain
19
+ │
20
+ your server ── store key ────────────┘ creates orders, verifies receipts, spends items
21
+ ```
22
+
23
+ 1. **Money never passes through your code.** The buyer's wallet pays the token's payout wallet directly,
24
+ in one on-chain transaction. Chain Daddy then grants the item and signs a receipt.
25
+ 2. **The client asks, the server decides.** Anything with value (spending credits, a ranked result, a
26
+ prize) is decided on your server, with a store key only the server holds.
27
+ 3. **Test first.** A test store runs the whole loop with no wallet, no chain and no money.
28
+
29
+ ## What the human needs first
30
+
31
+ Check these before writing code, and stop with a clear next step if one is missing:
32
+
33
+ | Need | How to check | If missing |
34
+ |---|---|---|
35
+ | A token page they own or manage, and its **chain key** (`base`) and **crown id** (the registration id) | `find-and-check-a-token` skill | Import a token they made elsewhere, or `launch-a-token` |
36
+ | A plan that can open a store: Developer, Partner or Enterprise, or Developer Beta Program membership | `GET https://api.chaindaddy.io/api/v2/iap/eligibility` (signed in) | Any paid plan, then sign the agreement at https://chaindaddy.io/_developer |
37
+ | Signed in on this computer | `chaindaddy whoami` | They run `chaindaddy login` and approve in the browser (any wallet or email/social sign-in); the CLI and the local MCP server both use it. A server or CI job gets a `cd_live_…` key instead (`chaindaddy keys create`) |
38
+
39
+ Never ask the human to paste a key into the chat. Ask them to set it as an environment variable.
40
+
41
+ ## The path
42
+
43
+ ### 1. Pick the token
44
+
45
+ - **Their own token, made elsewhere:** they import it on chaindaddy.io (one fee, or free for established
46
+ tokens). A store can only sell for a token whose page the wallet owns or manages.
47
+ - **A new token:** see `launch-a-token`. A token launched in a Chain Daddy launchpad pool pays **0%** on
48
+ store sales; any other token pays **2.5%**, taken out of the price at checkout.
49
+
50
+ ### 2. Decide where the game runs
51
+
52
+ | The game runs… | How it sells | Skill |
53
+ |---|---|---|
54
+ | On the token page, as a Crown App | Sends the `iap-purchase` action; the page shows its own purchase sheet and handles the wallet. No server or payment code needed | `build-a-crown-app` |
55
+ | In your own site, app or client | Your server creates an order; your client pays with the order's `payment` block | `sell-items-in-your-app` |
56
+ | Anywhere, with no code | A payment link: `https://chaindaddy.io/<symbol>/<chain>?buy=<sku>` | `sell-items-in-your-app` |
57
+
58
+ ### 3. Design the catalog
59
+
60
+ | Type | Use it for |
61
+ |---|---|
62
+ | `non_consumable` | Owned for good, one per wallet: a skin, a badge, a level pack |
63
+ | `consumable` | Used up: credits, boosts. A bundle `grants` other items |
64
+ | `timed` | Access for a while: a season pass, a rental |
65
+ | `unlock` | Never sold. Held while a rule holds, e.g. "holds at least 1,000 tokens" |
66
+
67
+ Put what the game needs to apply an item in its `metadata`: that JSON is the contract between the store and
68
+ the game. Price in dollars (`priceUsdMicros`, with `minPrice` / `maxPrice` bounds) and take payment in the
69
+ token, so prices don't swing with the market.
70
+
71
+ ### 4. Build against the test store
72
+
73
+ Open the test store beside the live one (`iap_open_test_store` / `chaindaddy iap store test <storeId>`), make
74
+ a `cd_iap_test_…` key on it, then run the whole loop: create an order, pay it with `iap_test_pay`, verify the
75
+ receipt (`livemode: false`), read and spend the items, and receive the webhook. Details in
76
+ `sell-items-in-your-app`.
77
+
78
+ ### 5. Know who the player is
79
+
80
+ - In a Crown App: the `session-token` action gives a short-lived token your server verifies.
81
+ - On your own site or app: Sign in with Chain Daddy (standard OpenID Connect) - see `sign-in-with-chain-daddy`.
82
+
83
+ Both identify the player by wallet (`sub`). Key accounts by that wallet.
84
+
85
+ ### 6. Verify on your server
86
+
87
+ Verify every receipt and session token on the server before acting on it - see `verify-receipts-and-sessions`.
88
+ Deliver on `confirmed`, credit each `orderId` once, and treat the store's item ledger
89
+ (`GET …/entitlements?wallet=`) as the source of truth.
90
+
91
+ ### 7. Go live
92
+
93
+ Switch the server to the live store's key. Read the store id from the key (`GET /api/v2/iap/stores` with a
94
+ store key returns its own store) so nothing else changes. In production, check `storeId` is the live store and
95
+ `livemode` is `true`.
96
+
97
+ To pay out real tokens as prizes, see https://docs.chaindaddy.io/developer/token-prizes (live stores only).
98
+
99
+ ## Mistakes to avoid
100
+
101
+ - Granting an item on `paid`. `paid` means the payment is on chain but still confirming. Deliver on `confirmed`.
102
+ - Trusting a receipt or session token because the client sent it. Verify it on the server, every time.
103
+ - Shipping a store key, account key or webhook secret in client code, a repo or a log.
104
+ - A new random idempotency key on each retry. Build it from something stable (`user-42-milestone-10`).
105
+ - Items with a random outcome (loot boxes, mystery boxes): not allowed.
106
+ - Describing items as money. They can't be transferred between wallets or exchanged for tokens or cash.
107
+ - Asking for a private key or seed phrase, or sending a transaction for the human. They sign in their own wallet.
108
+
109
+ ## Docs
110
+
111
+ - Store and rewards: https://docs.chaindaddy.io/developer/store-and-rewards
112
+ - Test your integration: https://docs.chaindaddy.io/developer/test-your-integration
113
+ - Build a game: https://docs.chaindaddy.io/developer/build-a-game
114
+ - Everything, for an agent: https://docs.chaindaddy.io/llms.txt (raw pages at `/md/<path>.md`)
@@ -0,0 +1,123 @@
1
+ ---
2
+ name: build-a-crown-app
3
+ description: Build, test and publish a Crown App - an app any Chain Daddy token page can install - as a sandboxed React widget, a headless runner that receives signed events, or both. Covers the CLI path, capp.json manifest and permissions, the sandbox rules, actions like iap-purchase and session-token, local testing, review, and what paid apps can and can't do today. Use when someone wants to put a game, storefront, gallery or tool on a token page.
4
+ license: MIT
5
+ ---
6
+
7
+ # Build a Crown App
8
+
9
+ A Crown App runs on token pages. The token's owner adds it from the page's **Apps** tab; every app is free to
10
+ install on every plan.
11
+
12
+ | Type | What it is | Scaffold |
13
+ |---|---|---|
14
+ | `widget` | A React component, sandboxed in a frame on the page | `chaindaddy app init widget my-app` |
15
+ | `headless` | A runner: your HTTPS endpoint that receives the page's signed events | `chaindaddy app init headless my-runner` |
16
+ | `full` | A widget and a runner | `chaindaddy app init full my-app` |
17
+
18
+ Starters with working code: `game` (three.js arcade game with a shop), `interactive` (canvas piece),
19
+ `community` (poll, links, holder count): `chaindaddy app init game my-game`.
20
+
21
+ ## Who can publish
22
+
23
+ - **Testing locally needs no account.** Start there.
24
+ - **Publishing** needs any active paid plan (not a trial) and the Developer Beta Program Agreement, signed at
25
+ https://chaindaddy.io/_developer with the wallet that holds the plan. Check with `chaindaddy app whoami`.
26
+ - Every build is a signed `.capp` package, checked automatically, then reviewed by a person.
27
+
28
+ ## The path
29
+
30
+ ```bash
31
+ npm install -g @chaindaddy/cli@beta # Node 20+
32
+ chaindaddy app init widget my-app && cd my-app && npm install
33
+ npm run dev # or chaindaddy app dev: a token page on 127.0.0.1:5180
34
+ # when it works:
35
+ chaindaddy login # the human approves in their browser; no key to paste
36
+ npm run validate && npm run build && npm run scan
37
+ npm run pack && npm run sign && npm run submit
38
+ chaindaddy app submit --request-review # or press Submit for review on the build in the portal
39
+ ```
40
+
41
+ Add `--json --non-interactive` to CLI calls you run as an agent. Exit codes: `0` ok, `1` user error, `2`
42
+ system, `3` validation failed, `4` auth, `5` server.
43
+
44
+ `app dev` runs the widget in the real sandbox at real slot sizes, answers every host action from sample data
45
+ (a store, purchases, sign-in, signatures), refuses what a real page refuses, and prints every violation. Fix
46
+ those before submitting.
47
+
48
+ ## The widget
49
+
50
+ - One named export (a plain function component, the name in `entryComponent`), one ESM file, at most
51
+ 2,000,000 bytes, with `react`, `react-dom` and `react/jsx-runtime` left external. `chaindaddy app build`
52
+ enforces this.
53
+ - Props: `appData` (the token's data), `settings` (the owner's settings, from your `configSchema`), `theme`
54
+ (use its colors), `connectedAddress`, `signedIn`, `isOwner`, `breakpoint`, `onAction`, and
55
+ `subscribeStore` (with `iap:purchase`).
56
+ - Everything sensitive goes through `onAction({ type, … })`, which resolves `{ ok, data }` or `{ ok: false, error }`.
57
+
58
+ ## Permissions (`capp.json` → `permissions`)
59
+
60
+ Declare only what you use; the owner sees each one before installing and the scan checks the bundle matches.
61
+
62
+ | Permission | Lets the app |
63
+ |---|---|
64
+ | `iap:purchase` | Sell from this token's store: `onAction({ type: 'iap-purchase', sku, quantity })`. The page shows its own sheet and the viewer's wallet pays; your app never sees a wallet or names an amount |
65
+ | `net:connect` | Reach your own servers listed in `network.origins` (exact `https://` / `wss://` hosts), and ask for a `session-token` |
66
+ | `fetch:self-api` | Call the platform API (`/api/...`) with no cookies |
67
+ | `storage:local` | Keep `localStorage` between visits (512K characters) |
68
+ | `notify:user` | Send the signed-in user a notification (5 a day per user) |
69
+ | `wallet:trade` | Open the token's Buy sheet |
70
+ | `wallet:transaction`, `wallet:sign` | Propose a transaction or an EIP-712 signature, limited to your token and contracts declared in `wallet`; the viewer sees it decoded and confirms |
71
+ | `clipboard:write`, `navigate:internal`, `ui:fullscreen` | Clipboard, move to another page, fullscreen - each on a tap |
72
+
73
+ Data scopes go in `requiredData` (`profile:basic`, `market:price`, `market:holders`, …).
74
+
75
+ ## Selling inside the app
76
+
77
+ ```ts
78
+ const res = await onAction({ type: 'iap-purchase', sku: 'credits.100', quantity: 1, appAccountToken: user.id });
79
+ // res.data.status: 'confirmed' | 'paid' | 'cancelled' | 'failed'; 'paid' settles later
80
+ props.subscribeStore?.((e) => {
81
+ if (e.type === 'store:order' && e.status === 'confirmed') unlock(e.sku, e.receipt);
82
+ if (e.type === 'store:entitlements') refreshInventory();
83
+ });
84
+ ```
85
+
86
+ Read the catalog and the viewer's items with `api-call` (`/api/v2/iap/public/…`, `/api/v2/iap/me/…`). An app
87
+ sells from the store of the token page it is on. The store itself: `sell-items-in-your-app`.
88
+
89
+ ## Your own server
90
+
91
+ Declare `net:connect` with your origins. Get `onAction({ type: 'session-token' })` → `{ token, expiresAt }`, send
92
+ it to your server, and verify it there (`verify-receipts-and-sessions`). To sign a visitor in, send
93
+ `onAction({ type: 'sign-in' })` from a button they tap - don't ask their wallet to sign a login of your own.
94
+
95
+ ## Runners
96
+
97
+ A runner receives signed events (`runner.events` in the manifest) at `runner.endpointUrl`. Verify
98
+ `X-App-Signature` (hex HMAC-SHA256 of the raw body, secret hex-decoded) before processing, dedupe by
99
+ `eventId`, and answer quickly. Per-token data goes through the Action API (`apps:storage:write`).
100
+
101
+ ## Paid apps
102
+
103
+ Every app is free to install. Charging for an app itself, with a revenue share for its developer, is in
104
+ development and not live: the beta agreement includes no payment to developers. What works today is selling
105
+ items for the token from inside the app (above), at 2.5% or 0% on a Chain Daddy launchpad token. Check
106
+ https://docs.chaindaddy.io/developer/paid-apps for the current state before promising anything.
107
+
108
+ ## Mistakes to avoid
109
+
110
+ - `eval`, `new Function` or `document.cookie`: the frame refuses them and the scan fails the build.
111
+ - Fetching anything not declared: images and calls to other hosts are blocked and reported as violations;
112
+ enough reports disable the app on every page.
113
+ - Setting `location` or following a link in the frame: it takes your app off the page. Use `navigate`.
114
+ - Keying signed-in state on `connectedAddress`; use the `signedIn` prop.
115
+ - Putting a store key, API key or secret in the bundle. The bundle is public.
116
+ - Declaring permissions you don't use: review refuses them, and owners see them.
117
+ - An app name another developer uses: names are unique.
118
+
119
+ ## Docs
120
+
121
+ https://docs.chaindaddy.io/developer/quickstart · widgets · manifest · actions · security · runners ·
122
+ submission · build-a-game · engine-apps (Godot and Unity web exports) - all under
123
+ https://docs.chaindaddy.io/developer/
@@ -0,0 +1,67 @@
1
+ ---
2
+ name: check-token-health
3
+ description: Read the health of the tokens a wallet has registered on Chain Daddy - the 0-100 score, which registrations are at risk or counting down to expiry, why, and what the owner can do. Read-only. Use when someone asks how their tokens are doing, whether they could lose a ticker, or what their health score means.
4
+ license: MIT
5
+ ---
6
+
7
+ # Check token health
8
+
9
+ Every registered token has a health score from 0 to 100, updated daily. If it stays **below 60 for 30 days
10
+ in a row**, the registration expires and anyone who can prove ownership of the token can register the ticker,
11
+ first come, first served. Read-only: this skill changes nothing.
12
+
13
+ ## Tools
14
+
15
+ | Need | MCP | CLI | HTTP |
16
+ |---|---|---|---|
17
+ | Every registration a wallet owns | `list_crowns` (`owner`) | `chaindaddy status --wallet <addr> --json` | - |
18
+ | One token's score | `check_heartbeat` | `chaindaddy status --symbol S --chain base` | `GET /api/v2/heartbeat/{symbol}/{chain}` (EVM) |
19
+ | Score history | - | - | `GET /api/v2/heartbeat/{symbol}/{chain}/history` |
20
+ | Holder spread | `get_holders` | - | `GET /api/v2/registration/{id}/holders` |
21
+
22
+ HTTP calls from your own code need `X-API-Key: cd_live_…`. The remote MCP server's read tools need no key.
23
+
24
+ ## Steps
25
+
26
+ 1. List the wallet's registrations. Ask for the wallet address; never for a key or a signature.
27
+ 2. Read each one's score and classify it:
28
+
29
+ | Score | Status | Meaning |
30
+ |---|---|---|
31
+ | 80-100 | Healthy | In good standing |
32
+ | 65-79 | Moderate | In good standing, worth a look |
33
+ | 60-64 | At risk | The owner gets a "Token at risk" alert |
34
+ | below 60 | Inactive | The 30-day countdown is running |
35
+ | below 60 for 30 days | Expired | The ticker is open to register again |
36
+
37
+ A registration in its first 30 days is in its **grace period**: tracked, but it can't be reclaimed
38
+ whatever the score. Releasing and registering again doesn't reset it.
39
+ 3. Show a short table, worst first: symbol, chain, score, status, and days left where a countdown runs.
40
+ 4. For each token at risk, explain the score's parts:
41
+ - **Age, 50%:** full marks for 30 days, then steps down over two years (90, 80, 70, 60, then 50). An older
42
+ token needs real trading and holders to stay above 60.
43
+ - **Trading volume, 30%:** smoothed (last 24 h 70%, 7-day average 30%) and capped at 100x the pool's
44
+ liquidity, so wash trading doesn't help.
45
+ - **Holder spread, 20%:** concentration in the top 10 and top 50 wallets, flagged wallets (snipers,
46
+ bundlers, insiders, sandwich traders), how many wallets hold half the supply, and the raw count.
47
+ Splitting a balance across new wallets lowers it.
48
+ 5. Say what helps: real trading, supply spread across real holders, health alerts on, and registering from a
49
+ multisig so a teammate can act. Point to the token manager's **Health** tab.
50
+
51
+ ## If it expired
52
+
53
+ Search shows it as **Inactive**. The original owner has no special claim: whoever registers first, at the
54
+ normal price, keeps it, and the page's details and manager roles are cleared. Tell the owner plainly and
55
+ point them to https://docs.chaindaddy.io/getting-started/import-an-existing-token. Don't register it for them.
56
+
57
+ ## Mistakes to avoid
58
+
59
+ - Suggesting trades with yourself, splitting wallets or other ways to game the score. They are filtered out
60
+ or lower it.
61
+ - Promising support can recover an expired ticker someone else registered. It can't.
62
+ - Reading the score as a rating of the token's quality or safety. It measures activity.
63
+
64
+ ## Docs
65
+
66
+ - https://docs.chaindaddy.io/platform/heartbeat
67
+ - https://docs.chaindaddy.io/alerts/