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
12 changes: 12 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,15 @@ jobs:
working-directory: packages/tollway-server
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

- name: Publish @tollway/payments
run: npm publish --provenance --access public
working-directory: packages/tollway-payments
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

- name: Publish @tollway/cli
run: npm publish --provenance --access public
working-directory: packages/tollway-cli
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
32 changes: 32 additions & 0 deletions demo/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
FROM node:20-alpine AS builder

WORKDIR /app

# Copy workspace root
COPY package.json package-lock.json turbo.json tsconfig.base.json ./

# Copy packages needed by demo
COPY packages/tollway-server ./packages/tollway-server

# Copy demo
COPY demo ./demo

# Install deps and build
RUN npm ci --workspace=packages/tollway-server --workspace=demo
RUN npm run build --workspace=packages/tollway-server
RUN cd demo && npm run build

# ─── Runtime image ────────────────────────────────────────────────────────────
FROM node:20-alpine

WORKDIR /app

COPY --from=builder /app/demo/dist ./dist
COPY --from=builder /app/packages/tollway-server/dist ./node_modules/@tollway/server/dist
COPY --from=builder /app/packages/tollway-server/package.json ./node_modules/@tollway/server/package.json
COPY --from=builder /app/demo/node_modules ./node_modules

ENV PORT=3000
EXPOSE 3000

CMD ["node", "dist/server.js"]
25 changes: 25 additions & 0 deletions demo/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"name": "@tollway/demo",
"version": "0.1.0",
"description": "Live demo server for the Tollway protocol",
"private": true,
"type": "module",
"scripts": {
"build": "tsc",
"start": "node dist/server.js",
"dev": "tsx server.ts"
},
"dependencies": {
"@tollway/server": "^0.1.0",
"express": "^4.19.2"
},
"devDependencies": {
"@types/express": "^4.17.21",
"@types/node": "^20.14.10",
"tsx": "^4.19.0",
"typescript": "^5.5.4"
},
"engines": {
"node": ">=18"
}
}
189 changes: 189 additions & 0 deletions demo/server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
/**
* Tollway Demo Server
*
* A live example of @tollway/server in action.
* - Serves /.well-known/tollway.json
* - Validates agent identity headers
* - Returns 402 for 'train' scope
* - Serves sample articles with structured metadata
*
* Deploy to Railway / Render / Fly with the included Dockerfile.
*/

import express from 'express';
import { tollwayMiddleware } from '@tollway/server';

const app = express();
const PORT = parseInt(process.env.PORT ?? '3000', 10);

// ─── Tollway Middleware ────────────────────────────────────────────────────────

app.use(
tollwayMiddleware({
policy: {
freeRequestsPerDay: 500,
trainingAllowed: false,
attributionRequired: true,
attributionFormat: '{title} via Tollway Demo ({url})',
cacheAllowed: true,
cacheTtlSeconds: 3600,
prohibitedActions: ['scrape_bulk'],
paymentRequiredActions: ['train'],
pricing: [
{ action: 'read', price: '0.001' },
{ action: 'summarize', price: '0.005' },
{ action: 'train', price: '0.05' },
],
requestsPerMinute: 30,
requestsPerDay: 500,
},
paymentAddress: process.env.PAYMENT_ADDRESS ?? '0x0000000000000000000000000000000000000000',
paymentNetwork: 'base-sepolia',
enableLogging: true,
onAgentRequest: (identity) => {
console.log(`[agent] ${identity.did} — ${identity.scope} — ${identity.purpose}`);
},
}),
);

// ─── Sample Content ────────────────────────────────────────────────────────────

const ARTICLES: Record<string, { title: string; author: string; date: string; body: string }> = {
'intro-to-tollway': {
title: 'Introducing the Tollway Protocol',
author: 'Tollway Team',
date: '2026-03-01',
body: `
The Tollway protocol brings structure to how AI agents access the web.
Just as robots.txt told crawlers what they could read, tollway.json tells
agents what they can do — and how much it costs.

Agents identify themselves with a DID (Decentralized Identifier) and sign
every request with an Ed25519 key. Sites respond with policies covering
training permissions, pricing, attribution requirements, and rate limits.

When a resource requires payment, the server returns HTTP 402 with
payment details. The agent pays in USDC on Base and retries.
`.trim(),
},
'agent-identity': {
title: 'Agent Identity in the Agentic Web',
author: 'Tollway Research',
date: '2026-03-05',
body: `
Today's AI agents browse the web pseudonymously. They present no identity,
accept no responsibility, and operate entirely in the shadows of user-agents.

The agentic web needs something better: a lightweight identity layer that
lets agents announce who they are, what they want, and who they work for —
without requiring central registries or heavyweight authentication flows.

The Tollway DID approach uses the W3C did:key method with Ed25519 keypairs.
Every agent generates a keypair; the public key becomes the DID. Requests
are signed so servers can verify the agent hasn't been spoofed.
`.trim(),
},
'x402-micropayments': {
title: 'x402: HTTP Payments for the Machine Economy',
author: 'Tollway Engineering',
date: '2026-03-08',
body: `
HTTP 402 Payment Required has been a reserved status code since 1991,
waiting for its moment. That moment is now.

The x402 standard (being standardized by Coinbase and others) defines how
servers express payment requirements and how clients fulfill them. Tollway
builds on x402 with USDC on Base for fast, cheap, auditable micropayments.

An agent that wants to train on content sends a signed request. The server
responds with 402 and a payment address. The agent sends USDC on-chain and
includes the transaction hash in a retry. The server verifies and responds.
`.trim(),
},
};

// ─── Routes ───────────────────────────────────────────────────────────────────

app.get('/', (_req, res) => {
res.setHeader('Content-Type', 'text/html');
res.send(`<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tollway Demo</title>
<meta name="description" content="Live demo of the Tollway protocol — robots.txt rebuilt for the agentic era">
<meta property="og:title" content="Tollway Demo">
<meta property="og:description" content="Live demo of the Tollway protocol — robots.txt rebuilt for the agentic era">
</head>
<body>
<h1>Tollway Protocol — Live Demo</h1>
<p>This server demonstrates the <a href="https://tollway.dev">Tollway protocol</a>.</p>
<h2>Try it</h2>
<pre>npx @tollway/cli fetch ${process.env.PUBLIC_URL ?? 'http://localhost:3000'}/articles/intro-to-tollway</pre>
<h2>Articles</h2>
<ul>
${Object.keys(ARTICLES).map(slug => `<li><a href="/articles/${slug}">${ARTICLES[slug].title}</a></li>`).join('\n ')}
</ul>
<h2>Policy</h2>
<p>See <a href="/.well-known/tollway.json">/.well-known/tollway.json</a></p>
</body>
</html>`);
});

app.get('/articles/:slug', (req, res) => {
const article = ARTICLES[req.params.slug];
if (!article) {
return res.status(404).json({ error: 'Article not found' });
}

const url = `${req.protocol}://${req.get('host')}${req.originalUrl}`;

res.setHeader('Content-Type', 'text/html');
res.send(`<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${article.title}</title>
<meta name="description" content="${article.body.slice(0, 150).replace(/\n/g, ' ')}">
<meta property="og:title" content="${article.title}">
<meta property="og:description" content="${article.body.slice(0, 150).replace(/\n/g, ' ')}">
<link rel="canonical" href="${url}">
</head>
<body>
<article>
<h1>${article.title}</h1>
<p><em>By ${article.author} — ${article.date}</em></p>
<div>${article.body.split('\n').map(p => `<p>${p}</p>`).join('\n ')}</div>
</article>
<footer>
<p>Content served via <a href="https://tollway.dev">Tollway</a></p>
</footer>
</body>
</html>`);
});

app.get('/articles', (_req, res) => {
res.json({
articles: Object.entries(ARTICLES).map(([slug, a]) => ({
slug,
title: a.title,
author: a.author,
date: a.date,
url: `/articles/${slug}`,
})),
});
});

app.get('/health', (_req, res) => {
res.json({ status: 'ok', version: '0.1.0', protocol: 'tollway/0.1' });
});

// ─── Start ────────────────────────────────────────────────────────────────────

app.listen(PORT, () => {
console.log(`Tollway demo server running on port ${PORT}`);
console.log(`Policy: http://localhost:${PORT}/.well-known/tollway.json`);
console.log(`Try: npx @tollway/cli fetch http://localhost:${PORT}/articles/intro-to-tollway`);
});
8 changes: 8 additions & 0 deletions demo/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"extends": "../tsconfig.base.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "."
},
"include": ["server.ts"]
}
Loading
Loading