Skip to content

Commit 53d5a22

Browse files
author
S. K. Rotwang MMMMMMMMM
committed
x402: settle through Coinbase CDP facilitator when CDP keys present
Route settlement through Coinbase's hosted x402 facilitator whenever COINBASE_API_KEY/SECRET (or X402_CDP_*/CDP_*) are set, which lists the service in the Coinbase x402 Bazaar and gives a 1k/month free settlement tier. No business/KYB verification required to be a seller (verified live against the CDP /supported endpoint: exact@base ok). - config: x402CdpApiKeyId/Secret with COINBASE_*/CDP_* fallbacks - x402: facilitator-mode selection (cdp|url) + shared createFacilitatorClient() used by both the static paywall and the dynamic custom top-up, so a signed challenge can't be verified against the wrong facilitator. Credentials are read at dispatch time and never stored on the public describePaywall surface. - describePaywall exposes facilitator_mode - docs (privacy/terms/index/landing/panel) + README re-point to Coinbase's facilitator (GDPR processor accuracy) - +5 tests (544 green)
1 parent 0de196f commit 53d5a22

12 files changed

Lines changed: 193 additions & 35 deletions

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,8 @@ EIP-3009 `transferWithAuthorization` for USDC on Base mainnet.
198198
| -------------------------- | -------------------------------- | ---------------------------------------------------------------------- |
199199
| `X402_RECIPIENT_ADDRESS` | (empty — paywall off) | Recipient wallet on the chosen network. 0x-prefixed 20-byte hex. |
200200
| `X402_NETWORK` | `eip155:8453` (Base mainnet) | Any CAIP-2 EVM network the facilitator supports. |
201-
| `X402_FACILITATOR_URL` | `https://x402.org/facilitator` | Use a production facilitator for mainnet (see x402.org/ecosystem). |
201+
| `COINBASE_API_KEY` + `COINBASE_API_SECRET` | (empty) | **Recommended.** A [CDP API key](https://docs.cdp.coinbase.com/x402/quickstart-for-sellers). When both are set, settlement routes through Coinbase's hosted facilitator (`api.cdp.coinbase.com/platform/v2/x402`) and the service is auto-listed in the **Coinbase x402 Bazaar**. No business/KYB verification needed; 1k free settlements/month. Accepts `X402_CDP_API_KEY_ID` / `CDP_API_KEY_ID` aliases. |
202+
| `X402_FACILITATOR_URL` | `https://facilitator.openx402.ai` | Fallback facilitator used only when no CDP credentials are set. |
202203
| `X402_FEED_PRICE` | `$0.05` | Money-formatted (`$0.05`) or atomic units (`50000`). |
203204
| `X402_PAYWALL_DESCRIPTION` || Shown on `/`, `/v1/paywall`, and the stats dashboard. |
204205
| `X402_MAX_TIMEOUT_SECONDS` | `120` | Maximum settlement window per call. |

docs/index.html

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -681,18 +681,20 @@ <h3 id="penny-oracle">Penny Oracle — /v1/q/* atomic-fact endpoints</h3>
681681
<code>Payment-Required</code> header carrying machine-readable
682682
<code>PaymentRequirements</code>; the client signs an EIP-3009
683683
<code>transferWithAuthorization</code> off-chain and retries with a
684-
<code>X-PAYMENT</code> header. We delegate verify + settle to the
685-
<a href="https://facilitator.openx402.ai">openx402.ai</a> facilitator,
686-
which broadcasts the EIP-3009 transfer on Base. No API keys, no
687-
subscriptions, no gas for the payer — the facilitator pays the gas
688-
and stays solvent from a small spread on the underlying USDC.
684+
<code>X-PAYMENT</code> header. We delegate verify + settle to
685+
<a href="https://docs.cdp.coinbase.com/x402/welcome">Coinbase&rsquo;s CDP</a>
686+
facilitator, which broadcasts the EIP-3009 transfer on Base. No API
687+
keys for the payer, no subscriptions, no gas for the payer — the
688+
facilitator pays the gas. (Settling through Coinbase also lists the
689+
service in the Coinbase x402 Bazaar.)
689690
</p>
690691
<p>
691692
Free metadata endpoint for budgeting before any paid call:
692693
</p>
693694
<pre><code>curl https://api.seneschal.space/v1/paywall
694695
# → {"protocol":"x402","network":"eip155:8453",
695-
# "facilitator":"https://facilitator.openx402.ai",
696+
# "facilitator":"https://api.cdp.coinbase.com/platform/v2/x402",
697+
# "facilitator_mode":"cdp",
696698
# "payTo":"0x46Ba634261566CF242c853d1f49511f9268ba674",
697699
# "scheme":"exact (EIP-3009 transferWithAuthorization)",
698700
# "routes":[{"endpoint":"GET /v1/premium/opportunities","price":"$0.05",...},

docs/landing.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,7 @@ <h2>Other premium endpoints (x402 micropayments)</h2>
407407
<p>
408408
Pay-per-call endpoints on Base mainnet, settled in USDC via the x402
409409
protocol &mdash; no API keys, no subscriptions, no gas for the payer
410-
(the <a href="https://facilitator.openx402.ai">openx402.ai</a>
410+
(<a href="https://docs.cdp.coinbase.com/x402/welcome">Coinbase&rsquo;s CDP</a>
411411
facilitator pays the EIP-3009 transfer gas):
412412
</p>
413413
<ul>

docs/panel.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ <h1>Seneschal Control Panel <span style="font-weight:400;color:#cfe3ff;font-size
182182
<!-- ── New watch ───────────────────────────────────────────────── -->
183183
<section class="panel active" id="panel-watch">
184184
<h2>Create a new payment-monitoring watch</h2>
185-
<p class="muted">You'll be asked to sign an <code>EIP-3009 transferWithAuthorization</code> for $0.10 USDC on Base. Once payment is verified by the openx402 facilitator the watch is created with $0.10 of starter credit (~5 idle days at the $0.02/day rate). Top up any time.</p>
185+
<p class="muted">You'll be asked to sign an <code>EIP-3009 transferWithAuthorization</code> for $0.10 USDC on Base. Once payment is verified by Coinbase's x402 facilitator the watch is created with $0.10 of starter credit (~5 idle days at the $0.02/day rate). Top up any time.</p>
186186

187187
<label class="field" for="w-chain">Chain</label>
188188
<select id="w-chain">
@@ -418,7 +418,7 @@ <h4>Equivalent API call</h4>
418418
// topup-5,historical}) it pulls the x402 challenge from the
419419
// `payment-required` response header, asks the wallet to sign
420420
// an EIP-3009 transferWithAuthorization, and resubmits with the
421-
// base64-encoded `x-payment` header. The openx402 facilitator
421+
// base64-encoded `x-payment` header. Coinbase's CDP facilitator
422422
// handles settlement; we never see a private key.
423423
// 4. Persists created watches in localStorage so the user can top
424424
// up / poll / cancel them later — server has no concept of

docs/privacy.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
<h1>Privacy policy</h1>
4343
<p class="meta">Effective 2026-05-22. Service operated under the brand name <strong>Seneschal</strong>.</p>
4444

45-
<div class="callout info"><strong>Short version.</strong> We don&rsquo;t want your data. We store the minimum needed to deliver the service you paid for, encrypt the sensitive bits at rest, share with no one except the openx402 payment facilitator and the webhook endpoint <em>you</em> chose, and delete it all when you cancel. No cookies, no analytics, no accounts, no email, no name.</p></div>
45+
<div class="callout info"><strong>Short version.</strong> We don&rsquo;t want your data. We store the minimum needed to deliver the service you paid for, encrypt the sensitive bits at rest, share with no one except Coinbase&rsquo;s x402 payment facilitator and the webhook endpoint <em>you</em> chose, and delete it all when you cancel. No cookies, no analytics, no accounts, no email, no name.</p></div>
4646

4747
<h2>1. Who we are</h2>
4848
<p>Seneschal is an anonymous, open-source operator of an Ethereum block builder plus a small bundle of paid data services (Private Watch, premium liquidation feeds, agent-payable single-fact endpoints). The natural person behind it operates pseudonymously as <code>Rotwang9000</code>. There is no registered company.</p>
@@ -74,15 +74,15 @@ <h2>3. Cookies and local storage</h2>
7474

7575
<h2>4. Who we share data with</h2>
7676
<ul>
77-
<li><strong>openx402 facilitator</strong> (<a href="https://facilitator.openx402.ai">facilitator.openx402.ai</a>). Verifies and settles your USDC payments on Base. Sees the payer address (you), the recipient address (us), the amount, and your signature. Does <em>not</em> see your view key, address, or webhook URL.</li>
77+
<li><strong>Coinbase Developer Platform (CDP) x402 facilitator</strong> (<a href="https://docs.cdp.coinbase.com/x402/welcome"><code>api.cdp.coinbase.com</code></a>, operated by Coinbase). Verifies and settles your USDC payments on Base. Sees the payer address (you), the recipient address (us), the amount, and your signature, and screens the payer address against sanctions / high-risk lists (Know-Your-Transaction). Does <em>not</em> see your view key, address, or webhook URL.</li>
7878
<li><strong>Your webhook endpoint.</strong> When funds land we POST a signed JSON body to the URL you gave us. You control that endpoint; we&rsquo;re a sender, not a controller.</li>
7979
<li><strong>The local NFPT scanner</strong> (a Monero / Zcash full-node front-end we operate on the same machine). Receives your view key and address in memory in order to scan. Same retention as the watch row.</li>
8080
<li><strong>Court orders or other lawfully binding requests.</strong> We will comply if and only if we receive a valid order in our operating jurisdiction (England &amp; Wales, see Terms §15). Because we deliberately don&rsquo;t collect identity data, there is usually nothing on you to hand over.</li>
8181
</ul>
8282
<p>We do <strong>not</strong> sell, rent, lease, or barter your data. We have no advertising partners. We have no &ldquo;data analytics&rdquo; partners. We do not enrich your data against third-party data brokers.</p>
8383

8484
<h2>5. International transfers</h2>
85-
<p>Our server lives in the EU (Netherlands). The openx402 facilitator may be hosted outside the UK / EEA; transferring payment metadata to it falls under Article 49(1)(b) UK GDPR (<em>transfer necessary for performance of a contract</em>) and is limited to the payer/recipient/amount/signature tuple needed to verify the payment.</p>
85+
<p>Our server lives in the EU (Netherlands). Coinbase&rsquo;s CDP facilitator is operated from the United States; transferring payment metadata to it falls under Article 49(1)(b) UK GDPR (<em>transfer necessary for performance of a contract</em>) and is limited to the payer/recipient/amount/signature tuple needed to verify the payment.</p>
8686

8787
<h2>6. Security</h2>
8888
<ul>

docs/terms.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
<h1>Terms of service</h1>
4141
<p class="meta">Effective 2026-05-22. By using any paid endpoint on <code>api.seneschal.space</code> or the control panel at <code>panel.seneschal.space</code> you agree to these terms.</p>
4242

43-
<div class="callout danger"><strong>No refunds.</strong> All Seneschal payments settle on-chain through the openx402 facilitator. We cannot reverse a settled USDC transfer. There are no refunds for any reason, including but not limited to accidental cancellation, browser data loss, or buyer&rsquo;s remorse. Top up only what you need.</div>
43+
<div class="callout danger"><strong>No refunds.</strong> All Seneschal payments settle on-chain through Coinbase&rsquo;s x402 facilitator. We cannot reverse a settled USDC transfer. There are no refunds for any reason, including but not limited to accidental cancellation, browser data loss, or buyer&rsquo;s remorse. Top up only what you need.</div>
4444

4545
<h2>1. Definitions</h2>
4646
<ul>
@@ -74,7 +74,7 @@ <h2>4. Eligibility &amp; acceptable use</h2>
7474
<h2>5. Fees, billing &amp; the credit meter</h2>
7575
<ul>
7676
<li>All prices are denominated in USDC on Base mainnet (CAIP-2 <code>eip155:8453</code>). Live prices are exposed at <a href="https://api.seneschal.space/v1/private/info"><code>/v1/private/info</code></a> and <a href="https://api.seneschal.space/.well-known/x402"><code>/.well-known/x402</code></a>.</li>
77-
<li>Payments use the <a href="https://docs.x402.org">x402 protocol</a>: you sign an EIP-3009 <code>transferWithAuthorization</code>, the openx402 facilitator settles it on-chain, we credit your Watch.</li>
77+
<li>Payments use the <a href="https://docs.x402.org">x402 protocol</a>: you sign an EIP-3009 <code>transferWithAuthorization</code>, Coinbase&rsquo;s x402 facilitator settles it on-chain, we credit your Watch.</li>
7878
<li>Watches drain credit at a per-day idle rate plus a per-webhook delivery rate. Both rates are published in <code>/v1/private/info</code>. The rates may change with at least 7 days&rsquo; notice on this page; we will not retro-bill credit you have already prepaid.</li>
7979
<li>A one-shot <code>low_credit</code> webhook fires when your balance falls below the documented threshold so you have time to top up before the Watch pauses.</li>
8080
<li>Top-ups are available in fixed tiers ($0.10 / $1 / $5) and in any variable amount from $0.10 to $25 via <code>/v1/private/topup-custom</code> (also exposed via the control panel slider).</li>

package-lock.json

Lines changed: 22 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"test:watch": "node --experimental-vm-modules node_modules/.bin/jest --watch"
1616
},
1717
"dependencies": {
18+
"@coinbase/x402": "^2.1.0",
1819
"@fastify/cors": "^11.2.0",
1920
"@fastify/rate-limit": "^10.3.0",
2021
"@modelcontextprotocol/sdk": "^1.29.0",

src/config.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,21 @@ export const config = Object.freeze({
7979
// USDC with proper EIP-712 metadata. Override at any time via
8080
// X402_FACILITATOR_URL.
8181
x402FacilitatorUrl: asString('X402_FACILITATOR_URL', 'https://facilitator.openx402.ai'),
82+
// Coinbase Developer Platform (CDP) facilitator credentials. When
83+
// BOTH are present the paywall routes settlement through Coinbase's
84+
// hosted facilitator (api.cdp.coinbase.com/platform/v2/x402) instead
85+
// of the permissionless openx402 URL above. Two wins from that:
86+
// 1. The service is auto-catalogued in *Coinbase's* x402 Bazaar —
87+
// the largest discovery surface for agent buyers.
88+
// 2. Free tier of 1,000 settlements/month, then $0.001 each.
89+
// No business/KYB verification is required: a plain CDP API key is
90+
// enough to *receive* x402 payments to your own wallet (verified
91+
// against the live /supported endpoint). We accept the generic
92+
// COINBASE_API_KEY / COINBASE_API_SECRET names too, since that's the
93+
// pair the CDP portal hands you, with X402_CDP_* / CDP_* overrides
94+
// for operators who keep separate keys per service.
95+
x402CdpApiKeyId: asString('X402_CDP_API_KEY_ID', asString('CDP_API_KEY_ID', asString('COINBASE_API_KEY', ''))),
96+
x402CdpApiKeySecret: asString('X402_CDP_API_KEY_SECRET', asString('CDP_API_KEY_SECRET', asString('COINBASE_API_SECRET', ''))),
8297
// Price per call for each premium endpoint, expressed in the x402
8398
// Money format ("$0.05" = 5 ¢). The facilitator quotes the USDC
8499
// atomic amount on Base from this.

src/private-watch-custom.js

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
effectiveRatesForRow
3434
} from './private-watch.js';
3535
import { topupWatch as storeTopupWatch, getWatch as storeGetWatch } from './private-watch-store.js';
36+
import { createFacilitatorClient } from './x402.js';
3637

3738
/**
3839
* Canonical USDC addresses per CAIP-2 network. Anything outside
@@ -162,18 +163,24 @@ export function decodePaymentHeader(headerValue) {
162163
}
163164

164165
/**
165-
* Lazy-loaded HTTPFacilitatorClient. The @x402 toolchain depends
166-
* on viem + a handful of other ESM modules that take ~250 ms to
167-
* pull in cold; only instantiate when the first dynamic-topup
168-
* request actually arrives. We cache the constructor (not the
169-
* instance) so the test suite can swap in a fake.
166+
* Lazy-loaded facilitator client. The @x402 toolchain depends on
167+
* viem + a handful of other ESM modules that take ~250 ms to pull in
168+
* cold; only instantiate when the first dynamic-topup request
169+
* actually arrives. We cache the promise (not the instance) so the
170+
* test suite can swap in a fake.
171+
*
172+
* Delegates to the shared `createFacilitatorClient` selector so the
173+
* dynamic top-up settles through the *same* facilitator (CDP or
174+
* openx402) as the static @x402/fastify routes — otherwise a payment
175+
* signed against our challenge could be verified against the wrong
176+
* facilitator. Receives the whole `x402Cfg` (not just a URL) because
177+
* CDP mode needs the credentials, not an endpoint.
170178
*
171179
* Tests inject a fake via the `facilitatorFactory` argument to
172180
* `registerCustomTopupRoute`.
173181
*/
174-
async function defaultFacilitatorFactory(url) {
175-
const { HTTPFacilitatorClient } = await import('@x402/core/server');
176-
return new HTTPFacilitatorClient({ url });
182+
async function defaultFacilitatorFactory(x402Cfg) {
183+
return createFacilitatorClient(x402Cfg);
177184
}
178185

179186
/**
@@ -223,7 +230,7 @@ export function registerCustomTopupRoute(app, deps) {
223230
throw new Error('x402 paywall disabled; cannot dispatch to facilitator');
224231
}
225232
if (!facilitatorP) {
226-
facilitatorP = Promise.resolve(facilitatorFactory(x402Cfg.facilitatorUrl));
233+
facilitatorP = Promise.resolve(facilitatorFactory(x402Cfg));
227234
}
228235
return facilitatorP;
229236
}

0 commit comments

Comments
 (0)