Skip to content
Merged

X402 #62

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
b4de6b8
First draft
badjer Sep 17, 2025
4f5a6a4
Simplify example
badjer Sep 17, 2025
3bed435
Simplify wrapper method; remove PaymentMaker.makePayment
badjer Sep 17, 2025
5c2b3f8
Update Solana PaymentMaker with signing logic
badjer Sep 17, 2025
fceeca3
Simplify: just add a new signing method
badjer Sep 18, 2025
beafcfd
Cleanup
badjer Sep 18, 2025
bf0cc65
Separate out x402 from EIP3009 bits
badjer Sep 18, 2025
af88214
Clean up: revert README.md and remove docs folder
badjer Sep 18, 2025
c8c7531
Restore /docs folder from main branch
badjer Sep 18, 2025
5ba867b
Update package-lock.json after rebase
badjer Sep 18, 2025
b9a2a58
Fix type errors and lint issues for X402 support
badjer Sep 18, 2025
e3411ab
Fix lint errors: prefix unused parameters with underscore
badjer Sep 18, 2025
02b786a
Fix lint warnings in x402Wrapper.ts
badjer Sep 18, 2025
8f91b71
Add Logger support to x402Wrapper with proper logging throughout
badjer Sep 18, 2025
dbeb8ff
Fix typecheck errors: correct FetchLike type signature and logger.err…
badjer Sep 18, 2025
44f7c57
Tests
badjer Sep 18, 2025
409bf19
Lint
badjer Sep 18, 2025
c5e20a6
Localhost test
badjer Sep 18, 2025
2eba9a5
Logging in test-client
badjer Sep 18, 2025
dc38657
Logging
badjer Sep 18, 2025
2f138a7
Properly handle X402 response formats
badjer Sep 18, 2025
0ce5970
Fix test mocks
badjer Sep 18, 2025
230aea1
Use ATXPAccount
badjer Sep 18, 2025
84eed1e
Revert "Use ATXPAccount"
badjer Sep 18, 2025
18a6141
Fix error message
badjer Sep 18, 2025
fd3e9fd
Temporary: base payemnt maker does X402 for integration testing
badjer Sep 18, 2025
0bce04b
Base64-encode header
badjer Sep 18, 2025
f627b12
WIP
badjer Sep 18, 2025
3373dd5
Working version with local signer
badjer Sep 18, 2025
a8bf4a1
Cleanup
badjer Sep 18, 2025
3f86585
WIP: RemoteSigner
badjer Sep 18, 2025
7453624
Cleanup
badjer Sep 18, 2025
53c7627
lint/typecheck
badjer Sep 19, 2025
ca44e23
Test fixes for error message change
badjer Sep 19, 2025
76f994c
X402 package
badjer Sep 19, 2025
1ad7ef6
WIP
badjer Sep 19, 2025
abd3982
No separate wrappers param
badjer Sep 19, 2025
421523a
Example uses MCP
badjer Sep 19, 2025
4689b55
Better MCP impl for server
badjer Sep 19, 2025
4fec4cd
Shorter test client
badjer Sep 19, 2025
d7c0262
Shorter server
badjer Sep 19, 2025
b924088
Cleanup
badjer Sep 19, 2025
1b89594
Working example
badjer Sep 19, 2025
11fb1bb
Test locally
badjer Sep 19, 2025
4b177a5
WIP
badjer Sep 19, 2025
7fc0593
Simpler server/test-client
badjer Sep 19, 2025
92cc99f
dev:resource and dev:cli working
badjer Sep 19, 2025
477cdc2
Fixed x402 example from src/dev
badjer Sep 19, 2025
981ebe9
Manually fix test-client
badjer Sep 19, 2025
1009f33
Fix example
badjer Sep 19, 2025
0f5a846
Cleanup, lint
badjer Sep 19, 2025
37f34ff
Fix tests
badjer Sep 19, 2025
ddb2cd6
Re-export class to fix tests
badjer Sep 19, 2025
a0edf4a
atxpLocalAccount
badjer Sep 19, 2025
7d8abe4
Pass chainType to ensure-currency
badjer Sep 19, 2025
566d9dc
Lint
badjer Sep 19, 2025
69cde01
Merge main into x402 branch and regenerate package-lock.json
badjer Sep 20, 2025
0e2e28e
typecheck/lint/test
badjer Sep 20, 2025
290f9bb
Cleanup
badjer Sep 20, 2025
237acaf
Log transaction hash
badjer Sep 20, 2025
f4a58d2
Cleanup; only allow ATXPAccount for X402wrapper
badjer Sep 20, 2025
2cb9122
Update tests
badjer Sep 20, 2025
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
10,029 changes: 10,029 additions & 0 deletions examples/x402/package-lock.json

Large diffs are not rendered by default.

33 changes: 33 additions & 0 deletions examples/x402/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"name": "@atxp/x402-example",
"version": "1.0.0",
"type": "module",
"private": true,
"scripts": {
"dev": "tsx watch src/server.ts",
"start": "tsx src/server.ts",
"test-client": "tsx src/test-client.ts"
},
"dependencies": {
"@atxp/client": "file:../../packages/atxp-client",
"@atxp/common": "file:../../packages/atxp-common",
"@atxp/x402": "file:../../packages/atxp-x402",
"@coinbase/x402": "^0.3.8",
"@modelcontextprotocol/sdk": "^1.18.1",
"bignumber.js": "^9.1.2",
"cors": "^2.8.5",
"dotenv": "^16.0.3",
"express": "^4.18.2",
"x402": "^0.6.1",
"x402-express": "^0.6.1",
"x402-fetch": "^0.6.0",
"zod": "^3.25.76"
},
"devDependencies": {
"@types/cors": "^2.8.13",
"@types/express": "^4.17.17",
"@types/node": "^20.0.0",
"tsx": "^4.0.0",
"typescript": "^5.0.0"
}
}
131 changes: 131 additions & 0 deletions examples/x402/src/server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/* eslint-disable no-console */
import express, { Request, Response } from 'express';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import { z } from 'zod';
import { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
import { paymentMiddleware } from 'x402-express';
import { facilitator } from '@coinbase/x402';
import dotenv from "dotenv";
import path from "path";
import { fileURLToPath } from 'url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
dotenv.config();
dotenv.config({ path: path.resolve(__dirname, '../../../.env') });

const PORT = 3001;

const getServer = () => {
// Create an MCP server with implementation details
const server = new McpServer({
name: 'stateless-streamable-http-server',
version: '1.0.0',
}, { capabilities: { logging: {} } });

// Register a tool specifically for testing resumability
server.tool(
'secure-data',
'Secure data',
{
message: z.string().optional().describe('Message to secure'),
},
async ({ message }: { message?: string }): Promise<CallToolResult> => {
// X402 payment will be enforced by middleware
return {
content: [
{
type: 'text',
text: `Secure data: ${message || 'No message provided'}`,
}
],
};
}
);

return server;
}

const app = express();
app.use(express.json());

const destinationAddress = process.env.ATXP_DESTINATION!;
const network = 'base';
console.log('Starting MCP server with destination', destinationAddress);

// Add X402 payment middleware
app.use(paymentMiddleware(
destinationAddress,
{ "POST /*": { price: "$0.01", network } },
process.env.CDP_API_KEY_ID ? facilitator : { url: "https://x402.org/facilitator" }
));


app.post('/', async (req: Request, res: Response) => {
const server = getServer();
try {
const transport: StreamableHTTPServerTransport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined,
enableJsonResponse: true
});
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
res.on('close', () => {
console.log('Request closed');
transport.close();
server.close();
});
} catch (error) {
console.error('Error handling MCP request:', error);
if (!res.headersSent) {
res.status(500).json({
jsonrpc: '2.0',
error: {
code: -32603,
message: 'Internal server error',
},
id: null,
});
}
}
});

app.get('/', async (req: Request, res: Response) => {
console.log('Received GET MCP request');
res.writeHead(405).end(JSON.stringify({
jsonrpc: "2.0",
error: {
code: -32000,
message: "Method not allowed."
},
id: null
}));
});

app.delete('/', async (req: Request, res: Response) => {
console.log('Received DELETE MCP request');
res.writeHead(405).end(JSON.stringify({
jsonrpc: "2.0",
error: {
code: -32000,
message: "Method not allowed."
},
id: null
}));
});


// Start the server
app.listen(PORT, (error) => {
if (error) {
console.error('Failed to start server:', error);
process.exit(1);
}
console.log(`MCP Stateless Streamable HTTP Server listening on port ${PORT}`);
});

// Handle server shutdown
process.on('SIGINT', async () => {
console.log('Shutting down server...');
process.exit(0);
});
40 changes: 40 additions & 0 deletions examples/x402/src/test-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#!/usr/bin/env node
import { atxpClient, ATXPAccount } from "@atxp/client";
import { wrapWithX402 } from "@atxp/x402";
import { ConsoleLogger, LogLevel } from '@atxp/common';
import dotenv from "dotenv";
import path from "path";
import { fileURLToPath } from 'url';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
dotenv.config();
dotenv.config({ path: path.resolve(__dirname, '../../../.env') });

async function main() {
const serverUrl = process.env.X402_SERVER_URL || "http://localhost:3001";

console.log("X402 Test Client (without payments)");
console.log(`Server: ${serverUrl}`);

const account = new ATXPAccount(process.env.ATXP_CONNECTION_STRING!);
const config = {
account,
logger: new ConsoleLogger({level: LogLevel.DEBUG}),
mcpServer: serverUrl,
allowHttp: true
};
const mcpClient = await atxpClient({
...config,
fetchFn: wrapWithX402(config)

});
const res = await mcpClient.callTool({
name: "secure-data",
arguments: { message: "blockchain metrics" }
});

console.log('Result:', res);

}

main().catch(console.error);
11 changes: 11 additions & 0 deletions examples/x402/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist",
"declaration": false,
"declarationMap": false
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
Loading