Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 27 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -725,6 +725,19 @@ app.get("/x402", (_req, res) => {
const researchAccepts = [baseAcceptResearch];
const deepAccepts = [baseAcceptDeep];

// Dedicated SKALE-gasless route — additive (2026-05-15).
// /deep-research/skale is functionally identical to /deep-research but exposes
// SKALE (eip155:1187947933) via PayAI as the sole accepts entry. This keeps
// the Bazaar-indexed /deep-research route Base-only (per 579923c2 constraint:
// CDP indexer rejects routes whose accepts[] contains networks outside its
// enum, even when Base is also present), while giving SKALE-native callers a
// real, callable, gasless endpoint they can curl and pay on SKALE today.
//
// Deliberately omits the bazaar discovery extension on this route: CDP
// doesn't index SKALE-only resources anyway, and including the extension
// surface here invites the exact rejection-pattern we're protecting against.
const deepSkaleAccepts = [skaleAcceptDeep];

// Batch pricing: $0.10 for up to 5 queries (same price structure as deep)
const BATCH_PRICE = "$0.10";
const SKALE_PRICE_BATCH = {
Expand Down Expand Up @@ -762,6 +775,13 @@ const routeConfig = {
mimeType: "application/json",
extensions: { ...bazaarDeep },
},
// SKALE-dedicated alias of /deep-research. Same backend, different payment
// network. No bazaar extension on purpose (see deepSkaleAccepts comment).
"POST /deep-research/skale": {
accepts: deepSkaleAccepts,
description: "Load when an agent on SKALE Network needs gasless multi-source deep analysis. Same backend as /deep-research. Powered by Sonar Pro. $0.10 USDC per query on SKALE.",
mimeType: "application/json",
},
"POST /research/batch": {
accepts: batchAccepts,
description: "Load when an agent needs up to 5 research queries answered in parallel in a single call, with the same citation+confidence output as /research. $0.10 USDC per batch on Base.",
Expand Down Expand Up @@ -3231,9 +3251,14 @@ app.get("/demo/video", (_req, res) => {
// Body: { "query": "any natural-language question" }
// Price: $0.10 USDC — uses sonar-pro for deeper, more comprehensive research

app.post("/deep-research", async (req, res) => {
// /deep-research and /deep-research/skale share the same backend.
// Distinct routes in routeConfig give them distinct 402 challenges (Base-only
// vs SKALE-only accepts), but once payment is settled the body of the request
// is handled identically below. trackRequest tags each path independently so
// /traffic stats stay honest about which payment network the caller used.
app.post(["/deep-research", "/deep-research/skale"], async (req, res) => {
const { query } = req.body;
trackRequest(req, "deep-research");
trackRequest(req, req.path === "/deep-research/skale" ? "deep-research-skale" : "deep-research");

if (!query || typeof query !== "string" || query.trim().length === 0) {
return res.status(400).json({
Expand Down
30 changes: 24 additions & 6 deletions tests/api.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -183,15 +183,33 @@
expect(networks.some(n => n.includes("8453"))).toBe(true);
});

// Stellar and SKALE are intentionally NOT advertised on the Bazaar-indexed
// /research and /deep-research routes — CDP's indexer rejects routes whose
// accepts[] contains networks outside its enum (commit 579923c2, 2026-05-03).
// Stellar is intentionally NOT advertised on the Bazaar-indexed /research and
// /deep-research routes — CDP's indexer rejects routes whose accepts[]
// contains networks outside its enum (see commit 579923c2, 2026-05-03).
// Stellar accept definitions remain in the codebase (stellarAcceptResearch /
// stellarAcceptDeep) for a future dedicated route when we wire it up.
// SKALE has a dedicated route ship landing on a feature branch — tests for
// it live on that branch and merge to main with the route itself.
it.skip("402 response includes Stellar payment option (deferred — needs dedicated route)", () => {});
it.skip("402 response includes SKALE gasless option (deferred — see feat/skale-dedicated-route)", () => {});

// SKALE has its own dedicated route /deep-research/skale that advertises
// SKALE Network (eip155:1187947933) via PayAI as the only accept, so SKALE
// gasless callers get a real callable endpoint without disturbing CDP/Bazaar
// indexing of the main /research and /deep-research routes.
it("402 response on /deep-research/skale includes SKALE gasless option", async () => {
const { status, headers } = await api("POST", "/deep-research/skale", { query: "test" });
expect(status).toBe(402);
const challenge = decode402Header(headers);
expect(challenge).not.toBeNull();
const networks = challenge.accepts.map(a => a.network);
expect(networks.some(n => n.includes("1187947933"))).toBe(true);
});

it("402 response on /deep-research/skale advertises ONLY SKALE (no Base bleed)", async () => {
const { headers } = await api("POST", "/deep-research/skale", { query: "test" });
const challenge = decode402Header(headers);
const networks = challenge.accepts.map(a => a.network);

Check failure on line 209 in tests/api.test.js

View workflow job for this annotation

GitHub Actions / test (20)

tests/api.test.js > x402 Payment Flow > 402 response on /deep-research/skale advertises ONLY SKALE (no Base bleed)

TypeError: Cannot read properties of null (reading 'accepts') ❯ tests/api.test.js:209:32
// Pure SKALE — no Base, no Stellar. Keeps the route surface honest.
expect(networks.every(n => n.includes("1187947933"))).toBe(true);
});

it("402 response on /deep-research stays Base-only (CDP Bazaar compatibility)", async () => {
const { headers } = await api("POST", "/deep-research", { query: "test" });
Expand Down
Loading