aixyz 0.32.0 → 0.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/accepts.ts CHANGED
@@ -1,32 +1,47 @@
1
1
  import { z } from "zod";
2
2
  import { FacilitatorClient, HTTPFacilitatorClient } from "@x402/core/server";
3
3
 
4
- export type Accepts = AcceptsX402 | AcceptsFree;
5
-
6
- export type AcceptsX402 = {
7
- scheme: "exact";
8
- price: string;
4
+ const AcceptsX402Scheme = z.object({
5
+ scheme: z.literal("exact"),
6
+ price: z.string(),
9
7
  // TODO(kevin): update type to Network (`string:string`)
10
- network?: string;
11
- payTo?: string;
12
- };
13
-
14
- export type AcceptsFree = {
15
- scheme: "free";
16
- };
17
-
18
- export const AcceptsScheme = z.discriminatedUnion("scheme", [
19
- z.object({
20
- scheme: z.literal("exact"),
21
- price: z.string(),
22
- network: z.string().optional(),
23
- payTo: z.string().optional(),
24
- }),
25
- z.object({
26
- scheme: z.literal("free"),
27
- }),
8
+ network: z.string().optional(),
9
+ payTo: z.string().optional(),
10
+ });
11
+
12
+ /** Used for multiple accepts — explicit network is required to properly register schemes on the resource server. */
13
+ const AcceptsX402EntryScheme = z.object({
14
+ scheme: z.literal("exact"),
15
+ price: z.string(),
16
+ network: z.string(),
17
+ payTo: z.string().optional(),
18
+ });
19
+
20
+ const AcceptsFreeScheme = z.object({
21
+ scheme: z.literal("free"),
22
+ });
23
+
24
+ export type AcceptsX402 = z.infer<typeof AcceptsX402Scheme>;
25
+ export type AcceptsX402Entry = z.infer<typeof AcceptsX402EntryScheme>;
26
+ export type AcceptsX402Multi = AcceptsX402Entry[];
27
+ export type AcceptsFree = z.infer<typeof AcceptsFreeScheme>;
28
+ export type Accepts = AcceptsX402 | AcceptsFree | AcceptsX402Multi;
29
+
30
+ export const AcceptsScheme: z.ZodType<Accepts> = z.union([
31
+ z.discriminatedUnion("scheme", [AcceptsX402Scheme, AcceptsFreeScheme]),
32
+ z.array(AcceptsX402EntryScheme).min(1),
28
33
  ]);
29
34
 
35
+ export function normalizeAcceptsX402(accepts: AcceptsX402 | AcceptsX402Multi): AcceptsX402[] {
36
+ return Array.isArray(accepts) ? accepts : [accepts];
37
+ }
38
+
39
+ export function isAcceptsPaid(accepts: Accepts): accepts is AcceptsX402 | AcceptsX402Multi {
40
+ if (Array.isArray(accepts))
41
+ return accepts.length > 0 && accepts.every((e) => e.scheme === "exact" && typeof e.network === "string");
42
+ return accepts.scheme === "exact";
43
+ }
44
+
30
45
  export type { FacilitatorClient };
31
46
 
32
47
  export { HTTPFacilitatorClient };
package/app/index.ts CHANGED
@@ -1,8 +1,8 @@
1
- import type { AcceptsX402 } from "../accepts";
1
+ import { isAcceptsPaid, AcceptsScheme } from "../accepts";
2
+ import type { Accepts } from "../accepts";
2
3
  import type { FacilitatorClient } from "@x402/core/server";
3
- import { type HttpMethod, type RouteHandler, type Middleware, type RouteEntry } from "./types";
4
+ import { type HttpMethod, type RouteHandler, type Middleware, type RouteEntry, type RouteOptions } from "./types";
4
5
  import { PaymentGateway } from "./payment/payment";
5
- import { Network } from "@x402/core/types";
6
6
  import { getAixyzConfig } from "@aixyz/config";
7
7
  import { loadEnvConfig } from "@next/env";
8
8
  import { BasePlugin, type RegisterContext, type InitializeContext } from "./plugin";
@@ -37,7 +37,6 @@ export class AixyzApp {
37
37
 
38
38
  if (options?.facilitators) {
39
39
  this.payment = new PaymentGateway(options.facilitators, config);
40
- this.payment.register((config.x402.network as Network) ?? "eip155:8453");
41
40
  }
42
41
  }
43
42
 
@@ -87,14 +86,21 @@ export class AixyzApp {
87
86
  return `${method} ${path}`;
88
87
  }
89
88
 
90
- /** Register a route with an optional x402 payment requirement. */
91
- route(method: HttpMethod, path: string, handler: RouteHandler, options?: { payment?: AcceptsX402 }): void {
89
+ /** Register a route with an optional x402 payment requirement. Free accepts are filtered out. */
90
+ route(method: HttpMethod, path: string, handler: RouteHandler, options?: RouteOptions): void {
92
91
  const key = this.getRouteKey(method, path);
92
+ if (options?.payment) {
93
+ const result = AcceptsScheme.safeParse(options.payment);
94
+ if (!result.success) {
95
+ throw new Error(`Invalid accepts config for route "${method} ${path}": ${result.error.message}`);
96
+ }
97
+ }
98
+ const payment = options?.payment && isAcceptsPaid(options.payment) ? options.payment : undefined;
93
99
  this.routes.set(key, {
94
100
  method,
95
101
  path,
96
102
  handler,
97
- payment: options?.payment,
103
+ payment,
98
104
  });
99
105
  }
100
106
 
@@ -8,7 +8,8 @@ import {
8
8
  type HTTPResponseInstructions,
9
9
  } from "@x402/core/http";
10
10
  import { ExactEvmScheme } from "@x402/evm/exact/server";
11
- import type { AcceptsX402 } from "../../accepts";
11
+ import type { AcceptsX402, AcceptsX402Multi } from "../../accepts";
12
+ import { normalizeAcceptsX402 } from "../../accepts";
12
13
  import { Network, PaymentPayload, PaymentRequirements } from "@x402/core/types";
13
14
  import { AixyzConfig } from "@aixyz/config";
14
15
 
@@ -78,29 +79,49 @@ export class PaymentGateway {
78
79
  /**
79
80
  * Add a payment-gated route. Must be called before initialize().
80
81
  */
81
- addRoute(method: string, path: string, accepts: AcceptsX402): void {
82
+ addRoute(method: string, path: string, accepts: AcceptsX402 | AcceptsX402Multi): void {
82
83
  const pattern = this.getRouteKey(method, path);
84
+ const items = normalizeAcceptsX402(accepts);
83
85
  this.pendingRoutes.set(pattern, {
84
- accepts: {
85
- scheme: accepts.scheme,
86
- payTo: accepts.payTo ?? this.config.x402.payTo,
87
- price: accepts.price,
88
- network: (accepts.network as Network) ?? (this.config.x402.network as Network),
89
- },
86
+ accepts: items.map((a) => ({
87
+ scheme: a.scheme,
88
+ payTo: a.payTo ?? this.config.x402.payTo,
89
+ price: a.price,
90
+ network: (a.network as Network) ?? (this.config.x402.network as Network),
91
+ })),
90
92
  });
91
93
  }
92
94
 
93
95
  /**
94
- * Initialize the payment gateway. Builds the x402HTTPResourceServer from registered routes.
96
+ * Initialize the payment gateway. Registers all required network schemes
97
+ * from pending routes, then builds the x402HTTPResourceServer.
95
98
  * Must be called after all routes are added.
96
99
  */
97
100
  async initialize(): Promise<void> {
101
+ this.registerNetworksFromRoutes();
98
102
  const routes: RoutesConfig =
99
103
  this.pendingRoutes.size > 0 ? Object.fromEntries(this.pendingRoutes) : { "* /*": { accepts: [] } };
100
104
  this.httpServer = new x402HTTPResourceServer(this.resourceServer, routes);
101
105
  await this.httpServer.initialize();
102
106
  }
103
107
 
108
+ private registerNetworksFromRoutes(): void {
109
+ const networks = new Set<Network>();
110
+ const defaultNetwork = (this.config.x402.network as Network) ?? ("eip155:8453" as Network);
111
+ networks.add(defaultNetwork);
112
+
113
+ for (const [, route] of this.pendingRoutes) {
114
+ const accepts = Array.isArray(route.accepts) ? route.accepts : [route.accepts];
115
+ for (const opt of accepts) {
116
+ if (opt.network) networks.add(opt.network as Network);
117
+ }
118
+ }
119
+
120
+ for (const network of networks) {
121
+ this.register(network);
122
+ }
123
+ }
124
+
104
125
  /**
105
126
  * Verify payment for a request. Returns a 402 Response if payment is required/invalid,
106
127
  * or null if the request is authorized to proceed.
package/app/plugin.ts CHANGED
@@ -1,5 +1,4 @@
1
- import type { HttpMethod, RouteHandler, RouteEntry, Middleware } from "./types";
2
- import type { AcceptsX402 } from "../accepts";
1
+ import type { HttpMethod, RouteHandler, RouteEntry, RouteOptions, Middleware } from "./types";
3
2
  import type { PaymentGateway } from "./payment/payment";
4
3
 
5
4
  /**
@@ -14,7 +13,7 @@ import type { PaymentGateway } from "./payment/payment";
14
13
  */
15
14
  export interface RegisterContext {
16
15
  /** Register a route on the application. Automatically tracked in `plugin.registeredRoutes`. */
17
- route(method: HttpMethod, path: string, handler: RouteHandler, options?: { payment?: AcceptsX402 }): void;
16
+ route(method: HttpMethod, path: string, handler: RouteHandler, options?: RouteOptions): void;
18
17
  /** Append a middleware to the application's middleware chain. */
19
18
  use(middleware: Middleware): void;
20
19
  }
@@ -250,7 +250,7 @@ export class A2APlugin extends BasePlugin {
250
250
  return Response.json(result);
251
251
  },
252
252
  {
253
- payment: entry.exports.accepts.scheme === "exact" ? entry.exports.accepts : undefined,
253
+ payment: entry.exports.accepts,
254
254
  },
255
255
  );
256
256
  }
@@ -3,6 +3,7 @@ import { z } from "zod";
3
3
  import { BasePlugin, type RegisterContext, type InitializeContext } from "../../plugin";
4
4
  import type { MCPPlugin } from "../mcp";
5
5
  import { renderHtml } from "./html";
6
+ import { isAcceptsPaid } from "../../../accepts";
6
7
 
7
8
  export type AixyzConfigRuntime = ReturnType<typeof getAixyzConfigRuntime>;
8
9
 
@@ -141,7 +142,7 @@ export class IndexPagePlugin extends BasePlugin {
141
142
  protocol: "a2a",
142
143
  name,
143
144
  path: entry.path,
144
- paid: entry.payment?.scheme === "exact",
145
+ paid: !!entry.payment, // only set for paid routes (free accepts filtered in AixyzApp.route())
145
146
  });
146
147
  }
147
148
  }
@@ -170,7 +171,7 @@ export class IndexPagePlugin extends BasePlugin {
170
171
  name: tool.name,
171
172
  path: "/mcp",
172
173
  description: tool.tool.description,
173
- paid: tool.accepts?.scheme === "exact",
174
+ paid: tool.accepts ? isAcceptsPaid(tool.accepts) : false,
174
175
  inputSchema,
175
176
  });
176
177
  }
@@ -4,9 +4,10 @@ import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/
4
4
  import { createPaymentWrapper } from "@x402/mcp";
5
5
  import { BasePlugin, type RegisterContext, type InitializeContext } from "../plugin";
6
6
  import type { Accepts } from "../../accepts";
7
- import { AcceptsScheme } from "../../accepts";
7
+ import { AcceptsScheme, isAcceptsPaid, normalizeAcceptsX402 } from "../../accepts";
8
8
  import { getAixyzConfig, getAixyzConfigRuntime } from "../../config";
9
9
  import { Network } from "@x402/core/types";
10
+ import { ExactEvmScheme } from "@x402/evm/exact/server";
10
11
 
11
12
  /**
12
13
  * MCP (Model Context Protocol) plugin. Collects tools and exposes them
@@ -83,21 +84,43 @@ export class MCPPlugin extends BasePlugin {
83
84
 
84
85
  const config = getAixyzConfig();
85
86
  const resourceServer = ctx.payment.resourceServer;
87
+ const defaultNetwork = (config.x402.network as Network) ?? ("eip155:8453" as Network);
88
+
89
+ // MCP payment operates via @x402/mcp wrappers independently of the HTTP payment
90
+ // middleware (PaymentGateway), so network schemes must be registered here separately.
91
+ // The default network is already registered by PaymentGateway.initialize() on the
92
+ // shared resourceServer — seed the set so we skip re-registering it.
93
+ const registeredNetworks = new Set<string>([defaultNetwork]);
94
+ for (const { accepts } of this.registeredTools) {
95
+ if (!accepts || !isAcceptsPaid(accepts)) continue;
96
+ for (const a of normalizeAcceptsX402(accepts)) {
97
+ const network = a.network ?? defaultNetwork;
98
+ if (!registeredNetworks.has(network)) {
99
+ registeredNetworks.add(network);
100
+ resourceServer.register(network as Network, new ExactEvmScheme());
101
+ }
102
+ }
103
+ }
86
104
 
87
105
  for (const { name, accepts } of this.registeredTools) {
88
- if (accepts?.scheme !== "exact") continue;
89
-
90
- const reqs = await resourceServer.buildPaymentRequirements({
91
- scheme: accepts.scheme,
92
- payTo: accepts.payTo ?? config.x402.payTo,
93
- price: accepts.price,
94
- network: (accepts.network as Network) ?? (config.x402.network as Network),
95
- });
106
+ if (!accepts || !isAcceptsPaid(accepts)) continue;
107
+
108
+ const items = normalizeAcceptsX402(accepts);
109
+ const allReqs: Awaited<ReturnType<typeof resourceServer.buildPaymentRequirements>> = [];
110
+ for (const a of items) {
111
+ const reqs = await resourceServer.buildPaymentRequirements({
112
+ scheme: a.scheme,
113
+ payTo: a.payTo ?? config.x402.payTo,
114
+ price: a.price,
115
+ network: (a.network as Network) ?? defaultNetwork,
116
+ });
117
+ allReqs.push(...reqs);
118
+ }
96
119
 
97
120
  this.paymentWrappers.set(
98
121
  name,
99
122
  createPaymentWrapper(resourceServer, {
100
- accepts: reqs,
123
+ accepts: allReqs,
101
124
  resource: { url: `mcp://tool/${name}` },
102
125
  }),
103
126
  );
package/app/types.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { AcceptsX402 } from "../accepts";
1
+ import type { Accepts, AcceptsX402, AcceptsX402Multi } from "../accepts";
2
2
 
3
3
  export type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH" | "HEAD" | "OPTIONS";
4
4
 
@@ -6,9 +6,13 @@ export type RouteHandler = (request: Request) => Response | Promise<Response>;
6
6
 
7
7
  export type Middleware = (request: Request, next: () => Promise<Response>) => Response | Promise<Response>;
8
8
 
9
+ export interface RouteOptions {
10
+ payment?: Accepts;
11
+ }
12
+
9
13
  export interface RouteEntry {
10
14
  method: HttpMethod;
11
15
  path: string;
12
16
  handler: RouteHandler;
13
- payment?: AcceptsX402;
17
+ payment?: AcceptsX402 | AcceptsX402Multi;
14
18
  }
@@ -6,18 +6,18 @@ description: "Payment configuration types and facilitator client for x402 gating
6
6
  Types and utilities for configuring x402 payment gating on agent and tool endpoints.
7
7
 
8
8
  ```typescript
9
- import type { Accepts, AcceptsX402, AcceptsFree } from "aixyz/accepts";
10
- import { HTTPFacilitatorClient, facilitator } from "aixyz/accepts";
9
+ import type { Accepts, AcceptsX402, AcceptsX402Entry, AcceptsX402Multi, AcceptsFree } from "aixyz/accepts";
10
+ import { HTTPFacilitatorClient, facilitator, normalizeAcceptsX402, isAcceptsPaid } from "aixyz/accepts";
11
11
  ```
12
12
 
13
13
  ## Types
14
14
 
15
15
  ### `Accepts`
16
16
 
17
- Union type for payment configuration:
17
+ Union type for payment configuration. Supports a single payment option, free access, or an array of payment options for multi-network support:
18
18
 
19
19
  ```typescript
20
- type Accepts = AcceptsX402 | AcceptsFree;
20
+ type Accepts = AcceptsX402 | AcceptsFree | AcceptsX402Multi;
21
21
  ```
22
22
 
23
23
  ### `AcceptsX402`
@@ -40,6 +40,34 @@ type AcceptsX402 = {
40
40
  | `network` | `string` | No | CAIP-2 chain ID, overrides `x402.network` from config |
41
41
  | `payTo` | `string` | No | EVM address to receive payment, overrides `x402.payTo` config |
42
42
 
43
+ ### `AcceptsX402Entry`
44
+
45
+ A single payment option within a multi-accepts array. Same as `AcceptsX402` but `network` is **required** — the server needs an explicit network to register each payment scheme:
46
+
47
+ ```typescript
48
+ type AcceptsX402Entry = {
49
+ scheme: "exact";
50
+ price: string;
51
+ network: string; // required
52
+ payTo?: string;
53
+ };
54
+ ```
55
+
56
+ | Field | Type | Required | Description |
57
+ | --------- | -------- | -------- | ------------------------------------------------------------- |
58
+ | `scheme` | `string` | Yes | Must be `"exact"` |
59
+ | `price` | `string` | Yes | USD price string (e.g. `"$0.005"`) |
60
+ | `network` | `string` | Yes | CAIP-2 chain ID — required for multi-accepts |
61
+ | `payTo` | `string` | No | EVM address to receive payment, overrides `x402.payTo` config |
62
+
63
+ ### `AcceptsX402Multi`
64
+
65
+ An array of payment entries, enabling multi-network support. Must contain at least one entry:
66
+
67
+ ```typescript
68
+ type AcceptsX402Multi = AcceptsX402Entry[];
69
+ ```
70
+
43
71
  ### `AcceptsFree`
44
72
 
45
73
  No payment required:
@@ -52,6 +80,22 @@ type AcceptsFree = {
52
80
 
53
81
  ## Exports
54
82
 
83
+ ### `normalizeAcceptsX402`
84
+
85
+ Converts a single or multi accepts value into a uniform array:
86
+
87
+ ```typescript
88
+ function normalizeAcceptsX402(accepts: AcceptsX402 | AcceptsX402Multi): AcceptsX402[];
89
+ ```
90
+
91
+ ### `isAcceptsPaid`
92
+
93
+ Type guard that returns `true` if the accepts config requires payment (i.e., is not `{ scheme: "free" }`):
94
+
95
+ ```typescript
96
+ function isAcceptsPaid(accepts: Accepts): accepts is AcceptsX402 | AcceptsX402Multi;
97
+ ```
98
+
55
99
  ### `HTTPFacilitatorClient`
56
100
 
57
101
  Client for communicating with an x402 facilitator service. Re-exported from `@x402/core/server`.
@@ -74,12 +118,19 @@ import { facilitator } from "aixyz/accepts";
74
118
 
75
119
  ### `AcceptsScheme`
76
120
 
77
- Zod schema for validating `Accepts` objects at runtime:
121
+ Zod schema for validating `Accepts` objects at runtime. Accepts a single object or an array of payment entries:
78
122
 
79
123
  ```typescript
80
124
  import { AcceptsScheme } from "aixyz/accepts";
81
125
 
126
+ // Single accepts
82
127
  AcceptsScheme.parse({ scheme: "exact", price: "$0.005" });
128
+
129
+ // Multiple accepts
130
+ AcceptsScheme.parse([
131
+ { scheme: "exact", price: "$0.005", network: "eip155:8453" },
132
+ { scheme: "exact", price: "$0.005", network: "eip155:84532" },
133
+ ]);
83
134
  ```
84
135
 
85
136
  ## Usage
@@ -103,6 +154,21 @@ export const accepts: Accepts = {
103
154
  };
104
155
  ```
105
156
 
157
+ ### Multiple payment options
158
+
159
+ Accept payment across multiple networks by passing an array. Each entry requires an explicit `network`:
160
+
161
+ ```typescript title="app/agent.ts"
162
+ import type { Accepts } from "aixyz/accepts";
163
+
164
+ export const accepts: Accepts = [
165
+ { scheme: "exact", price: "$0.005", network: "eip155:8453" },
166
+ { scheme: "exact", price: "$0.005", network: "eip155:84532" },
167
+ ];
168
+ ```
169
+
170
+ All referenced networks are automatically registered with the payment gateway during initialization — no manual setup required.
171
+
106
172
  ### Custom facilitator
107
173
 
108
174
  Create `app/accepts.ts` to override the default facilitator:
@@ -38,7 +38,7 @@ export default new ToolLoopAgent({
38
38
  | `accepts` | `Accepts` | No | Payment config — gates the A2A `/agent` route |
39
39
  | `capabilities` | `Capabilities` | No | A2A capabilities — controls streaming and push notification support |
40
40
 
41
- When `accepts` is exported, the `/agent` endpoint requires x402 payment. Without it, the agent is not registered on the A2A endpoint.
41
+ When `accepts` is exported, the `/agent` endpoint requires x402 payment. Without it, the agent is not registered on the A2A endpoint. `accepts` can also be an array of payment entries for [multi-network support](/getting-started/payments#multiple-payment-options).
42
42
 
43
43
  ## Capabilities
44
44
 
@@ -28,10 +28,10 @@ export default tool({
28
28
 
29
29
  ## Exports
30
30
 
31
- | Export | Type | Required | Description |
32
- | --------- | --------- | -------- | --------------------------------------------- |
33
- | `default` | `tool()` | Yes | The tool instance |
34
- | `accepts` | `Accepts` | No | Payment config — gates the tool on MCP `/mcp` |
31
+ | Export | Type | Required | Description |
32
+ | --------- | --------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
33
+ | `default` | `tool()` | Yes | The tool instance |
34
+ | `accepts` | `Accepts` | No | Payment config — gates the tool on MCP `/mcp`. Supports [array format](/getting-started/payments#multiple-payment-options) for multi-network |
35
35
 
36
36
  ## Conventions
37
37
 
@@ -43,6 +43,15 @@ The optional `accepts` named export controls [x402 payment](/getting-started/pay
43
43
  - Agents **with** `accepts` are registered on payment-gated A2A endpoints
44
44
  - Agents **without** `accepts` are not registered
45
45
 
46
+ You can also pass an array to accept payment on multiple networks:
47
+
48
+ ```typescript
49
+ export const accepts: Accepts = [
50
+ { scheme: "exact", price: "$0.005", network: "eip155:8453" },
51
+ { scheme: "exact", price: "$0.005", network: "eip155:84532" },
52
+ ];
53
+ ```
54
+
46
55
  ### `capabilities` Export
47
56
 
48
57
  The optional `capabilities` named export configures the A2A agent card's capabilities and controls how the executor runs your agent:
@@ -160,6 +169,8 @@ export const accepts: Accepts = { scheme: "exact", price: "$0.001" };
160
169
  export default tool({ ... });
161
170
  ```
162
171
 
172
+ Tools also support [multiple payment options](/getting-started/payments#multiple-payment-options) via an array of accepts entries.
173
+
163
174
  ## Custom Server
164
175
 
165
176
  For full control over endpoint registration and middleware, create `app/server.ts`. This overrides auto-generation entirely:
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: "Deploying"
3
- description: "Deploy your agent to Vercel, standalone Bun, or Docker"
3
+ description: "Deploy your agent to Vercel, Railway, standalone Bun, or Docker"
4
4
  ---
5
5
 
6
6
  ## Vercel
@@ -33,7 +33,7 @@ Set `OPENAI_API_KEY` and any payment environment variables in the Vercel dashboa
33
33
 
34
34
  ## Standalone (Bun Runtime)
35
35
 
36
- For direct deployment on Bun-compatible hosts (DigitalOcean, Railway, Fly.io, etc.):
36
+ For direct deployment on Bun-compatible hosts (DigitalOcean, Fly.io, etc.):
37
37
 
38
38
  ```bash
39
39
  aixyz build
@@ -52,16 +52,58 @@ export OPENAI_API_KEY=sk-...
52
52
  bun .aixyz/output/server.js
53
53
  ```
54
54
 
55
+ ## Railway
56
+
57
+ [Railway](https://railway.com) auto-detects a `Dockerfile` in your project root. Use a multi-stage build so only the compiled output is shipped in the final image, and pass `RAILWAY_PUBLIC_DOMAIN` at build time so the agent URL is resolved automatically.
58
+
59
+ ```dockerfile title="Dockerfile"
60
+ FROM oven/bun:1 AS build
61
+ WORKDIR /app
62
+ COPY . .
63
+ RUN bun install
64
+ ARG RAILWAY_PUBLIC_DOMAIN
65
+ ENV RAILWAY_PUBLIC_DOMAIN=$RAILWAY_PUBLIC_DOMAIN
66
+ RUN bun run build
67
+
68
+ FROM oven/bun:1-slim
69
+ WORKDIR /app
70
+ COPY --from=build /app/.aixyz/output .aixyz/output
71
+ CMD ["bun", ".aixyz/output/server.js"]
72
+ ```
73
+
74
+ Deploy by connecting your GitHub repository to a Railway project. Railway injects `RAILWAY_PUBLIC_DOMAIN` automatically so `bun run build` picks up the correct public URL — no manual `url` config needed.
75
+
76
+ Set `OPENAI_API_KEY` and any payment environment variables in the Railway dashboard under **Variables**.
77
+
78
+ Add a `.dockerignore` to keep the build context small:
79
+
80
+ ```text title=".dockerignore"
81
+ node_modules
82
+ .aixyz
83
+ .vercel
84
+ .env*.local
85
+ *.log
86
+ ```
87
+
88
+ <Note>
89
+ `RAILWAY_PUBLIC_DOMAIN` is only available after a public domain is assigned in Railway. If you use a custom domain,
90
+ set it there first and Railway will expose it as the `RAILWAY_PUBLIC_DOMAIN` build argument.
91
+ </Note>
92
+
55
93
  ## Docker
56
94
 
57
- Using the standalone build:
95
+ For self-hosted or custom container deployments, use a multi-stage build to keep the final image small:
58
96
 
59
97
  ```dockerfile title="Dockerfile"
60
- FROM oven/bun:1.3.9
98
+ FROM oven/bun:1 AS build
61
99
  WORKDIR /app
62
100
  COPY . .
63
101
  RUN bun install
64
102
  RUN bun run build
103
+
104
+ FROM oven/bun:1-slim
105
+ WORKDIR /app
106
+ COPY --from=build /app/.aixyz/output .aixyz/output
65
107
  CMD ["bun", ".aixyz/output/server.js"]
66
108
  ```
67
109
 
@@ -75,6 +117,16 @@ docker run -p 3000:3000 \
75
117
  my-agent
76
118
  ```
77
119
 
120
+ Add a `.dockerignore` to keep the build context small:
121
+
122
+ ```text title=".dockerignore"
123
+ node_modules
124
+ .aixyz
125
+ .vercel
126
+ .env*.local
127
+ *.log
128
+ ```
129
+
78
130
  <Warning>Never bake API keys into Docker images. Pass them as environment variables at runtime.</Warning>
79
131
 
80
132
  <Note>
@@ -64,6 +64,37 @@ export const accepts: Accepts = {
64
64
  - Tool `accepts` gates the tool on the MCP `/mcp` endpoint
65
65
  - Agents and tools **without** `accepts` are not registered on protocol endpoints
66
66
 
67
+ ## Multiple Payment Options
68
+
69
+ Accept payment across multiple networks by passing an array of payment entries. This lets clients pay on whichever network they prefer:
70
+
71
+ ```typescript
72
+ export const accepts: Accepts = [
73
+ { scheme: "exact", price: "$0.005", network: "eip155:8453" }, // Base
74
+ { scheme: "exact", price: "$0.005", network: "eip155:84532" }, // Base Sepolia
75
+ ];
76
+ ```
77
+
78
+ When using the array format, `network` is **required** on each entry so the server can register the correct payment scheme for each network.
79
+
80
+ All referenced networks are automatically discovered and registered during server initialization — no manual configuration needed beyond the `accepts` export.
81
+
82
+ This works for both agents and tools:
83
+
84
+ ```typescript
85
+ // app/agent.ts — multi-network agent pricing
86
+ export const accepts: Accepts = [
87
+ { scheme: "exact", price: "$0.01", network: "eip155:8453" },
88
+ { scheme: "exact", price: "$0.01", network: "eip155:1" },
89
+ ];
90
+
91
+ // app/tools/premium-search.ts — multi-network tool pricing
92
+ export const accepts: Accepts = [
93
+ { scheme: "exact", price: "$0.001", network: "eip155:8453" },
94
+ { scheme: "exact", price: "$0.001", network: "eip155:1" },
95
+ ];
96
+ ```
97
+
67
98
  ## Payment Networks
68
99
 
69
100
  Configure the payment network in `aixyz.config.ts`:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aixyz",
3
- "version": "0.32.0",
3
+ "version": "0.34.0",
4
4
  "description": "Payment-native SDK for AI Agent",
5
5
  "keywords": [
6
6
  "ai",
@@ -40,9 +40,9 @@
40
40
  },
41
41
  "dependencies": {
42
42
  "@a2a-js/sdk": "^0.3.10",
43
- "@aixyz/cli": "0.32.0",
44
- "@aixyz/config": "0.32.0",
45
- "@aixyz/erc-8004": "0.32.0",
43
+ "@aixyz/cli": "0.34.0",
44
+ "@aixyz/config": "0.34.0",
45
+ "@aixyz/erc-8004": "0.34.0",
46
46
  "@kitajs/html": "^4.2.13",
47
47
  "@modelcontextprotocol/sdk": "^1.27.1",
48
48
  "@next/env": "^16.1.6",