Overview
The @1stdigital/prism-fastify package provides a Fastify plugin for payment-protecting your API routes using the x402 protocol. It leverages Fastify’s hook system for high-performance payment validation.High Performance
Fastify’s async hooks for minimal overhead
Plugin Architecture
Standard Fastify plugin pattern
Type Safe
Full TypeScript support with decorators
Installation
- npm
- yarn
- pnpm
Copy
npm install @1stdigital/prism-fastify fastify
bash yarn add @1stdigital/prism-fastify fastify Copy
pnpm add @1stdigital/prism-fastify fastify
Quick Start
Copy
import Fastify from "fastify";
import prismPlugin from "@1stdigital/prism-fastify";
const app = Fastify({ logger: true });
// Register Prism plugin
await app.register(prismPlugin, {
apiKey: process.env.PRISM_API_KEY || "dev-key-123",
baseUrl: "https://prism-api.test.1stdigital.tech",
routes: {
"/api/premium": {
price: 0.01, // $0.01 USD
description: "Premium API access",
},
},
});
// Protected route - payment verified automatically
app.get("/api/premium", async (request, reply) => {
return {
message: "Premium content!",
payer: request.prismPayer, // Wallet address
};
});
await app.listen({ port: 3000 });
Configuration
Plugin Options
Copy
interface PrismFastifyOptions {
apiKey: string; // Your Prism API key
baseUrl?: string; // Gateway URL (optional)
debug?: boolean; // Enable debug logging
routes: Record<string, RoutePaymentConfig>;
}
interface RoutePaymentConfig {
price: number | string; // Price in USD
description: string; // Human-readable description
mimeType?: string; // Response MIME type
}
Route Protection
Plugin Registration
Copy
await app.register(prismPlugin, {
apiKey: "your-api-key",
routes: {
"/api/premium": {
price: 0.01,
description: "Premium API",
},
"/api/weather": {
price: "$0.001",
description: "Weather data",
},
"/api/data/*": {
price: 0.005,
description: "Data API (wildcard)",
},
},
});
Accessing Payment Info
Payment info is added to the request object:Copy
app.get("/api/premium", async (request, reply) => {
// Access payer wallet address
const payer = request.prismPayer;
// '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb'
// Access full payment object
const payment = request.prismPayment;
/*
{
scheme: 'eip3009',
network: 'eth-sepolia',
asset: 'usdc',
amount: '10000',
signature: '0x...'
}
*/
return {
message: "Premium content",
payer,
network: payment?.network,
};
});
Settlement Validation
Fastify uses the onSend hook to validate settlement before sending data:Copy
// Internal implementation (automatic!)
fastify.addHook('onSend', async (request, reply, payload) => {
const settlementResult = await core.settlementCallback(...);
if (!settlementResult || !settlementResult.success) {
// ❌ Settlement failed - replace payload
reply.code(402);
reply.header('Content-Type', 'application/json');
return JSON.stringify({
error: 'Payment settlement failed',
details: settlementResult?.errorReason
});
}
// ✅ Settlement succeeded - add header and return payload
reply.header('X-PAYMENT-RESPONSE', settlementResult.transaction);
return payload;
});
TypeScript Support
Extend Fastify types for payment properties:Copy
import "fastify";
declare module "fastify" {
interface FastifyRequest {
prismPayer?: string;
prismPayment?: {
scheme: string;
network: string;
asset: string;
amount: string;
signature: string;
};
}
}
// Now TypeScript knows about payment properties
app.get("/api/data", async (request, reply) => {
const payer: string | undefined = request.prismPayer; // Type-safe!
return { data: [1, 2, 3], payer };
});
Testing
Copy
import { test } from "tap";
import Fastify from "fastify";
import prismPlugin from "@1stdigital/prism-fastify";
test("payment required without header", async (t) => {
const app = Fastify();
await app.register(prismPlugin, {
apiKey: "test-key",
routes: {
"/api/premium": { price: 0.01, description: "Test" },
},
});
app.get("/api/premium", async () => ({ message: "Premium" }));
const response = await app.inject({
method: "GET",
url: "/api/premium",
});
t.equal(response.statusCode, 402);
t.ok(response.json().paymentRequired);
});
test("access granted with valid payment", async (t) => {
const app = Fastify();
await app.register(prismPlugin, {
apiKey: "test-key",
routes: {
"/api/premium": { price: 0.01, description: "Test" },
},
});
app.get("/api/premium", async (request) => ({
message: "Premium",
payer: request.prismPayer,
}));
const payment = JSON.stringify({
scheme: "eip3009",
signature: "0x" + "0".repeat(130),
});
const response = await app.inject({
method: "GET",
url: "/api/premium",
headers: { "X-PAYMENT": payment },
});
t.equal(response.statusCode, 200);
t.equal(response.json().message, "Premium");
});
Production Deployment
Copy
import Fastify from "fastify";
import prismPlugin from "@1stdigital/prism-fastify";
const app = Fastify({
logger: {
level: process.env.LOG_LEVEL || "info",
},
});
await app.register(prismPlugin, {
apiKey: process.env.PRISM_API_KEY!,
baseUrl: process.env.PRISM_BASE_URL,
debug: process.env.NODE_ENV !== "production",
routes: {
"/api/premium": {
price: 0.01,
description: "Premium API",
},
},
});
// Start server
await app.listen({
port: parseInt(process.env.PORT || "3000"),
host: "0.0.0.0",
});