@newblock/iautopay-mcp 0.0.6 → 0.0.7
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/CLAUDE_CLI_MCP_SETUP.md +1 -5
- package/README.md +4 -42
- package/dist/iautopay-mcp.js +0 -4
- package/package.json +1 -1
- package/dist/server.js +0 -585
package/CLAUDE_CLI_MCP_SETUP.md
CHANGED
|
@@ -8,8 +8,6 @@ Configuration is automatically generated at installation via `opencode.json`.
|
|
|
8
8
|
|
|
9
9
|
Available commands:
|
|
10
10
|
- `/autopay_guide` - Show iAutoPay usage guide
|
|
11
|
-
- `/autopay_toA` - Quick payment of 0.01 USDC
|
|
12
|
-
- `/autopay_toB` - Quick payment of 0.05 USDC (with confirmation)
|
|
13
11
|
- `/autopay_buy_apikey_1day` - Buy 1-day API Key (0.09 USDC)
|
|
14
12
|
- `/autopay_buy_apikey_7days` - Buy 7-day API Key (0.49 USDC)
|
|
15
13
|
- `/autopay_get_info` - Get iAutoPay server information
|
|
@@ -68,9 +66,7 @@ When using Claude CLI with MCP enabled, the following tools are available:
|
|
|
68
66
|
|
|
69
67
|
- `guide` - Display complete iAutoPay usage guide
|
|
70
68
|
- `info` - Get server information (stock, prices, network config)
|
|
71
|
-
- `buy_apikey` - Buy API key (supports 1/7 day durations)
|
|
72
|
-
- `pay_stablecoin` - Pay stablecoin to specified address
|
|
73
|
-
- `sync_opencode_config` - Auto-configure opencode.json shortcuts
|
|
69
|
+
- `buy_apikey` - Buy API key (supports 1/7/30 day durations)
|
|
74
70
|
- `refresh_pricing` - Refresh prices from server
|
|
75
71
|
|
|
76
72
|
## Usage Examples
|
package/README.md
CHANGED
|
@@ -120,16 +120,8 @@ Add these shortcuts to your `opencode.json` for faster access:
|
|
|
120
120
|
}
|
|
121
121
|
}
|
|
122
122
|
},
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
"template": "Use pay_stablecoin tool to pay 0.01 USDC to 0x1a85156c2943b63febeee7883bd84a7d1cf0da0c, params: to=\"0x1a85156c2943b63febeee7883bd84a7d1cf0da0c\", amount=\"10000\"",
|
|
126
|
-
"description": "Pay 0.01 USDC to account A"
|
|
127
|
-
},
|
|
128
|
-
"autopay_toB": {
|
|
129
|
-
"template": "First use question tool to ask user confirmation with options: 1) Confirm (continue payment), 2) Cancel (do not pay). Show payment details: Pay 0.05 USDC to 0x1a85156c2943b63febeee7883bd84a7d1cf0da0c, params: to=\"0x1a85156c2943b63febeee7883bd84a7d1cf0da0c\", amount=\"50000\". Only proceed if user confirms.",
|
|
130
|
-
"description": "Pay 0.05 USDC to account A (requires confirmation)"
|
|
131
|
-
},
|
|
132
|
-
"autopay_buy_apikey_1day": {
|
|
123
|
+
"command": {
|
|
124
|
+
"autopay_buy_apikey_1day": {
|
|
133
125
|
"template": "Use buy_apikey tool to buy 1-day API Key, params: {\"duration\": 1}",
|
|
134
126
|
"description": "Buy 1-day API Key (0.09 USDC)"
|
|
135
127
|
},
|
|
@@ -149,8 +141,6 @@ Add these shortcuts to your `opencode.json` for faster access:
|
|
|
149
141
|
}
|
|
150
142
|
```
|
|
151
143
|
|
|
152
|
-
Run `sync_opencode_config` tool to auto-add these commands to your config.
|
|
153
|
-
|
|
154
144
|
### Opencode Quick Commands Usage Examples
|
|
155
145
|
|
|
156
146
|
### 1: /autopay_guide
|
|
@@ -161,13 +151,9 @@ iAutoPay Usage Guide
|
|
|
161
151
|
Available Tools
|
|
162
152
|
- guide - Display complete usage guide
|
|
163
153
|
- info - Get server information (stock, prices, network config)
|
|
164
|
-
- buy_apikey - Buy API key (supports 1/7 day durations)
|
|
165
|
-
- pay_stablecoin - Pay stablecoin to specified address
|
|
166
|
-
- sync_opencode_config - Auto-configure opencode.json shortcuts
|
|
154
|
+
- buy_apikey - Buy API key (supports 1/7/30 day durations)
|
|
167
155
|
- refresh_pricing - Refresh prices from server
|
|
168
156
|
Quick Commands
|
|
169
|
-
- autopay_toA - Quick pay 0.01 USDC
|
|
170
|
-
- autopay_toB - Pay 0.1 USDC (requires confirmation)
|
|
171
157
|
- autopay_buy_apikey_1day - Buy 1-day API Key (0.1 USDC)
|
|
172
158
|
- autopay_buy_apikey_7days - Buy 7-day API Key (0.9 USDC)
|
|
173
159
|
- autopay_get_info - Quick get server information
|
|
@@ -180,31 +166,7 @@ Pricing
|
|
|
180
166
|
- 7 days: 0.9 USDC
|
|
181
167
|
Environment: dev
|
|
182
168
|
|
|
183
|
-
### 2: /
|
|
184
|
-
|
|
185
|
-
Output:
|
|
186
|
-
|
|
187
|
-
Paying 0.01 USDC to 0x1a85156c2943b63febeee7883bd84a7d1cf0da0c...
|
|
188
|
-
Transaction hash: 0xabc123...
|
|
189
|
-
Payment successful!
|
|
190
|
-
|
|
191
|
-
### 3: /autopay_toB
|
|
192
|
-
|
|
193
|
-
Output:
|
|
194
|
-
|
|
195
|
-
Please confirm payment:
|
|
196
|
-
- Amount: 0.05 USDC
|
|
197
|
-
- Recipient: 0x1a85156c2943b63febeee7883bd84a7d1cf0da0c
|
|
198
|
-
- Network: Base Sepolia (84532)
|
|
199
|
-
|
|
200
|
-
Select: 1) Confirm 2) Cancel
|
|
201
|
-
[User selects confirm]
|
|
202
|
-
|
|
203
|
-
Paying 0.05 USDC to 0x1a85156c2943b63febeee7883bd84a7d1cf0da0c...
|
|
204
|
-
Transaction hash: 0xdef456...
|
|
205
|
-
Payment successful!
|
|
206
|
-
|
|
207
|
-
### 4: /autopay_buy_apikey_1day
|
|
169
|
+
### 2: /autopay_buy_apikey_1day
|
|
208
170
|
|
|
209
171
|
Output:
|
|
210
172
|
|
package/dist/iautopay-mcp.js
CHANGED
|
@@ -451,13 +451,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
451
451
|
{ name: "guide", description: "显示完整使用指南" },
|
|
452
452
|
{ name: "info", description: "获取服务器信息(库存、价格、网络配置)" },
|
|
453
453
|
{ name: "buy_apikey", description: "购买 API key(支持1/7/30天时长)" },
|
|
454
|
-
{ name: "pay_stablecoin", description: "支付稳定币到指定地址" },
|
|
455
|
-
{ name: "sync_opencode_config", description: "自动配置 opencode.json 快捷命令" },
|
|
456
454
|
{ name: "refresh_pricing", description: "从服务器刷新价格" }
|
|
457
455
|
],
|
|
458
456
|
commands: [
|
|
459
|
-
{ name: "autopay_toA", description: "快速支付 0.01 USDC" },
|
|
460
|
-
{ name: "autopay_toB", description: "支付 0.05 USDC(需确认)" },
|
|
461
457
|
{ name: "autopay_buy_apikey_1day", description: `购买1天API Key(${pricing["1day"]})` },
|
|
462
458
|
{ name: "autopay_buy_apikey_7days", description: `购买7天API Key(${pricing["7days"]})` },
|
|
463
459
|
{ name: "autopay_buy_apikey_30days", description: `购买30天API Key(${pricing["30days"]})` },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@newblock/iautopay-mcp",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"description": "iAutoPay is an MCP service that enables AI agents to automatically pay for purchases. It currently runs on Base chain (operated by Coinbase) and supports USDC payments.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/iautopay-mcp.js",
|
package/dist/server.js
DELETED
|
@@ -1,585 +0,0 @@
|
|
|
1
|
-
import crypto from "node:crypto";
|
|
2
|
-
import { fileURLToPath } from "node:url";
|
|
3
|
-
import { dirname } from "node:path";
|
|
4
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
5
|
-
const __dirname = dirname(__filename);
|
|
6
|
-
import { Server, } from "@modelcontextprotocol/sdk/server/index.js";
|
|
7
|
-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
8
|
-
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
9
|
-
import { z } from "zod";
|
|
10
|
-
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
11
|
-
import { createPublicClient, createWalletClient, hexToSignature, http, isAddress, } from "viem";
|
|
12
|
-
import { privateKeyToAccount } from "viem/accounts";
|
|
13
|
-
// ============================================================================
|
|
14
|
-
// Hardcoded Configuration (no .env dependency)
|
|
15
|
-
// ============================================================================
|
|
16
|
-
const CUR_ENV = 'dev';
|
|
17
|
-
const CONFIG = {
|
|
18
|
-
// User's private key for signing payments (provided via opencode.json environment)
|
|
19
|
-
BUYER_PRIVATE_KEY: process.env.BUYER_PRIVATE_KEY,
|
|
20
|
-
// Remote Fact API server
|
|
21
|
-
FACT_API_URL: CUR_ENV == 'dev' ? "https://apipaymcp.okart.fun" : "https://apipaymcp.okart.fun",
|
|
22
|
-
// Blockchain configuration (Base Sepolia for dev, Base Mainnet for prod)
|
|
23
|
-
RPC_URL: CUR_ENV == 'dev' ? "https://sepolia.base.org" : "https://mainnet.base.org",
|
|
24
|
-
CHAIN_ID: CUR_ENV == 'dev' ? "84532" : "8453",
|
|
25
|
-
// Payment token details
|
|
26
|
-
PAYMENT_TOKEN_NAME: "USDC",
|
|
27
|
-
};
|
|
28
|
-
// Validate required configuration
|
|
29
|
-
if (!CONFIG.BUYER_PRIVATE_KEY) {
|
|
30
|
-
throw new Error("BUYER_PRIVATE_KEY must be provided via opencode.json environment");
|
|
31
|
-
}
|
|
32
|
-
// Validate private key format
|
|
33
|
-
try {
|
|
34
|
-
privateKeyToAccount(CONFIG.BUYER_PRIVATE_KEY);
|
|
35
|
-
}
|
|
36
|
-
catch (error) {
|
|
37
|
-
throw new Error(`Invalid BUYER_PRIVATE_KEY format: ${error}`);
|
|
38
|
-
}
|
|
39
|
-
// Derive values from config
|
|
40
|
-
const BUYER_CHAIN_ID = CONFIG.CHAIN_ID;
|
|
41
|
-
const BUYER_RPC_URL = CONFIG.RPC_URL;
|
|
42
|
-
const FACT_API_URL = CONFIG.FACT_API_URL;
|
|
43
|
-
// USDC addresses
|
|
44
|
-
const USDC_ADDRESSES = {
|
|
45
|
-
dev: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
|
|
46
|
-
prod: "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"
|
|
47
|
-
};
|
|
48
|
-
const CURRENT_USDC = USDC_ADDRESSES[CUR_ENV];
|
|
49
|
-
const paymentRequirementsSchema = z.object({
|
|
50
|
-
scheme: z.string().optional(),
|
|
51
|
-
network: z.string().min(1),
|
|
52
|
-
asset: z.string().min(1),
|
|
53
|
-
price: z.string().min(1),
|
|
54
|
-
payee: z.string().optional(),
|
|
55
|
-
});
|
|
56
|
-
const payStablecoinInput = z.object({
|
|
57
|
-
to: z.string().min(1),
|
|
58
|
-
amount: z.string().min(1),
|
|
59
|
-
});
|
|
60
|
-
const buyApikeyInput = z.object({
|
|
61
|
-
duration: z.number().optional().refine(val => !val || [1, 7, 30].includes(val), {
|
|
62
|
-
message: "Duration must be 1, 7, or 30 days"
|
|
63
|
-
})
|
|
64
|
-
});
|
|
65
|
-
const getInfoInput = z.object({});
|
|
66
|
-
const guideInput = z.object({});
|
|
67
|
-
const syncOpencodeConfigInput = z.object({});
|
|
68
|
-
const refreshPricingInput = z.object({});
|
|
69
|
-
// ============================================================================
|
|
70
|
-
// Dynamic Pricing from Fact API
|
|
71
|
-
// ============================================================================
|
|
72
|
-
let CACHED_PRICING = null;
|
|
73
|
-
async function fetchPricingFromFactAPI() {
|
|
74
|
-
try {
|
|
75
|
-
const res = await fetch(`${FACT_API_URL}/info`);
|
|
76
|
-
if (res.ok) {
|
|
77
|
-
const data = await res.json();
|
|
78
|
-
CACHED_PRICING = {
|
|
79
|
-
"1day": `${data.prices?.["1dayUsdc"] || 0.09} USDC`,
|
|
80
|
-
"7days": `${data.prices?.["7daysUsdc"] || 0.49} USDC`,
|
|
81
|
-
"30days": `${data.prices?.["30daysUsdc"] || 0.99} USDC`
|
|
82
|
-
};
|
|
83
|
-
return CACHED_PRICING;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
catch (error) {
|
|
87
|
-
}
|
|
88
|
-
CACHED_PRICING = {
|
|
89
|
-
"1day": "0.09 USDC",
|
|
90
|
-
"7days": "0.49 USDC",
|
|
91
|
-
"30days": "0.99 USDC"
|
|
92
|
-
};
|
|
93
|
-
return CACHED_PRICING;
|
|
94
|
-
}
|
|
95
|
-
const buyerAccount = privateKeyToAccount(CONFIG.BUYER_PRIVATE_KEY);
|
|
96
|
-
const publicClient = createPublicClient({
|
|
97
|
-
transport: http(BUYER_RPC_URL),
|
|
98
|
-
});
|
|
99
|
-
const walletClient = createWalletClient({
|
|
100
|
-
account: buyerAccount,
|
|
101
|
-
transport: http(BUYER_RPC_URL),
|
|
102
|
-
});
|
|
103
|
-
// ============================================================================
|
|
104
|
-
// Retry Configuration
|
|
105
|
-
// ============================================================================
|
|
106
|
-
const RETRY_CONFIG = {
|
|
107
|
-
MAX_RETRIES: 3,
|
|
108
|
-
BASE_DELAY: 1000,
|
|
109
|
-
};
|
|
110
|
-
async function fetchWithRetry(url, options, context = 'API Request') {
|
|
111
|
-
for (let i = 0; i < RETRY_CONFIG.MAX_RETRIES; i++) {
|
|
112
|
-
try {
|
|
113
|
-
const response = await fetch(url, options);
|
|
114
|
-
if (response.ok || response.status < 500) {
|
|
115
|
-
return response;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
catch (error) {
|
|
119
|
-
}
|
|
120
|
-
if (i < RETRY_CONFIG.MAX_RETRIES - 1) {
|
|
121
|
-
const delay = RETRY_CONFIG.BASE_DELAY * Math.pow(2, i);
|
|
122
|
-
await new Promise(resolve => setTimeout(resolve, delay));
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
throw new Error(`${context} failed after ${RETRY_CONFIG.MAX_RETRIES} retries`);
|
|
126
|
-
}
|
|
127
|
-
// ============================================================================
|
|
128
|
-
// Token ABI
|
|
129
|
-
// ============================================================================
|
|
130
|
-
const tokenAbi = [
|
|
131
|
-
{
|
|
132
|
-
type: "function",
|
|
133
|
-
name: "name",
|
|
134
|
-
stateMutability: "view",
|
|
135
|
-
inputs: [],
|
|
136
|
-
outputs: [{ name: "", type: "string" }],
|
|
137
|
-
},
|
|
138
|
-
{
|
|
139
|
-
type: "function",
|
|
140
|
-
name: "balanceOf",
|
|
141
|
-
stateMutability: "view",
|
|
142
|
-
inputs: [{ name: "account", type: "address" }],
|
|
143
|
-
outputs: [{ name: "", type: "uint256" }],
|
|
144
|
-
},
|
|
145
|
-
{
|
|
146
|
-
type: "function",
|
|
147
|
-
name: "decimals",
|
|
148
|
-
stateMutability: "view",
|
|
149
|
-
inputs: [],
|
|
150
|
-
outputs: [{ name: "", type: "uint8" }],
|
|
151
|
-
},
|
|
152
|
-
{
|
|
153
|
-
type: "function",
|
|
154
|
-
name: "transferWithAuthorization",
|
|
155
|
-
stateMutability: "nonpayable",
|
|
156
|
-
inputs: [
|
|
157
|
-
{ name: "from", type: "address" },
|
|
158
|
-
{ name: "to", type: "address" },
|
|
159
|
-
{ name: "value", type: "uint256" },
|
|
160
|
-
{ name: "validAfter", type: "uint256" },
|
|
161
|
-
{ name: "validBefore", type: "uint256" },
|
|
162
|
-
{ name: "nonce", type: "bytes32" },
|
|
163
|
-
{ name: "v", type: "uint8" },
|
|
164
|
-
{ name: "r", type: "bytes32" },
|
|
165
|
-
{ name: "s", type: "bytes32" },
|
|
166
|
-
],
|
|
167
|
-
outputs: [{ name: "", type: "bool" }],
|
|
168
|
-
},
|
|
169
|
-
];
|
|
170
|
-
const transferWithAuthorizationTypes = {
|
|
171
|
-
TransferWithAuthorization: [
|
|
172
|
-
{ name: "from", type: "address" },
|
|
173
|
-
{ name: "to", type: "address" },
|
|
174
|
-
{ name: "value", type: "uint256" },
|
|
175
|
-
{ name: "validAfter", type: "uint256" },
|
|
176
|
-
{ name: "validBefore", type: "uint256" },
|
|
177
|
-
{ name: "nonce", type: "bytes32" },
|
|
178
|
-
],
|
|
179
|
-
};
|
|
180
|
-
function parseChainId(network) {
|
|
181
|
-
const parts = network.split(":");
|
|
182
|
-
if (parts.length === 2 && parts[0] === "eip155") {
|
|
183
|
-
const value = Number(parts[1]);
|
|
184
|
-
if (!Number.isNaN(value))
|
|
185
|
-
return value;
|
|
186
|
-
}
|
|
187
|
-
return Number(BUYER_CHAIN_ID);
|
|
188
|
-
}
|
|
189
|
-
async function buildPaymentSignature(requirements) {
|
|
190
|
-
const chainId = parseChainId(requirements.network);
|
|
191
|
-
const verifyingContract = requirements.asset;
|
|
192
|
-
if (!isAddress(verifyingContract)) {
|
|
193
|
-
throw new Error("Invalid payment asset address");
|
|
194
|
-
}
|
|
195
|
-
let tokenName = await publicClient
|
|
196
|
-
.readContract({
|
|
197
|
-
address: verifyingContract,
|
|
198
|
-
abi: tokenAbi,
|
|
199
|
-
functionName: "name",
|
|
200
|
-
})
|
|
201
|
-
.catch(() => CONFIG.PAYMENT_TOKEN_NAME);
|
|
202
|
-
let version = "1";
|
|
203
|
-
// Special handling for Base Sepolia USDC and Base Mainnet USDC
|
|
204
|
-
const sepoliaUSDC = "0x036cbd53842c5426634e7929541ec2318f3dcf7e";
|
|
205
|
-
const mainnetUSDC = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913";
|
|
206
|
-
if (requirements.network === "eip155:84532" &&
|
|
207
|
-
requirements.asset.toLowerCase() === sepoliaUSDC.toLowerCase()) {
|
|
208
|
-
tokenName = "USDC";
|
|
209
|
-
version = "2";
|
|
210
|
-
}
|
|
211
|
-
else if (requirements.network === "eip155:8453" &&
|
|
212
|
-
requirements.asset.toLowerCase() === mainnetUSDC.toLowerCase()) {
|
|
213
|
-
tokenName = "USD Coin";
|
|
214
|
-
version = "2";
|
|
215
|
-
}
|
|
216
|
-
const nonce = `0x${crypto.randomBytes(32).toString("hex")}`;
|
|
217
|
-
const now = Math.floor(Date.now() / 1000);
|
|
218
|
-
const validAfter = BigInt(0);
|
|
219
|
-
const validBefore = BigInt(now + 28800);
|
|
220
|
-
const payee = requirements.payee ?? buyerAccount.address;
|
|
221
|
-
const signature = await walletClient.signTypedData({
|
|
222
|
-
domain: {
|
|
223
|
-
name: tokenName,
|
|
224
|
-
version,
|
|
225
|
-
chainId,
|
|
226
|
-
verifyingContract,
|
|
227
|
-
},
|
|
228
|
-
types: transferWithAuthorizationTypes,
|
|
229
|
-
primaryType: "TransferWithAuthorization",
|
|
230
|
-
message: {
|
|
231
|
-
from: buyerAccount.address,
|
|
232
|
-
to: payee,
|
|
233
|
-
value: BigInt(requirements.price),
|
|
234
|
-
validAfter,
|
|
235
|
-
validBefore,
|
|
236
|
-
nonce,
|
|
237
|
-
},
|
|
238
|
-
});
|
|
239
|
-
const { v, r, s } = hexToSignature(signature);
|
|
240
|
-
return {
|
|
241
|
-
from: buyerAccount.address,
|
|
242
|
-
to: payee,
|
|
243
|
-
value: requirements.price,
|
|
244
|
-
validAfter: validAfter.toString(),
|
|
245
|
-
validBefore: validBefore.toString(),
|
|
246
|
-
nonce,
|
|
247
|
-
v: Number(v),
|
|
248
|
-
r,
|
|
249
|
-
s,
|
|
250
|
-
};
|
|
251
|
-
}
|
|
252
|
-
async function payStablecoin(params) {
|
|
253
|
-
const [balance, decimals] = await Promise.all([
|
|
254
|
-
publicClient.readContract({
|
|
255
|
-
address: params.asset,
|
|
256
|
-
abi: tokenAbi,
|
|
257
|
-
functionName: 'balanceOf',
|
|
258
|
-
args: [buyerAccount.address]
|
|
259
|
-
}),
|
|
260
|
-
publicClient.readContract({
|
|
261
|
-
address: params.asset,
|
|
262
|
-
abi: tokenAbi,
|
|
263
|
-
functionName: 'decimals'
|
|
264
|
-
})
|
|
265
|
-
]);
|
|
266
|
-
const balanceNumber = Number(balance) / (10 ** Number(decimals));
|
|
267
|
-
const amountNumber = Number(params.amount) / 1e6;
|
|
268
|
-
if (balanceNumber < amountNumber) {
|
|
269
|
-
throw new Error(`Insufficient balance: required ${amountNumber.toFixed(6)} USDC, available ${balanceNumber.toFixed(6)} USDC`);
|
|
270
|
-
}
|
|
271
|
-
const requirements = {
|
|
272
|
-
scheme: "exact",
|
|
273
|
-
network: params.isTestnet ? "eip155:84532" : `eip155:${BUYER_CHAIN_ID}`,
|
|
274
|
-
asset: params.asset,
|
|
275
|
-
price: params.amount,
|
|
276
|
-
payee: params.to,
|
|
277
|
-
};
|
|
278
|
-
const signaturePayload = await buildPaymentSignature(requirements);
|
|
279
|
-
const transferRes = await fetchWithRetry(`${FACT_API_URL}/v1/transfer`, {
|
|
280
|
-
method: "POST",
|
|
281
|
-
headers: {
|
|
282
|
-
"content-type": "application/json",
|
|
283
|
-
"PAYMENT-SIGNATURE": JSON.stringify(signaturePayload),
|
|
284
|
-
},
|
|
285
|
-
body: JSON.stringify({
|
|
286
|
-
to: params.to,
|
|
287
|
-
amount: params.amount,
|
|
288
|
-
asset: params.asset,
|
|
289
|
-
}),
|
|
290
|
-
}, 'PAY_STABLECOIN Transfer');
|
|
291
|
-
if (!transferRes.ok) {
|
|
292
|
-
const error = await transferRes.text();
|
|
293
|
-
const networkInfo = params.isTestnet ? "Testnet (Base Sepolia 84532)" : `Mainnet (Base ${BUYER_CHAIN_ID === '8453' ? 'Mainnet' : BUYER_CHAIN_ID})`;
|
|
294
|
-
throw new Error(`Transfer failed (${networkInfo}): ${error}`);
|
|
295
|
-
}
|
|
296
|
-
const result = await transferRes.json();
|
|
297
|
-
const deductedAmount = amountNumber.toFixed(6);
|
|
298
|
-
const currentBalance = (balanceNumber - amountNumber).toFixed(6);
|
|
299
|
-
return {
|
|
300
|
-
...result,
|
|
301
|
-
from: buyerAccount.address,
|
|
302
|
-
to: params.to,
|
|
303
|
-
amount: `${amountNumber.toFixed(6)} USDC`,
|
|
304
|
-
asset: params.asset,
|
|
305
|
-
network: params.isTestnet ? "Testnet (Base Sepolia)" : "Mainnet (Base)",
|
|
306
|
-
deductedAmount: `${deductedAmount} USDC`,
|
|
307
|
-
currentBalance: `${currentBalance} USDC`
|
|
308
|
-
};
|
|
309
|
-
}
|
|
310
|
-
const server = new Server({ name: "autopay-server", version: "0.1.0" }, { capabilities: { tools: {} } });
|
|
311
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
312
|
-
return {
|
|
313
|
-
tools: [
|
|
314
|
-
{
|
|
315
|
-
name: "guide",
|
|
316
|
-
description: "⭐ FIRST TIME? Run this guide to learn how to use iAutoPay tools and commands.",
|
|
317
|
-
inputSchema: zodToJsonSchema(guideInput),
|
|
318
|
-
},
|
|
319
|
-
{
|
|
320
|
-
name: "info",
|
|
321
|
-
description: "Get iAutoPay server information (API key stock, price, network config). Tip: Check before buying API keys.",
|
|
322
|
-
inputSchema: zodToJsonSchema(getInfoInput),
|
|
323
|
-
},
|
|
324
|
-
{
|
|
325
|
-
name: "buy_apikey",
|
|
326
|
-
description: "Purchase an API key with optional duration (1/7/30 days). Prices: 1 day=0.9 USDC, 7 days=4.9 USDC, 30 days=9.9 USDC. Run 'info' first to confirm stock.",
|
|
327
|
-
inputSchema: zodToJsonSchema(buyApikeyInput),
|
|
328
|
-
},
|
|
329
|
-
{
|
|
330
|
-
name: "pay_stablecoin",
|
|
331
|
-
description: "Pay stablecoin to any address using EIP-3009. Amount is in smallest unit (e.g., 100000 = 0.1 USDC).",
|
|
332
|
-
inputSchema: zodToJsonSchema(payStablecoinInput),
|
|
333
|
-
},
|
|
334
|
-
{
|
|
335
|
-
name: "sync_opencode_config",
|
|
336
|
-
description: "Auto-configure opencode.json with quick commands (autopay_toA, autopay_toB, etc.)",
|
|
337
|
-
inputSchema: zodToJsonSchema(syncOpencodeConfigInput),
|
|
338
|
-
},
|
|
339
|
-
{
|
|
340
|
-
name: "refresh_pricing",
|
|
341
|
-
description: "Refresh pricing from API. Use this if prices are changed on the server.",
|
|
342
|
-
inputSchema: zodToJsonSchema(refreshPricingInput),
|
|
343
|
-
},
|
|
344
|
-
],
|
|
345
|
-
};
|
|
346
|
-
});
|
|
347
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
348
|
-
const { name, arguments: args } = request.params;
|
|
349
|
-
if (name === "pay_stablecoin") {
|
|
350
|
-
const parsed = payStablecoinInput.parse(args);
|
|
351
|
-
try {
|
|
352
|
-
const result = await payStablecoin({
|
|
353
|
-
to: parsed.to,
|
|
354
|
-
amount: parsed.amount,
|
|
355
|
-
asset: CURRENT_USDC,
|
|
356
|
-
isTestnet: CUR_ENV === 'dev',
|
|
357
|
-
});
|
|
358
|
-
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
359
|
-
}
|
|
360
|
-
catch (error) {
|
|
361
|
-
throw error;
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
if (name === "buy_apikey") {
|
|
365
|
-
const parsed = buyApikeyInput.parse(args);
|
|
366
|
-
const duration = parsed.duration || 1;
|
|
367
|
-
const [balance, decimals] = await Promise.all([
|
|
368
|
-
publicClient.readContract({
|
|
369
|
-
address: CURRENT_USDC,
|
|
370
|
-
abi: tokenAbi,
|
|
371
|
-
functionName: 'balanceOf',
|
|
372
|
-
args: [buyerAccount.address]
|
|
373
|
-
}),
|
|
374
|
-
publicClient.readContract({
|
|
375
|
-
address: CURRENT_USDC,
|
|
376
|
-
abi: tokenAbi,
|
|
377
|
-
functionName: 'decimals'
|
|
378
|
-
})
|
|
379
|
-
]);
|
|
380
|
-
const balanceNumber = Number(balance) / (10 ** Number(decimals));
|
|
381
|
-
const priceMap = {
|
|
382
|
-
1: CACHED_PRICING?.["1day"] || "0.09 USDC",
|
|
383
|
-
7: CACHED_PRICING?.["7days"] || "0.49 USDC",
|
|
384
|
-
30: CACHED_PRICING?.["30days"] || "0.99 USDC",
|
|
385
|
-
};
|
|
386
|
-
const priceString = priceMap[duration];
|
|
387
|
-
const priceNumber = parseFloat(priceString);
|
|
388
|
-
const priceWei = (priceNumber * 1e6).toString();
|
|
389
|
-
if (balanceNumber < priceNumber) {
|
|
390
|
-
throw new Error(`Insufficient balance: required ${priceNumber.toFixed(6)} USDC, available ${balanceNumber.toFixed(6)} USDC`);
|
|
391
|
-
}
|
|
392
|
-
const requirements = {
|
|
393
|
-
scheme: "exact",
|
|
394
|
-
network: CUR_ENV === 'dev' ? "eip155:84532" : `eip155:${BUYER_CHAIN_ID}`,
|
|
395
|
-
asset: CURRENT_USDC,
|
|
396
|
-
price: priceWei,
|
|
397
|
-
payee: "0x1a85156c2943b63febeee7883bd84a7d1cf0da0c",
|
|
398
|
-
};
|
|
399
|
-
const signaturePayload = await buildPaymentSignature(requirements);
|
|
400
|
-
const buyRes = await fetch(`${FACT_API_URL}/v1/buy-apikey`, {
|
|
401
|
-
method: "POST",
|
|
402
|
-
headers: {
|
|
403
|
-
"content-type": "application/json",
|
|
404
|
-
"PAYMENT-SIGNATURE": JSON.stringify(signaturePayload),
|
|
405
|
-
},
|
|
406
|
-
body: JSON.stringify({ duration })
|
|
407
|
-
});
|
|
408
|
-
if (!buyRes.ok) {
|
|
409
|
-
const error = await buyRes.text();
|
|
410
|
-
const networkInfo = CUR_ENV === 'dev' ? "Testnet (Base Sepolia 84532)" : `Mainnet (Base ${BUYER_CHAIN_ID})`;
|
|
411
|
-
throw new Error(`Buy API key failed (${networkInfo}): ${error}`);
|
|
412
|
-
}
|
|
413
|
-
const result = await buyRes.json();
|
|
414
|
-
return {
|
|
415
|
-
content: [{
|
|
416
|
-
type: "text",
|
|
417
|
-
text: JSON.stringify({
|
|
418
|
-
...result,
|
|
419
|
-
price: `${priceNumber.toFixed(6)} USDC`,
|
|
420
|
-
deductedAmount: `${priceNumber.toFixed(6)} USDC`,
|
|
421
|
-
currentBalance: `${(balanceNumber - priceNumber).toFixed(6)} USDC`
|
|
422
|
-
})
|
|
423
|
-
}]
|
|
424
|
-
};
|
|
425
|
-
}
|
|
426
|
-
if (name === "info") {
|
|
427
|
-
const parsed = getInfoInput.parse(args);
|
|
428
|
-
try {
|
|
429
|
-
const res = await fetch(`${FACT_API_URL}/info`);
|
|
430
|
-
if (res.ok) {
|
|
431
|
-
const data = await res.json();
|
|
432
|
-
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
433
|
-
}
|
|
434
|
-
else {
|
|
435
|
-
throw new Error(`Failed to fetch info: ${res.statusText}`);
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
catch (error) {
|
|
439
|
-
throw error;
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
if (name === "guide") {
|
|
443
|
-
const parsed = guideInput.parse(args);
|
|
444
|
-
const networkInfo = `${BUYER_CHAIN_ID === '84532' ? 'Base Sepolia' : 'Base Mainnet'} (${BUYER_CHAIN_ID})`;
|
|
445
|
-
const pricing = CACHED_PRICING || {
|
|
446
|
-
"1day": "0.09 USDC",
|
|
447
|
-
"7days": "0.49 USDC",
|
|
448
|
-
"30days": "0.99 USDC"
|
|
449
|
-
};
|
|
450
|
-
const toolsData = {
|
|
451
|
-
tools: [
|
|
452
|
-
{ name: "guide", description: "显示完整使用指南" },
|
|
453
|
-
{ name: "info", description: "获取服务器信息(库存、价格、网络配置)" },
|
|
454
|
-
{ name: "buy_apikey", description: "购买 API key(支持1/7/30天时长)" },
|
|
455
|
-
{ name: "pay_stablecoin", description: "支付稳定币到指定地址" },
|
|
456
|
-
{ name: "sync_opencode_config", description: "自动配置 opencode.json 快捷命令" },
|
|
457
|
-
{ name: "refresh_pricing", description: "从服务器刷新价格" }
|
|
458
|
-
],
|
|
459
|
-
commands: [
|
|
460
|
-
{ name: "autopay_toA", description: "快速支付 0.01 USDC" },
|
|
461
|
-
{ name: "autopay_toB", description: "支付 0.05 USDC(需确认)" },
|
|
462
|
-
{ name: "autopay_buy_apikey_1day", description: `购买1天API Key(${pricing["1day"]})` },
|
|
463
|
-
{ name: "autopay_buy_apikey_7days", description: `购买7天API Key(${pricing["7days"]})` },
|
|
464
|
-
{ name: "autopay_buy_apikey_30days", description: `购买30天API Key(${pricing["30days"]})` },
|
|
465
|
-
{ name: "autopay_get_info", description: "快速获取服务器信息" }
|
|
466
|
-
],
|
|
467
|
-
network: {
|
|
468
|
-
testnet: "Base Sepolia (84532)",
|
|
469
|
-
mainnet: "Base Mainnet (8453)",
|
|
470
|
-
current: networkInfo
|
|
471
|
-
},
|
|
472
|
-
pricing,
|
|
473
|
-
environment: CUR_ENV
|
|
474
|
-
};
|
|
475
|
-
return { content: [{ type: "text", text: JSON.stringify(toolsData, null, 2) }] };
|
|
476
|
-
}
|
|
477
|
-
if (name === "sync_opencode_config") {
|
|
478
|
-
const parsed = syncOpencodeConfigInput.parse(args);
|
|
479
|
-
try {
|
|
480
|
-
const fs = await import('fs/promises');
|
|
481
|
-
const opencodePath = '/Users/michael/opc/proj/iautopay/opencode.json';
|
|
482
|
-
const opencodeData = JSON.parse(await fs.readFile(opencodePath, 'utf-8'));
|
|
483
|
-
const pricing = CACHED_PRICING || {
|
|
484
|
-
"1day": "0.09 USDC",
|
|
485
|
-
"7days": "0.49 USDC",
|
|
486
|
-
"30days": "0.99 USDC"
|
|
487
|
-
};
|
|
488
|
-
const requiredCommands = {
|
|
489
|
-
"autopay_toA": {
|
|
490
|
-
"template": "使用 pay_stablecoin 工具向 0x1a85156c2943b63febeee7883bd84a7d1cf0da0c 支付 0.01 USDC,参数为:to=\"0x1a85156c2943b63febeee7883bd84a7d1cf0da0c\", amount=\"10000\"",
|
|
491
|
-
"description": "支付0.01 USDC给A账户"
|
|
492
|
-
},
|
|
493
|
-
"autopay_toB": {
|
|
494
|
-
"template": "首先使用 question 工具询问用户确认,选项包括:1) 确认(继续支付),2) 取消(不进行支付)。显示支付详情:向 0x1a85156c2943b63febeee7883bd84a7d1cf0da0c 支付 0.05 USDC,参数为:to=\"0x1a85156c2943b63febeee7883bd84a7d1cf0da0c\", amount=\"50000\"。只有用户选择确认时才继续支付。",
|
|
495
|
-
"description": "支付0.05 USDC给A账户(需要确认)"
|
|
496
|
-
},
|
|
497
|
-
"autopay_buy_apikey_1day": {
|
|
498
|
-
"template": "使用 buy_apikey 工具购买1天API Key,参数为:{\"duration\": 1}",
|
|
499
|
-
"description": `购买1天API Key(${pricing["1day"]})`
|
|
500
|
-
},
|
|
501
|
-
"autopay_buy_apikey_7days": {
|
|
502
|
-
"template": "使用 buy_apikey 工具购买7天API Key,参数为:{\"duration\": 7}",
|
|
503
|
-
"description": `购买7天API Key(${pricing["7days"]})`
|
|
504
|
-
},
|
|
505
|
-
"autopay_buy_apikey_30days": {
|
|
506
|
-
"template": "使用 buy_apikey 工具购买30天API Key,参数为:{\"duration\": 30}",
|
|
507
|
-
"description": `购买30天API Key(${pricing["30days"]})`
|
|
508
|
-
},
|
|
509
|
-
"autopay_get_info": {
|
|
510
|
-
"template": "使用 info 工具获取服务器信息(API Key 库存、价格、网络配置)",
|
|
511
|
-
"description": "获取iAutoPay服务器信息"
|
|
512
|
-
},
|
|
513
|
-
"autopay_guide": {
|
|
514
|
-
"template": "使用 guide 工具显示 iAutoPay 使用指南",
|
|
515
|
-
"description": "显示iAutoPay使用指南"
|
|
516
|
-
}
|
|
517
|
-
};
|
|
518
|
-
let addedCommands = [];
|
|
519
|
-
let updatedCommands = [];
|
|
520
|
-
if (!opencodeData.command) {
|
|
521
|
-
opencodeData.command = {};
|
|
522
|
-
}
|
|
523
|
-
for (const [key, value] of Object.entries(requiredCommands)) {
|
|
524
|
-
if (!opencodeData.command[key]) {
|
|
525
|
-
opencodeData.command[key] = value;
|
|
526
|
-
addedCommands.push(key);
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
if (addedCommands.length > 0) {
|
|
530
|
-
await fs.writeFile(opencodePath, JSON.stringify(opencodeData, null, 2), 'utf-8');
|
|
531
|
-
return {
|
|
532
|
-
content: [{
|
|
533
|
-
type: "text",
|
|
534
|
-
text: `✅ 已添加 ${addedCommands.length} 个命令到 opencode.json:\n${addedCommands.map(c => ` - ${c}`).join('\n')}`
|
|
535
|
-
}]
|
|
536
|
-
};
|
|
537
|
-
}
|
|
538
|
-
else {
|
|
539
|
-
return {
|
|
540
|
-
content: [{
|
|
541
|
-
type: "text",
|
|
542
|
-
text: "✅ 所有 autopay_ 命令已存在,无需更新"
|
|
543
|
-
}]
|
|
544
|
-
};
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
catch (error) {
|
|
548
|
-
throw new Error(`同步配置失败: ${error}`);
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
if (name === "refresh_pricing") {
|
|
552
|
-
const parsed = refreshPricingInput.parse(args);
|
|
553
|
-
try {
|
|
554
|
-
await fetchPricingFromFactAPI();
|
|
555
|
-
return {
|
|
556
|
-
content: [{
|
|
557
|
-
type: "text",
|
|
558
|
-
text: `✅ 价格已刷新:\n${JSON.stringify(CACHED_PRICING, null, 2)}`
|
|
559
|
-
}]
|
|
560
|
-
};
|
|
561
|
-
}
|
|
562
|
-
catch (error) {
|
|
563
|
-
throw new Error(`刷新价格失败: ${error}`);
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
throw new Error(`Unknown tool: ${name}`);
|
|
567
|
-
});
|
|
568
|
-
const transport = new StdioServerTransport();
|
|
569
|
-
await fetchPricingFromFactAPI();
|
|
570
|
-
try {
|
|
571
|
-
const usdcBalance = await publicClient.readContract({
|
|
572
|
-
address: CURRENT_USDC,
|
|
573
|
-
abi: tokenAbi,
|
|
574
|
-
functionName: 'balanceOf',
|
|
575
|
-
args: [buyerAccount.address]
|
|
576
|
-
});
|
|
577
|
-
const decimals = await publicClient.readContract({
|
|
578
|
-
address: CURRENT_USDC,
|
|
579
|
-
abi: tokenAbi,
|
|
580
|
-
functionName: 'decimals'
|
|
581
|
-
});
|
|
582
|
-
}
|
|
583
|
-
catch (error) {
|
|
584
|
-
}
|
|
585
|
-
await server.connect(transport);
|