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 +5 -4
- package/package.json +1 -1
- package/template/README.md +5 -4
- package/template/src/config.ts +126 -8
- package/template/src/index.ts +32 -3
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
|
|
70
|
-
projectKey: process.env.PROJECT_KEY, // Optional:
|
|
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:**
|
|
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
package/template/README.md
CHANGED
|
@@ -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
|
|
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:**
|
|
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
|
|
package/template/src/config.ts
CHANGED
|
@@ -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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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:
|
|
125
|
+
rpcUrl: process.env.RPC_URL!,
|
|
17
126
|
|
|
18
|
-
usdcAddress:
|
|
127
|
+
usdcAddress: process.env.USDC_ADDRESS!,
|
|
19
128
|
usdcDecimals: Number(process.env.USDC_DECIMALS ?? 6),
|
|
20
129
|
|
|
21
|
-
facilitatorPrivateKey:
|
|
130
|
+
facilitatorPrivateKey: process.env.FACILITATOR_PRIVATE_KEY!,
|
|
22
131
|
|
|
23
132
|
// Secret for settle token verification (required for security)
|
|
24
|
-
facilitatorSecret:
|
|
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
|
+
}
|
package/template/src/index.ts
CHANGED
|
@@ -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
|
-
|
|
30
|
-
console.log(`
|
|
58
|
+
printStartupBanner();
|
|
59
|
+
console.log(`Server listening on http://localhost:${CONFIG.port}`);
|
|
31
60
|
});
|