About creator-paywall
creator-paywall is a Node/Express template for adding crypto subscription access to an existing creator product. It combines wallet-based SIWE login with on-chain payment verification and subscription middleware.
Key features
- 01SIWE wallet login
- 02Ethereum、Base、BSC 支持
- 03Native coin 与 ERC20 收款
- 04链上交易验证
- 05订阅鉴权中间件
- 06SQLite 存储,可替换数据库
Use cases
- 01为创作者的应用或 API 添加付费订阅
- 02按月或按年收取加密货币
- 03用订阅状态保护产品路由
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ starchild-ai-agent/ official-skills --skill creator-paywallAfter installing, type/creator-paywallin Claude Code to confirm it loaded.
Bundled files
References and scripts installed along with SKILL.md.
scripts/init.shtemplate/.env.example.gitignoreconfig.jspackage.jsonREADME.mdtemplate/public/index.htmltemplate/src/auth.jschains.jsdb.jspayments.jsserver.jsDetails
From the project README💸 Creator Paywall
A minimal, drop-in crypto subscription paywall. Two jobs only: wallet auth + subscription verification. Built to be embedded into a creator's existing product, not to be a platform.
Connect wallet → sign SIWE message → session (wallet = identity)
│
├─ pick plan (chain + token + monthly/yearly)
├─ transfer tokens to the creator's wallet
└─ submit txHash → backend verifies on-chain → subscription active
The core trick: the payer's address must equal the logged-in wallet. That's how a plain
transfer is matched to a user with zero memos or order IDs. Match rule is from == logged-in wallet.
When to use this
- Creator built something (app, dashboard, content, API) and wants paid access.
- They want crypto payments straight to their Starchild agent wallet — no Stripe, no signup.
- Subscription model only (monthly / yearly). Not pay-per-use metering, not fiat.
If the creator needs fiat/cards or automatic recurring charges, this is the wrong tool — tell them so.
What it is
A small Node/Express + viem app. ~900 LOC, SQLite store (swappable). Chains: Ethereum, Base, BSC. Tokens: native coin or any ERC20 (including the creator's own custom token).
template/
config.js ← the ONLY file most creators edit (chains / tokens / prices / wallet)
src/auth.js ← SIWE login + requireAuth / requireSubscription middleware
src/payments.js ← on-chain verification (txHash receipt + bounded getLogs scan)
src/chains.js ← viem clients + plan/price helpers
src/db.js ← SQLite (replace with your DB when integrating)
src/server.js ← REST API wiring everything together
public/index.html ← demo frontend: 3 views (landing / paywall / member), token-based auth
README.md ← integration docs for the creator
.env.example
Quick start (scaffold a new instance)
Step 1 — get the creator's receiving wallet. Default = their Starchild agent EVM wallet.
Call the wallet_info tool and use the ethereum address.
Step 2 — scaffold. Run the init script (copies template, writes .env, npm installs):
bash skills/creator-paywall/scripts/init.sh <target_dir> <creator_wallet> [jwt_secret] [siwe_domain]
# example:
bash skills/creator-paywall/scripts/init.sh output/projects/my-paywall 0xYourAgentWallet
jwt_secret auto-generates if omitted. siwe_domain defaults to localhost (set the real domain in prod).
Step 3 — set the plans. Edit config.js → PLANS: each entry is one (chain, token, monthly, yearly)
the user can pick. Stablecoins recommended (stable fiat value). For custom tokens, set the correct decimals.
Step 4 — run & preview.
# foreground for a quick check, or use the preview tool:
Use the preview tool: serve with dir=<target_dir>, command="node src/server.js", port=3007
(port must match PORT in .env). Then give the user /preview/<id>/.
Critical gotchas (learned building this — don't skip)
- Preview runs in an iframe → cookies are blocked. The frontend uses token-based auth
(
localStorage+Authorization: Bearer), NOT cookies. If you rewrite the frontend, keep this — cookie sessions silently fail inside the preview frame (login "does nothing"). - Preview is served under
/preview/<id>/. All frontend API calls MUST be relative (resolved againstdocument.baseURI), never absolute/api/...— absolute paths hit the root domain and 404, leaving a blank page. - Wallets are often blocked inside iframes. UI/state works in the frame, but to actually trigger MetaMask signing/transfer the user should open the preview in a new browser tab or a wallet in-app browser, OR publish to a public URL. The frontend already warns about this.
- Native coin (ETH/BNB) can't be log-scanned. The "Check payment" button uses
getLogswhich only catches ERC20Transferevents. Native payments must be confirmed via txHash submit (the UI handles this automatically after sending; manual paste is the fallback). - No background polling — by design. Confirmation is on-demand only (post-payment txHash poll, "Check payment" button, manual hash). Keeps public-RPC usage tiny. Don't add a block-scanning loop.
- Amount match uses
>=, not==. Safe for fee-on-transfer / rounding. Keep it.
Integrating into the creator's own product
The whole thing reduces to two middlewares:
import { requireAuth, requireSubscription } from "./src/auth.js";
app.get("/my/premium", requireAuth, requireSubscription, (req, res) => {
// req.wallet = user address; req.subscription.expires_at = unix expiry
res.json({ ... });
});
- Bring your own DB by replacing
src/db.js(everything else is storage-agnostic). - Session works via Bearer token (returned by
/api/login) — works in SPAs and iframes. - Subscriptions renew by the user paying again (
expires_atrolls forward). No on-chain auto-debit.
API surface
| Method | Route | Auth | Purpose |
|---|---|---|---|
| GET | /api/config | — | chains, plans, creator wallet |
| GET | /api/nonce?address=&chainId=&uri= | — | SIWE message to sign |
| POST | /api/login {message,signature,chainId} | — | verify → returns session token |
| POST | /api/logout | — | end session |
| GET | /api/me | token | wallet + subscription status |
| POST | /api/subscribe/intent {chainId,token,period} | token | start payment → amount + payTo |
| POST | /api/subscribe/verify {intentId,txHash} | token | confirm a tx (native + ERC20) |
| POST | /api/subscribe/check {intentId?} | token | scan for ERC20 payment |
| GET | /api/protected | token + subscription | example gated resource |
Verification rules (what counts as paid)
- Tx confirmed (chain-specific confirmations) and succeeded
- Recipient == creator wallet
- Sender == logged-in wallet
- Token matches the selected plan
- Amount ≥ plan price (
>=) - txHash not already used (idempotent)
Going to production
- Set a strong
JWT_SECRETand the realSIWE_DOMAINin.env. - Swap public RPCs in
config.jsfor your own (Alchemy/QuickNode) — setRPC_ETHEREUM/RPC_BASE/RPC_BSC. - Replace SQLite (
src/db.js) with your production DB. - Publish via the
community-publishskill for a public URL (also fixes the iframe-wallet limitation).