t402 Payment Protocol Extensions
npm install @t402/extensionst402 Payment Protocol Extensions. This package provides optional extensions that enhance the t402 payment protocol with additional functionality like resource discovery and cataloging.
``bash`
pnpm install @t402/extensions
Extensions are optional features that can be added to t402 payment flows. They allow servers to provide additional metadata and enable facilitators to offer enhanced services like resource discovery and cataloging.
Currently, this package includes:
- Bazaar Discovery Extension: Enables automatic cataloging and indexing of t402-enabled resources
The Bazaar Discovery Extension enables facilitators to automatically catalog and index t402-enabled resources by following server-declared discovery instructions. This allows users to discover paid APIs and services through facilitator catalogs.
1. Servers declare discovery metadata when configuring their payment endpoints
2. The HTTP method is automatically inferred from the route definition (e.g., "GET /weather")
3. Facilitators extract this metadata from payment requests
4. Users can browse and discover available paid resources through facilitator catalogs
Declare endpoint discovery metadata in your payment middleware configuration. This helps facilitators understand how to call your endpoints and what they return.
> Note: The HTTP method is automatically inferred from the route key (e.g., "GET /weather" → GET method). You don't need to specify it in declareDiscoveryExtension.
#### Basic Example: GET Endpoint with Query Parameters
`typescript
import { declareDiscoveryExtension } from "@t402/extensions/bazaar";
const resources = {
"GET /weather": {
accepts: {
scheme: "exact",
price: "$0.001",
network: "eip155:84532",
payTo: "0xYourAddress"
},
extensions: {
...declareDiscoveryExtension({
input: { city: "San Francisco" },
inputSchema: {
properties: {
city: { type: "string" },
units: { type: "string", enum: ["celsius", "fahrenheit"] }
},
required: ["city"]
},
output: {
example: {
city: "San Francisco",
weather: "foggy",
temperature: 15,
humidity: 85
}
},
}),
},
},
};
`
#### Example: POST Endpoint with JSON Body
For POST, PUT, and PATCH endpoints, specify bodyType to indicate the request body format:
`typescript
import { declareDiscoveryExtension } from "@t402/extensions/bazaar";
const resources = {
"POST /api/translate": {
accepts: {
scheme: "exact",
price: "$0.01",
network: "eip155:84532",
payTo: "0xYourAddress"
},
extensions: {
...declareDiscoveryExtension({
input: {
text: "Hello, world!",
targetLanguage: "es"
},
inputSchema: {
properties: {
text: { type: "string" },
targetLanguage: { type: "string", pattern: "^[a-z]{2}$" }
},
required: ["text", "targetLanguage"]
},
bodyType: "json",
output: {
example: {
translatedText: "¡Hola, mundo!",
sourceLanguage: "en",
targetLanguage: "es"
}
},
}),
},
},
};
`
#### Example: PUT Endpoint with Form Data
`typescript`
const resources = {
"PUT /api/user/profile": {
accepts: {
scheme: "exact",
price: "$0.05",
network: "eip155:84532",
payTo: "0xYourAddress"
},
extensions: {
...declareDiscoveryExtension({
input: {
name: "John Doe",
email: "john@example.com",
bio: "Software developer"
},
inputSchema: {
properties: {
name: { type: "string", minLength: 1 },
email: { type: "string", format: "email" },
bio: { type: "string", maxLength: 500 }
},
required: ["name", "email"]
},
bodyType: "form-data",
output: {
example: {
success: true,
userId: "123",
updatedAt: "2024-01-01T00:00:00Z"
}
},
}),
},
},
};
#### Example: DELETE Endpoint
`typescript`
const resources = {
"DELETE /api/data/:id": {
accepts: {
scheme: "exact",
price: "$0.001",
network: "eip155:84532",
payTo: "0xYourAddress"
},
extensions: {
...declareDiscoveryExtension({
input: { id: "123" },
inputSchema: {
properties: {
id: { type: "string" }
},
required: ["id"]
},
output: {
example: {
success: true,
deletedId: "123"
}
},
}),
},
},
};
#### Using with Next.js Middleware
`typescript
import { paymentProxy, t402ResourceServer } from "@t402/next";
import { HTTPFacilitatorClient } from "@t402/core/http";
import { ExactEvmScheme } from "@t402/evm/exact/server";
import { declareDiscoveryExtension } from "@t402/extensions/bazaar";
const facilitatorClient = new HTTPFacilitatorClient({ url: "https://facilitator.t402.io" });
const resourceServer = new t402ResourceServer(facilitatorClient)
.register("eip155:84532", new ExactEvmScheme());
export const proxy = paymentProxy(
{
"/api/weather": {
accepts: {
scheme: "exact",
price: "$0.001",
network: "eip155:84532",
payTo: "0xYourAddress",
},
extensions: {
...declareDiscoveryExtension({
input: { city: "San Francisco" },
inputSchema: {
properties: { city: { type: "string" } },
required: ["city"],
},
output: {
example: { city: "San Francisco", weather: "foggy" }
},
}),
},
},
},
resourceServer,
);
`
Extract discovery information from incoming payment requests to catalog resources in the Bazaar.
#### Basic Usage
`typescript
import { extractDiscoveryInfo } from "@t402/extensions/bazaar";
import type { PaymentPayload, PaymentRequirements } from "@t402/core/types";
async function handlePayment(
paymentPayload: PaymentPayload,
paymentRequirements: PaymentRequirements
) {
// Extract discovery info from the payment
const discovered = extractDiscoveryInfo(paymentPayload, paymentRequirements);
if (discovered) {
// discovered contains:
// {
// resourceUrl: "https://api.example.com/weather",
// method: "GET",
// t402Version: 2,
// discoveryInfo: {
// input: { type: "http", method: "GET", queryParams: { city: "..." } },
// output: { type: "json", example: { ... } }
// }
// }
// Catalog the resource in your Bazaar
await catalogResource({
url: discovered.resourceUrl,
method: discovered.method,
inputSchema: discovered.discoveryInfo.input,
outputExample: discovered.discoveryInfo.output?.example,
});
}
}
`
#### Validating Discovery Extensions
`typescript
import { validateDiscoveryExtension, extractDiscoveryInfo } from "@t402/extensions/bazaar";
function processPayment(paymentPayload: PaymentPayload, paymentRequirements: PaymentRequirements) {
const discovered = extractDiscoveryInfo(paymentPayload, paymentRequirements);
if (discovered && paymentPayload.extensions?.bazaar) {
// Validate the extension schema
const validation = validateDiscoveryExtension(paymentPayload.extensions.bazaar);
if (!validation.valid) {
console.warn("Invalid discovery extension:", validation.errors);
// Handle invalid extension (log, reject, etc.)
return;
}
// Extension is valid, proceed with cataloging
catalogResource(discovered);
}
}
`
#### Using with Server Extension Helper
The bazaarResourceServerExtension automatically enriches discovery extensions with HTTP method information from the request context:
`typescript
import { bazaarResourceServerExtension } from "@t402/extensions/bazaar";
import { t402ResourceServer } from "@t402/core/server";
// The extension helper automatically extracts discovery info
const resourceServer = new t402ResourceServer(facilitatorClient)
.register("eip155:84532", new ExactEvmScheme())
.useExtension(bazaarResourceServerExtension);
`
Creates a discovery extension object for resource servers.
Parameters:
- config.input (optional): Example input values (query params for GET/HEAD/DELETE, body for POST/PUT/PATCH)config.inputSchema
- (optional): JSON Schema for input validationconfig.bodyType
- (optional): For POST/PUT/PATCH, specify "json", "form-data", or "text" (default: "json")config.output
- (optional): Output specificationoutput.example
- : Example output dataoutput.schema
- : JSON Schema for output validation
> Note: The HTTP method is NOT passed to this function. It is automatically inferred from the route key (e.g., "GET /weather") or enriched by bazaarResourceServerExtension at runtime.
Returns: An object with a bazaar key containing the discovery extension.
Example:
`typescript`
const extension = declareDiscoveryExtension({
input: { query: "search term" },
inputSchema: {
properties: { query: { type: "string" } },
required: ["query"]
},
output: {
example: { results: [] }
}
});
// Returns: { bazaar: { info: {...}, schema: {...} } }
Extracts discovery information from a payment request (for facilitators).
Parameters:
- paymentPayload: The payment payload from the clientpaymentRequirements
- : The payment requirements from the servervalidate
- (optional): Whether to validate the extension (default: true)
Returns: DiscoveredResource object or null if not found.
`typescript`
interface DiscoveredResource {
resourceUrl: string;
method: string;
t402Version: number;
discoveryInfo: DiscoveryInfo;
}
Example:
`typescript`
const info = extractDiscoveryInfo(paymentPayload, paymentRequirements);
if (info) {
console.log(info.resourceUrl); // "https://api.example.com/endpoint"
console.log(info.method); // "GET"
console.log(info.discoveryInfo); // { input: {...}, output: {...} }
}
Validates a discovery extension's info against its schema.
Parameters:
- extension: A discovery extension object
Returns: { valid: boolean, errors?: string[] }
Example:
`typescript`
const result = validateDiscoveryExtension(extension);
if (!result.valid) {
console.error("Validation errors:", result.errors);
}
Validates and extracts discovery info in one step.
Parameters:
- extension: A discovery extension object
Returns: { valid: boolean, info?: DiscoveryInfo, errors?: string[] }
Example:
`typescript`
const { valid, info, errors } = validateAndExtract(extension);
if (valid && info) {
// Use info
}
A server extension that automatically enriches discovery extensions with HTTP method information from the request context.
Usage:
`typescript
import { bazaarResourceServerExtension } from "@t402/extensions/bazaar";
const resourceServer = new t402ResourceServer(facilitatorClient)
.useExtension(bazaarResourceServerExtension);
`
The extension identifier constant ("bazaar").
`typescript`
import { BAZAAR } from "@t402/extensions/bazaar";
// BAZAAR === "bazaar"
Problem: extractDiscoveryInfo returns null.
Solutions:
- Ensure the server has declared the extension using declareDiscoveryExtensionpaymentPayload.extensions.bazaar
- Check that existsoutputSchema
- Verify you're using t402 v2 (v1 uses a different format in )
Problem: validateDiscoveryExtension returns valid: false.
Solutions:
- Ensure inputSchema matches the structure of inputinputSchema.required
- Check that required fields are marked in
- Verify JSON Schema syntax is correct
Problem: Discovery info is incomplete.
Solutions:
- Ensure both input and inputSchema are providedbodyType
- For POST/PUT/PATCH, include in the configoutput.example
- Check that is provided if you want output documentation
Problem: The HTTP method is missing from discovery info.
Solutions:
- Use bazaarResourceServerExtension which automatically injects the method"METHOD /path"
- Ensure the route key follows the format (e.g., "GET /weather")
- t402 Core Package - Core t402 protocol implementation
- t402 Specification - Full protocol specification
This package supports both t402 v1 and v2:
- v2: Extensions are in PaymentPayload.extensions and PaymentRequired.extensionsPaymentRequirements.outputSchema
- v1: Discovery info is in (automatically converted)
The extractDiscoveryInfo` function automatically handles both versions.