-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathindex.ts
More file actions
122 lines (111 loc) · 4.3 KB
/
Copy pathindex.ts
File metadata and controls
122 lines (111 loc) · 4.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
#!/usr/bin/env node
/**
* x402 Facilitator Server CLI
*
* Run with: npx x402-facilitator
* Or after global install: x402-facilitator
*
* Environment variables:
* - PORT: Server port (default: 8090)
* - DATABASE_URL: PostgreSQL connection string (optional, enables Drizzle tracking)
* - TRACKING_ALLOW_IN_MEMORY_FALLBACK: set to "true" to continue startup when DB init fails
* - CDP_API_KEY_ID, CDP_API_KEY_SECRET, CDP_WALLET_SECRET: For CDP signer
* - EVM_PRIVATE_KEY, SVM_PRIVATE_KEY: For private key signer (fallback)
* - EVM_RPC_URL_BASE, EVM_RPC_URL_BASE_SEPOLIA: RPC URLs
* - STARKNET_NETWORKS: Comma-separated Starknet networks to enable (opt-in, default: disabled)
* - STARKNET_RPC_URL_STARKNET_MAINNET, STARKNET_RPC_URL_STARKNET_SEPOLIA: Starknet RPC URLs
* - STARKNET_PAYMASTER_ENDPOINT_STARKNET_MAINNET, STARKNET_PAYMASTER_ENDPOINT_STARKNET_SEPOLIA: Starknet paymaster endpoints
* - STARKNET_PAYMASTER_API_KEY, STARKNET_PAYMASTER_API_KEY_STARKNET_MAINNET, STARKNET_PAYMASTER_API_KEY_STARKNET_SEPOLIA: Paymaster API key(s)
* - STARKNET_SPONSOR_ADDRESS, STARKNET_SPONSOR_ADDRESS_STARKNET_MAINNET, STARKNET_SPONSOR_ADDRESS_STARKNET_SEPOLIA: Sponsor address(es), required for enabled Starknet networks
* - UPTO_VERIFY_BALANCE_CHECK: "true" to enforce on-chain balance preflight in /verify for upto
* - BEARER_TOKEN: Required bearer token for /verify and /settle
* - BEARER_TOKENS: Optional comma-separated bearer token list (overrides BEARER_TOKEN)
*/
import pg from "pg";
import { defaultSigners } from "./setup.js";
import { createFacilitator } from "@daydreamsai/facilitator";
import { createApp } from "./app.js";
import { createDrizzleAdapter, createTracking } from "./db.js";
import { runMigrations } from "./db-migrate.js";
import { createBearerTokenModule } from "./modules/bearer-token.js";
const PORT = parseInt(process.env.PORT || "8090", 10);
const DATABASE_URL = process.env.DATABASE_URL;
const TRACKING_ALLOW_IN_MEMORY_FALLBACK =
process.env.TRACKING_ALLOW_IN_MEMORY_FALLBACK === "true";
const BEARER_TOKEN = process.env.BEARER_TOKEN?.trim();
const BEARER_TOKENS = process.env.BEARER_TOKENS?.split(",")
.map((token) => token.trim())
.filter(Boolean);
const TOKENS = BEARER_TOKENS && BEARER_TOKENS.length > 0
? BEARER_TOKENS
: BEARER_TOKEN
? [BEARER_TOKEN]
: [];
if (TOKENS.length === 0) {
throw new Error(
"Set BEARER_TOKEN or BEARER_TOKENS to require bearer auth for facilitator startup."
);
}
// Database setup (optional)
let pool = DATABASE_URL
? new pg.Pool({ connectionString: DATABASE_URL })
: undefined;
let pgClient = pool ? createDrizzleAdapter(pool) : undefined;
// Run migrations if database is configured
if (pool) {
try {
await runMigrations(pool);
} catch (err) {
console.error("❌ Database migration failed");
console.error(err instanceof Error ? err.message : err);
if (!TRACKING_ALLOW_IN_MEMORY_FALLBACK) {
console.error(
"Set TRACKING_ALLOW_IN_MEMORY_FALLBACK=true to continue with in-memory tracking."
);
await pool.end().catch(() => {});
process.exit(1);
}
console.error("⚠️ Continuing with in-memory tracking.");
await pool.end().catch(() => {});
pool = undefined;
pgClient = undefined;
}
}
// Resource tracking (falls back to in-memory if no DATABASE_URL)
const tracking = createTracking(pgClient, {
onTrackingError: (err, id) => {
console.error(`[tracking:${id}]`, err);
},
});
// Facilitator + App
const facilitator = createFacilitator({ ...defaultSigners });
const app = createApp({
facilitator,
tracking,
modules: [
createBearerTokenModule({
tokens: TOKENS,
protectedPaths: ["/verify", "/settle"],
realm: "facilitator",
}),
],
});
app.listen(PORT);
console.log(`x402 Facilitator listening on http://localhost:${PORT}`);
if (pgClient) {
console.log(`Resource tracking: PostgreSQL (Drizzle)`);
} else if (DATABASE_URL) {
console.log(
`Resource tracking: In-memory (DB init failed; set TRACKING_ALLOW_IN_MEMORY_FALLBACK=true to allow this explicitly)`
);
} else {
console.log(`Resource tracking: In-memory (set DATABASE_URL for persistence)`);
}
// Graceful shutdown
const shutdown = async () => {
tracking.stopAutoPrune();
if (pool) await pool.end();
process.exit(0);
};
process.on("SIGINT", () => void shutdown());
process.on("SIGTERM", () => void shutdown());