create-mantle-facilitator 0.4.0 → 0.4.1

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/README.md CHANGED
@@ -62,16 +62,17 @@ Add `FACILITATOR_SECRET` to your backend and configure the SDK:
62
62
  ```typescript
63
63
  import { mantlePaywall } from '@puga-labs/x402-mantle-sdk/server/express';
64
64
 
65
+ // Self-hosted facilitator (requires facilitatorUrl + facilitatorSecret)
65
66
  const pay = mantlePaywall({
66
67
  priceUsd: 0.01,
67
68
  payTo: '0xYourWallet',
68
- facilitatorUrl: 'https://your-facilitator.com',
69
- facilitatorSecret: process.env.FACILITATOR_SECRET, // Same secret as in facilitator
70
- projectKey: process.env.PROJECT_KEY, // Optional: from dashboard for analytics
69
+ facilitatorUrl: 'https://your-facilitator.com', // Required for self-hosted
70
+ facilitatorSecret: process.env.FACILITATOR_SECRET!, // Required for self-hosted
71
+ // projectKey: process.env.PROJECT_KEY, // Optional: for analytics
71
72
  });
72
73
  ```
73
74
 
74
- **Note:** The `facilitatorUrl` is automatically passed to clients via 402 responses, so frontend code doesn't need to configure it.
75
+ **Note:** When using `facilitatorSecret` (self-hosted mode), `facilitatorUrl` is required. The URL is automatically passed to clients via 402 responses, so frontend code doesn't need to configure it.
75
76
 
76
77
  ## Running Locally
77
78
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-mantle-facilitator",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {
@@ -53,16 +53,17 @@ This prevents third parties from using your facilitator to settle their payments
53
53
  ```typescript
54
54
  import { mantlePaywall } from '@puga-labs/x402-mantle-sdk/server/express';
55
55
 
56
+ // Self-hosted facilitator (requires facilitatorUrl + facilitatorSecret)
56
57
  const pay = mantlePaywall({
57
58
  priceUsd: 0.01,
58
59
  payTo: '0xYourWallet',
59
- facilitatorUrl: 'https://your-facilitator.com',
60
- facilitatorSecret: process.env.FACILITATOR_SECRET, // Same as in facilitator .env
61
- projectKey: process.env.PROJECT_KEY, // Optional: for analytics
60
+ facilitatorUrl: 'https://your-facilitator.com', // Required for self-hosted
61
+ facilitatorSecret: process.env.FACILITATOR_SECRET!, // Required for self-hosted
62
+ // projectKey: process.env.PROJECT_KEY, // Optional: for analytics
62
63
  });
63
64
  ```
64
65
 
65
- **Note:** The `facilitatorUrl` is automatically passed to clients via 402 responses.
66
+ **Note:** When using `facilitatorSecret`, `facilitatorUrl` is required. The URL is automatically passed to clients via 402 responses.
66
67
 
67
68
  ## Endpoints
68
69
 
@@ -1,27 +1,136 @@
1
1
  // src/config.ts
2
2
  import "dotenv/config";
3
+ import { ethers } from "ethers";
3
4
  import { DEFAULT_TELEMETRY_ENDPOINT } from "./constants";
4
5
 
5
- function required(name: string): string {
6
- const v = process.env[name];
7
- if (!v) throw new Error(`Missing env var: ${name}`);
8
- return v;
6
+ // ============================================
7
+ // Validation types and helpers
8
+ // ============================================
9
+
10
+ interface ConfigError {
11
+ field: string;
12
+ message: string;
13
+ howToFix: string[];
14
+ }
15
+
16
+ function validateConfig(): ConfigError[] {
17
+ const errors: ConfigError[] = [];
18
+
19
+ // Required: FACILITATOR_PRIVATE_KEY
20
+ const privateKey = process.env.FACILITATOR_PRIVATE_KEY;
21
+ if (!privateKey) {
22
+ errors.push({
23
+ field: "FACILITATOR_PRIVATE_KEY",
24
+ message: "This is the wallet that will pay gas fees for settlements.",
25
+ howToFix: [
26
+ "Open .env file",
27
+ "Set FACILITATOR_PRIVATE_KEY=0xYourPrivateKey",
28
+ "Need a wallet? Create one at https://metamask.io",
29
+ "Make sure it has MNT for gas fees.",
30
+ ],
31
+ });
32
+ } else if (!privateKey.startsWith("0x") || privateKey.length !== 66) {
33
+ errors.push({
34
+ field: "FACILITATOR_PRIVATE_KEY",
35
+ message: "Invalid private key format.",
36
+ howToFix: [
37
+ "Private key must start with 0x",
38
+ "Private key must be 66 characters (0x + 64 hex digits)",
39
+ `Current length: ${privateKey.length} characters`,
40
+ ],
41
+ });
42
+ }
43
+
44
+ // Required: FACILITATOR_SECRET
45
+ const secret = process.env.FACILITATOR_SECRET;
46
+ if (!secret) {
47
+ errors.push({
48
+ field: "FACILITATOR_SECRET",
49
+ message: "This secret authenticates your backend with the facilitator.",
50
+ howToFix: [
51
+ "Open .env file",
52
+ "Set FACILITATOR_SECRET=fac_YOUR_SECRET_HERE",
53
+ "Then copy this value to your backend's facilitatorSecret config",
54
+ ],
55
+ });
56
+ }
57
+
58
+ // Required: RPC_URL
59
+ if (!process.env.RPC_URL) {
60
+ errors.push({
61
+ field: "RPC_URL",
62
+ message: "RPC URL for connecting to Mantle network.",
63
+ howToFix: [
64
+ "Open .env file",
65
+ "Set RPC_URL=https://rpc.mantle.xyz",
66
+ ],
67
+ });
68
+ }
69
+
70
+ // Required: USDC_ADDRESS
71
+ if (!process.env.USDC_ADDRESS) {
72
+ errors.push({
73
+ field: "USDC_ADDRESS",
74
+ message: "USDC contract address on Mantle.",
75
+ howToFix: [
76
+ "Open .env file",
77
+ "Set USDC_ADDRESS=0x09Bc4E0D864854c6aFB6eB9A9cdF58aC190D0dF9",
78
+ ],
79
+ });
80
+ }
81
+
82
+ return errors;
9
83
  }
10
84
 
85
+ function printConfigErrors(errors: ConfigError[]): void {
86
+ console.error("");
87
+ console.error("CONFIGURATION ERROR");
88
+ console.error("=".repeat(50));
89
+
90
+ for (const error of errors) {
91
+ console.error("");
92
+ console.error(`Missing: ${error.field}`);
93
+ console.error(`${error.message}`);
94
+ console.error("");
95
+ console.error("To fix:");
96
+ error.howToFix.forEach((step, i) => {
97
+ console.error(` ${i + 1}. ${step}`);
98
+ });
99
+ }
100
+
101
+ console.error("");
102
+ console.error("=".repeat(50));
103
+ console.error("");
104
+ }
105
+
106
+ // ============================================
107
+ // Run validation at startup
108
+ // ============================================
109
+
110
+ const configErrors = validateConfig();
111
+ if (configErrors.length > 0) {
112
+ printConfigErrors(configErrors);
113
+ process.exit(1);
114
+ }
115
+
116
+ // ============================================
117
+ // Build config (all required fields are validated)
118
+ // ============================================
119
+
11
120
  export const CONFIG = {
12
121
  port: Number(process.env.PORT ?? 8080),
13
122
 
14
123
  networkId: process.env.NETWORK_ID ?? "mantle-mainnet",
15
124
  chainId: Number(process.env.CHAIN_ID ?? 5000),
16
- rpcUrl: required("RPC_URL"),
125
+ rpcUrl: process.env.RPC_URL!,
17
126
 
18
- usdcAddress: required("USDC_ADDRESS"),
127
+ usdcAddress: process.env.USDC_ADDRESS!,
19
128
  usdcDecimals: Number(process.env.USDC_DECIMALS ?? 6),
20
129
 
21
- facilitatorPrivateKey: required("FACILITATOR_PRIVATE_KEY"),
130
+ facilitatorPrivateKey: process.env.FACILITATOR_PRIVATE_KEY!,
22
131
 
23
132
  // Secret for settle token verification (required for security)
24
- facilitatorSecret: required("FACILITATOR_SECRET"),
133
+ facilitatorSecret: process.env.FACILITATOR_SECRET!,
25
134
 
26
135
  logLevel: process.env.LOG_LEVEL ?? "info",
27
136
 
@@ -33,3 +142,12 @@ export const CONFIG = {
33
142
  }
34
143
  : undefined,
35
144
  } as const;
145
+
146
+ // ============================================
147
+ // Derive facilitator address from private key
148
+ // ============================================
149
+
150
+ export function getFacilitatorAddress(): string {
151
+ const wallet = new ethers.Wallet(CONFIG.facilitatorPrivateKey);
152
+ return wallet.address;
153
+ }
@@ -1,7 +1,7 @@
1
1
  // src/index.ts
2
2
  import express from "express";
3
3
  import cors from "cors";
4
- import { CONFIG } from "./config";
4
+ import { CONFIG, getFacilitatorAddress } from "./config";
5
5
 
6
6
  import { healthRoute } from "./routes/health";
7
7
  import { supportedRoute } from "./routes/supported";
@@ -25,7 +25,36 @@ app.get("/supported", supportedRoute);
25
25
  app.post("/verify", verifyRoute);
26
26
  app.post("/settle", settleRoute);
27
27
 
28
+ // ============================================
29
+ // Startup banner
30
+ // ============================================
31
+
32
+ function printStartupBanner(): void {
33
+ const address = getFacilitatorAddress();
34
+ const shortAddress = `${address.slice(0, 6)}...${address.slice(-4)}`;
35
+ const telemetryStatus = CONFIG.telemetry ? "enabled" : "disabled";
36
+
37
+ console.log("");
38
+ console.log("+".padEnd(50, "-") + "+");
39
+ console.log("| Mantle x402 Facilitator".padEnd(50) + "|");
40
+ console.log("+".padEnd(50, "-") + "+");
41
+ console.log(`| Network: ${CONFIG.networkId} (${CONFIG.chainId})`.padEnd(50) + "|");
42
+ console.log(`| Address: ${shortAddress}`.padEnd(50) + "|");
43
+ console.log(`| Port: ${CONFIG.port}`.padEnd(50) + "|");
44
+ console.log(`| Telemetry: ${telemetryStatus}`.padEnd(50) + "|");
45
+ console.log("+".padEnd(50, "-") + "+");
46
+ console.log("");
47
+
48
+ // Telemetry warning
49
+ if (!CONFIG.telemetry) {
50
+ console.log("Note: Telemetry is disabled.");
51
+ console.log(" To enable analytics, set TELEMETRY_PROJECT_KEY in .env");
52
+ console.log(" Get your key at https://x402mantlesdk.xyz/dashboard");
53
+ console.log("");
54
+ }
55
+ }
56
+
28
57
  app.listen(CONFIG.port, () => {
29
- console.log(`Facilitator server listening on http://localhost:${CONFIG.port}`);
30
- console.log(`Network: ${CONFIG.networkId} (chainId=${CONFIG.chainId})`);
58
+ printStartupBanner();
59
+ console.log(`Server listening on http://localhost:${CONFIG.port}`);
31
60
  });