@newblock/iautopay-mcp 0.0.5 → 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 +14 -15
- package/README.md +91 -180
- package/dist/iautopay-mcp.js +101 -108
- package/mcp-config.json.example +2 -2
- package/package.json +1 -1
- package/dist/server.js +0 -585
package/CLAUDE_CLI_MCP_SETUP.md
CHANGED
|
@@ -7,11 +7,10 @@ This project supports both OpenCode and Claude CLI MCP servers.
|
|
|
7
7
|
Configuration is automatically generated at installation via `opencode.json`.
|
|
8
8
|
|
|
9
9
|
Available commands:
|
|
10
|
-
- `/
|
|
11
|
-
- `/
|
|
12
|
-
- `/
|
|
13
|
-
- `/
|
|
14
|
-
- `/autopay_getInfo` - Get service information
|
|
10
|
+
- `/autopay_guide` - Show iAutoPay usage guide
|
|
11
|
+
- `/autopay_buy_apikey_1day` - Buy 1-day API Key (0.09 USDC)
|
|
12
|
+
- `/autopay_buy_apikey_7days` - Buy 7-day API Key (0.49 USDC)
|
|
13
|
+
- `/autopay_get_info` - Get iAutoPay server information
|
|
15
14
|
|
|
16
15
|
## Claude CLI
|
|
17
16
|
|
|
@@ -28,7 +27,7 @@ This method uses the configuration file generated at `mcp-config.json` during in
|
|
|
28
27
|
|
|
29
28
|
```bash
|
|
30
29
|
# Add MCP server to Claude CLI
|
|
31
|
-
BUYER_PRIVATE_KEY="your_private_key" claude mcp add
|
|
30
|
+
BUYER_PRIVATE_KEY="your_private_key" claude mcp add autopay \
|
|
32
31
|
-e BUYER_PRIVATE_KEY="your_private_key" \
|
|
33
32
|
-- npx -y @newblock/iautopay-mcp
|
|
34
33
|
|
|
@@ -36,7 +35,7 @@ BUYER_PRIVATE_KEY="your_private_key" claude mcp add iauto-pay \
|
|
|
36
35
|
claude mcp list
|
|
37
36
|
|
|
38
37
|
# Should show:
|
|
39
|
-
#
|
|
38
|
+
# autopay: npx -y @newblock/iautopay-mcp - ✓ Connected
|
|
40
39
|
```
|
|
41
40
|
|
|
42
41
|
After adding, you can simply run `claude` without any additional flags.
|
|
@@ -47,7 +46,7 @@ After adding, you can simply run `claude` without any additional flags.
|
|
|
47
46
|
# Create mcp-config.json manually:
|
|
48
47
|
{
|
|
49
48
|
"mcpServers": {
|
|
50
|
-
"
|
|
49
|
+
"autopay": {
|
|
51
50
|
"command": "npx",
|
|
52
51
|
"args": ["-y", "@newblock/iautopay-mcp"],
|
|
53
52
|
"env": {
|
|
@@ -65,10 +64,10 @@ claude --mcp-config mcp-config.json
|
|
|
65
64
|
|
|
66
65
|
When using Claude CLI with MCP enabled, the following tools are available:
|
|
67
66
|
|
|
68
|
-
- `
|
|
69
|
-
- `
|
|
70
|
-
- `
|
|
71
|
-
- `
|
|
67
|
+
- `guide` - Display complete iAutoPay usage guide
|
|
68
|
+
- `info` - Get server information (stock, prices, network config)
|
|
69
|
+
- `buy_apikey` - Buy API key (supports 1/7/30 day durations)
|
|
70
|
+
- `refresh_pricing` - Refresh prices from server
|
|
72
71
|
|
|
73
72
|
## Usage Examples
|
|
74
73
|
|
|
@@ -91,14 +90,14 @@ claude
|
|
|
91
90
|
claude mcp list
|
|
92
91
|
|
|
93
92
|
# Get details of specific server
|
|
94
|
-
claude mcp get
|
|
93
|
+
claude mcp get autopay
|
|
95
94
|
```
|
|
96
95
|
|
|
97
96
|
## Removing MCP Server
|
|
98
97
|
|
|
99
98
|
If you added to local config:
|
|
100
99
|
```bash
|
|
101
|
-
claude mcp remove
|
|
100
|
+
claude mcp remove autopay -s local
|
|
102
101
|
```
|
|
103
102
|
|
|
104
103
|
If using config file, simply remove the `--mcp-config mcp-config.json` flag when running `claude`.
|
|
@@ -112,7 +111,7 @@ If using config file, simply remove the `--mcp-config mcp-config.json` flag when
|
|
|
112
111
|
|
|
113
112
|
**Tools not showing up:**
|
|
114
113
|
1. Verify MCP connection: `claude mcp list`
|
|
115
|
-
2. Check server health: `claude mcp get
|
|
114
|
+
2. Check server health: `claude mcp get autopay`
|
|
116
115
|
3. Restart Claude CLI
|
|
117
116
|
|
|
118
117
|
**Wrong environment (dev vs prod):**
|
package/README.md
CHANGED
|
@@ -1,22 +1,6 @@
|
|
|
1
1
|
# iAutoPay MCP Service
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## Features
|
|
6
|
-
|
|
7
|
-
- 🚀 **Smart Payment**: Small automatic payments, large amount manual approval
|
|
8
|
-
- 💳 **USDC Payments**: Support USDC-based payments on Base chain
|
|
9
|
-
- 🔐 **Secure**: Environment variable based configuration for private keys
|
|
10
|
-
- 🤖 **AI-Native**: Full MCP integration designed for AI agents
|
|
11
|
-
- 💸 **Fixed Transfer**: Preset fixed transfer account command, direct transfer by command
|
|
12
|
-
- 🔑 **API Key Purchase**: Support GLM4.7 LLM API Key purchase service with dynamic pricing
|
|
13
|
-
|
|
14
|
-
## Supported Models
|
|
15
|
-
|
|
16
|
-
API keys purchased through this service provide access to:
|
|
17
|
-
- `z-ai/glm4.7` (GLM4.7 with thinking chain support)
|
|
18
|
-
- `minimaxai/minimax-m2.1` (MiniMax general LLM)
|
|
19
|
-
- `deepseek-ai/deepseek-v3.2` (DeepSeek with thinking chain)
|
|
3
|
+
iAutoPay is an MCP (Model Context Protocol) service that enables AI agents to automatically pay for purchases. It supports all EVM-compatible public chains and USDC payments. Agents can use it to automatically purchase paid AI-related services and data.
|
|
20
4
|
|
|
21
5
|
## Installation
|
|
22
6
|
|
|
@@ -34,27 +18,10 @@ This will automatically download and cache the package.
|
|
|
34
18
|
|
|
35
19
|
```bash
|
|
36
20
|
npm install -g @newblock/iautopay-mcp
|
|
37
|
-
@newblock/iautopay-mcp
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
### Option 3: Project Dependency
|
|
41
|
-
|
|
42
|
-
```bash
|
|
43
|
-
npm install @newblock/iautopay-mcp
|
|
44
|
-
node node_modules/@newblock/iautopay-mcp/dist/iautopay-mcp.js
|
|
45
21
|
```
|
|
46
22
|
|
|
47
23
|
## Configuration
|
|
48
24
|
|
|
49
|
-
### Environment Variables
|
|
50
|
-
|
|
51
|
-
Set required environment variables:
|
|
52
|
-
|
|
53
|
-
```bash
|
|
54
|
-
# Required: Your wallet private key for signing payments
|
|
55
|
-
export BUYER_PRIVATE_KEY="0x..."
|
|
56
|
-
```
|
|
57
|
-
|
|
58
25
|
### OpenCode Configuration
|
|
59
26
|
|
|
60
27
|
Add to your `opencode.json`:
|
|
@@ -63,12 +30,12 @@ Add to your `opencode.json`:
|
|
|
63
30
|
{
|
|
64
31
|
"$schema": "https://opencode.ai/config.json",
|
|
65
32
|
"mcp": {
|
|
66
|
-
"
|
|
33
|
+
"autopay": {
|
|
67
34
|
"type": "local",
|
|
68
35
|
"command": ["npx", "-y", "@newblock/iautopay-mcp"],
|
|
69
36
|
"enabled": true,
|
|
70
37
|
"environment": {
|
|
71
|
-
"BUYER_PRIVATE_KEY": "
|
|
38
|
+
"BUYER_PRIVATE_KEY": "0xEVM_wallet_private_key"
|
|
72
39
|
}
|
|
73
40
|
}
|
|
74
41
|
}
|
|
@@ -83,7 +50,7 @@ For detailed instructions on using Claude CLI with MCP, see [CLAUDE_CLI_MCP_SETU
|
|
|
83
50
|
|
|
84
51
|
```bash
|
|
85
52
|
# Install and add to Claude CLI
|
|
86
|
-
BUYER_PRIVATE_KEY="your_private_key" claude mcp add
|
|
53
|
+
BUYER_PRIVATE_KEY="your_private_key" claude mcp add autopay \
|
|
87
54
|
-e BUYER_PRIVATE_KEY="your_private_key" \
|
|
88
55
|
-- npx -y @newblock/iautopay-mcp
|
|
89
56
|
|
|
@@ -98,179 +65,122 @@ Add to your `~/.claude/claude_desktop_config.json`:
|
|
|
98
65
|
```json
|
|
99
66
|
{
|
|
100
67
|
"mcpServers": {
|
|
101
|
-
"
|
|
68
|
+
"autopay": {
|
|
102
69
|
"command": "npx",
|
|
103
|
-
"args": ["@newblock/iautopay-mcp"],
|
|
70
|
+
"args": ["-y", "@newblock/iautopay-mcp"],
|
|
104
71
|
"env": {
|
|
105
|
-
"BUYER_PRIVATE_KEY": "
|
|
72
|
+
"BUYER_PRIVATE_KEY": "0xEVM_wallet_private_key"
|
|
106
73
|
}
|
|
107
74
|
}
|
|
108
75
|
}
|
|
109
76
|
}
|
|
110
77
|
```
|
|
111
78
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
79
|
+
> **Basic Knowledge About Cryptocurrency and Wallets**
|
|
80
|
+
>
|
|
81
|
+
> Before using iAutoPay, you need to understand some basic cryptocurrency concepts:
|
|
82
|
+
>
|
|
83
|
+
> - **Wallet Private Key**: A key similar to a password, used to sign transactions. **Keep it safe and never share it with anyone!**
|
|
84
|
+
> - **Testnet**: A network for testing where test coins can be obtained for free from faucets
|
|
85
|
+
> - **Mainnet**: The official network that requires real funds
|
|
86
|
+
> - **USDC**: A stablecoin pegged to US dollar (1 USDC ≈ 1 USD)
|
|
87
|
+
>
|
|
88
|
+
> How to get test coins:
|
|
89
|
+
> 1. Visit the Base Sepolia faucet: https://sepoliafaucet.com/
|
|
90
|
+
> 2. Enter your wallet address
|
|
91
|
+
> 3. Get free test USDC
|
|
92
|
+
>
|
|
93
|
+
> Security tips:
|
|
94
|
+
> - Never share your private key
|
|
95
|
+
> - Test thoroughly on testnet before using mainnet
|
|
96
|
+
> - Use a dedicated wallet, don't store large amounts of funds
|
|
97
|
+
>
|
|
98
|
+
> For more blockchain basics, refer to: https://www.wtf.academy/zh/course/ethers101/HelloVitalik
|
|
117
99
|
|
|
118
|
-
|
|
119
|
-
```json
|
|
120
|
-
{}
|
|
121
|
-
```
|
|
100
|
+
### opencode autopay command loading success
|
|
122
101
|
|
|
123
|
-
|
|
124
|
-
- Complete guide with all tools and commands
|
|
125
|
-
- Pricing information
|
|
126
|
-
- Network configuration
|
|
102
|
+

|
|
127
103
|
|
|
128
|
-
|
|
104
|
+
## MCP Tools Usage
|
|
129
105
|
|
|
130
|
-
|
|
106
|
+
### Quick Commands Configuration
|
|
131
107
|
|
|
132
|
-
|
|
133
|
-
```json
|
|
134
|
-
{}
|
|
135
|
-
```
|
|
108
|
+
Add these shortcuts to your `opencode.json` for faster access:
|
|
136
109
|
|
|
137
|
-
**Returns:**
|
|
138
110
|
```json
|
|
139
111
|
{
|
|
140
|
-
"
|
|
141
|
-
"
|
|
142
|
-
"
|
|
143
|
-
|
|
144
|
-
|
|
112
|
+
"$schema": "https://opencode.ai/config.json",
|
|
113
|
+
"mcp": {
|
|
114
|
+
"autopay": {
|
|
115
|
+
"type": "local",
|
|
116
|
+
"command": ["npx", "-y", "@newblock/iautopay-mcp"],
|
|
117
|
+
"enabled": true,
|
|
118
|
+
"environment": {
|
|
119
|
+
"BUYER_PRIVATE_KEY": "0xEVM_wallet_private_key"
|
|
120
|
+
}
|
|
121
|
+
}
|
|
145
122
|
},
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
123
|
+
"command": {
|
|
124
|
+
"autopay_buy_apikey_1day": {
|
|
125
|
+
"template": "Use buy_apikey tool to buy 1-day API Key, params: {\"duration\": 1}",
|
|
126
|
+
"description": "Buy 1-day API Key (0.09 USDC)"
|
|
127
|
+
},
|
|
128
|
+
"autopay_buy_apikey_7days": {
|
|
129
|
+
"template": "Use buy_apikey tool to buy 7-day API Key, params: {\"duration\": 7}",
|
|
130
|
+
"description": "Buy 7-day API Key (0.49 USDC)"
|
|
131
|
+
},
|
|
132
|
+
"autopay_get_info": {
|
|
133
|
+
"template": "Use info tool to get server information (API Key stock, prices, network config)",
|
|
134
|
+
"description": "Get iAutoPay server information"
|
|
135
|
+
},
|
|
136
|
+
"autopay_guide": {
|
|
137
|
+
"template": "Use guide tool to show iAutoPay usage guide",
|
|
138
|
+
"description": "Show iAutoPay usage guide"
|
|
139
|
+
}
|
|
149
140
|
}
|
|
150
141
|
}
|
|
151
142
|
```
|
|
152
143
|
|
|
153
|
-
###
|
|
144
|
+
### Opencode Quick Commands Usage Examples
|
|
154
145
|
|
|
155
|
-
|
|
146
|
+
### 1: /autopay_guide
|
|
156
147
|
|
|
157
|
-
|
|
158
|
-
```json
|
|
159
|
-
{
|
|
160
|
-
"duration": 1
|
|
161
|
-
}
|
|
162
|
-
```
|
|
148
|
+
Output:
|
|
163
149
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
-
|
|
167
|
-
-
|
|
150
|
+
iAutoPay Usage Guide
|
|
151
|
+
Available Tools
|
|
152
|
+
- guide - Display complete usage guide
|
|
153
|
+
- info - Get server information (stock, prices, network config)
|
|
154
|
+
- buy_apikey - Buy API key (supports 1/7/30 day durations)
|
|
155
|
+
- refresh_pricing - Refresh prices from server
|
|
156
|
+
Quick Commands
|
|
157
|
+
- autopay_buy_apikey_1day - Buy 1-day API Key (0.1 USDC)
|
|
158
|
+
- autopay_buy_apikey_7days - Buy 7-day API Key (0.9 USDC)
|
|
159
|
+
- autopay_get_info - Quick get server information
|
|
160
|
+
Network Information
|
|
161
|
+
- Testnet: Base Sepolia (84532)
|
|
162
|
+
- Mainnet: Base Mainnet (8453)
|
|
163
|
+
- Current Network: Base Sepolia (84532)
|
|
164
|
+
Pricing
|
|
165
|
+
- 1 day: 0.1 USDC
|
|
166
|
+
- 7 days: 0.9 USDC
|
|
167
|
+
Environment: dev
|
|
168
168
|
|
|
169
|
-
|
|
170
|
-
```json
|
|
171
|
-
{
|
|
172
|
-
"apiKey": "sk-ABCD12345678901234567890",
|
|
173
|
-
"txHash": "0x4d757c7e121ad31607ee1e9c5af65bfe13b82c112fcf077638814c031ecc3a6b",
|
|
174
|
-
"payState": "paid",
|
|
175
|
-
"price": "0.09 USDC",
|
|
176
|
-
"deductedAmount": "0.09 USDC",
|
|
177
|
-
"currentBalance": "9.91 USDC"
|
|
178
|
-
}
|
|
179
|
-
```
|
|
169
|
+
### 2: /autopay_buy_apikey_1day
|
|
180
170
|
|
|
181
|
-
|
|
171
|
+
Output:
|
|
182
172
|
|
|
183
|
-
|
|
173
|
+
Purchasing 1-day API Key...
|
|
174
|
+
Price: 0.09 USDC
|
|
175
|
+
Processing payment...
|
|
176
|
+
Transaction hash: 0xghi789...
|
|
177
|
+
Purchase successful!
|
|
184
178
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
{
|
|
188
|
-
"to": "0x1234567890123456789012345678901234567890",
|
|
189
|
-
"amount": "100000"
|
|
190
|
-
}
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
**Amount is in smallest units** (e.g., 100000 = 0.1 USDC, 1000000 = 1 USDC)
|
|
194
|
-
|
|
195
|
-
**Returns:**
|
|
196
|
-
```json
|
|
197
|
-
{
|
|
198
|
-
"from": "0x...",
|
|
199
|
-
"to": "0x...",
|
|
200
|
-
"amount": "0.1 USDC",
|
|
201
|
-
"txHash": "0x...",
|
|
202
|
-
"deductedAmount": "0.1 USDC",
|
|
203
|
-
"currentBalance": "9.9 USDC"
|
|
204
|
-
}
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
### sync_opencode_config
|
|
208
|
-
|
|
209
|
-
Auto-configure opencode.json with quick commands (autopay_toA, autopay_toB, etc.).
|
|
210
|
-
|
|
211
|
-
**Parameters:**
|
|
212
|
-
```json
|
|
213
|
-
{}
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
**Returns:**
|
|
217
|
-
```json
|
|
218
|
-
{
|
|
219
|
-
"message": "✅ 已添加 7 个命令到 opencode.json"
|
|
220
|
-
}
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
### refresh_pricing
|
|
224
|
-
|
|
225
|
-
Refresh pricing from API. Use this if prices are changed on the server.
|
|
179
|
+
Your API Key: sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
180
|
+
Valid for: 1 day
|
|
226
181
|
|
|
227
|
-
|
|
228
|
-
```json
|
|
229
|
-
{}
|
|
230
|
-
```
|
|
231
|
-
|
|
232
|
-
**Returns:**
|
|
233
|
-
```json
|
|
234
|
-
{
|
|
235
|
-
"1day": "0.09 USDC",
|
|
236
|
-
"7days": "0.49 USDC",
|
|
237
|
-
"30days": "0.99 USDC"
|
|
238
|
-
}
|
|
239
|
-
```
|
|
240
|
-
|
|
241
|
-
## Environment Configuration
|
|
242
|
-
|
|
243
|
-
The MCP server supports two environments configured in `src/server.ts`:
|
|
244
|
-
|
|
245
|
-
### Development (Base Sepolia)
|
|
246
|
-
- Chain ID: 84532
|
|
247
|
-
- RPC URL: https://sepolia.base.org
|
|
248
|
-
- USDC Address: 0x036CbD53842c5426634e7929541eC2318f3dCF7e
|
|
249
|
-
- Token Name: "USDC"
|
|
182
|
+
👉 Check [LLM Services Guide](doc/LLM_SERVICES_GUIDE.md) to learn how to use the API Key to access supported models
|
|
250
183
|
|
|
251
|
-
### Production (Base Mainnet)
|
|
252
|
-
- Chain ID: 8453
|
|
253
|
-
- RPC URL: https://mainnet.base.org
|
|
254
|
-
- USDC Address: 0x833589fcd6edb6e08f4c7c32d4f71b54bda02913
|
|
255
|
-
- Token Name: "USD Coin"
|
|
256
|
-
|
|
257
|
-
## Example Workflows
|
|
258
|
-
|
|
259
|
-
### Example 1: Purchase GLM4.7 API Key
|
|
260
|
-
|
|
261
|
-
```
|
|
262
|
-
Use info tool to check stock and pricing
|
|
263
|
-
Use buy_apikey tool with duration: 1
|
|
264
|
-
Receive API key in response
|
|
265
|
-
```
|
|
266
|
-
|
|
267
|
-
### Example 2: Direct USDC Payment
|
|
268
|
-
|
|
269
|
-
```
|
|
270
|
-
Use pay_stablecoin tool with to: "0x1a85156c2943b63febeee7883bd84a7d1cf0da0c" and amount: "10000"
|
|
271
|
-
Transaction executes automatically
|
|
272
|
-
Receive transaction hash
|
|
273
|
-
```
|
|
274
184
|
|
|
275
185
|
## License
|
|
276
186
|
|
|
@@ -286,5 +196,6 @@ For issues and questions, please use the [GitHub Issues](https://github.com/newb
|
|
|
286
196
|
|
|
287
197
|
## Documentation
|
|
288
198
|
|
|
199
|
+
- [LLM Services Guide](doc/LLM_SERVICES_GUIDE.md) - Supported models and API Key purchase guide
|
|
289
200
|
- [CLAUDE_CLI_MCP_SETUP.md](CLAUDE_CLI_MCP_SETUP.md) - Claude CLI integration guide
|
|
290
201
|
- [mcp-config.json.example](mcp-config.json.example) - MCP configuration template
|
package/dist/iautopay-mcp.js
CHANGED
|
@@ -53,10 +53,10 @@ const paymentRequirementsSchema = z.object({
|
|
|
53
53
|
price: z.string().min(1),
|
|
54
54
|
payee: z.string().optional(),
|
|
55
55
|
});
|
|
56
|
-
const payStablecoinInput = z.object({
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
});
|
|
56
|
+
// const payStablecoinInput = z.object({
|
|
57
|
+
// to: z.string().min(1),
|
|
58
|
+
// amount: z.string().min(1),
|
|
59
|
+
// });
|
|
60
60
|
const buyApikeyInput = z.object({
|
|
61
61
|
duration: z.number().optional().refine(val => !val || [1, 7, 30].includes(val), {
|
|
62
62
|
message: "Duration must be 1, 7, or 30 days"
|
|
@@ -64,7 +64,7 @@ const buyApikeyInput = z.object({
|
|
|
64
64
|
});
|
|
65
65
|
const getInfoInput = z.object({});
|
|
66
66
|
const guideInput = z.object({});
|
|
67
|
-
const syncOpencodeConfigInput = z.object({});
|
|
67
|
+
// const syncOpencodeConfigInput = z.object({});
|
|
68
68
|
const refreshPricingInput = z.object({});
|
|
69
69
|
// ============================================================================
|
|
70
70
|
// Dynamic Pricing from Fact API
|
|
@@ -326,16 +326,16 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
326
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
327
|
inputSchema: zodToJsonSchema(buyApikeyInput),
|
|
328
328
|
},
|
|
329
|
-
{
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
},
|
|
334
|
-
{
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
},
|
|
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
339
|
{
|
|
340
340
|
name: "refresh_pricing",
|
|
341
341
|
description: "Refresh pricing from API. Use this if prices are changed on the server.",
|
|
@@ -346,21 +346,20 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
346
346
|
});
|
|
347
347
|
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
348
348
|
const { name, arguments: args } = request.params;
|
|
349
|
-
if (name === "pay_stablecoin") {
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
}
|
|
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 as string) === 'dev',
|
|
357
|
+
// });
|
|
358
|
+
// return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
359
|
+
// } catch (error) {
|
|
360
|
+
// throw error;
|
|
361
|
+
// }
|
|
362
|
+
// }
|
|
364
363
|
if (name === "buy_apikey") {
|
|
365
364
|
const parsed = buyApikeyInput.parse(args);
|
|
366
365
|
const duration = parsed.duration || 1;
|
|
@@ -452,13 +451,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
452
451
|
{ name: "guide", description: "显示完整使用指南" },
|
|
453
452
|
{ name: "info", description: "获取服务器信息(库存、价格、网络配置)" },
|
|
454
453
|
{ name: "buy_apikey", description: "购买 API key(支持1/7/30天时长)" },
|
|
455
|
-
{ name: "pay_stablecoin", description: "支付稳定币到指定地址" },
|
|
456
|
-
{ name: "sync_opencode_config", description: "自动配置 opencode.json 快捷命令" },
|
|
457
454
|
{ name: "refresh_pricing", description: "从服务器刷新价格" }
|
|
458
455
|
],
|
|
459
456
|
commands: [
|
|
460
|
-
{ name: "autopay_toA", description: "快速支付 0.01 USDC" },
|
|
461
|
-
{ name: "autopay_toB", description: "支付 0.05 USDC(需确认)" },
|
|
462
457
|
{ name: "autopay_buy_apikey_1day", description: `购买1天API Key(${pricing["1day"]})` },
|
|
463
458
|
{ name: "autopay_buy_apikey_7days", description: `购买7天API Key(${pricing["7days"]})` },
|
|
464
459
|
{ name: "autopay_buy_apikey_30days", description: `购买30天API Key(${pricing["30days"]})` },
|
|
@@ -474,80 +469,78 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
474
469
|
};
|
|
475
470
|
return { content: [{ type: "text", text: JSON.stringify(toolsData, null, 2) }] };
|
|
476
471
|
}
|
|
477
|
-
if (name === "sync_opencode_config") {
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
}
|
|
550
|
-
}
|
|
472
|
+
// if (name === "sync_opencode_config") {
|
|
473
|
+
// const parsed = syncOpencodeConfigInput.parse(args);
|
|
474
|
+
// try {
|
|
475
|
+
// const fs = await import('fs/promises');
|
|
476
|
+
// const opencodePath = '/Users/michael/opc/proj/iautopay/opencode.json';
|
|
477
|
+
// const opencodeData = JSON.parse(await fs.readFile(opencodePath, 'utf-8'));
|
|
478
|
+
// const pricing = CACHED_PRICING || {
|
|
479
|
+
// "1day": "0.09 USDC",
|
|
480
|
+
// "7days": "0.49 USDC",
|
|
481
|
+
// "30days": "0.99 USDC"
|
|
482
|
+
// };
|
|
483
|
+
// const requiredCommands = {
|
|
484
|
+
// "autopay_toA": {
|
|
485
|
+
// "template": "使用 pay_stablecoin 工具向 0x1a85156c2943b63febeee7883bd84a7d1cf0da0c 支付 0.01 USDC,参数为:to=\"0x1a85156c2943b63febeee7883bd84a7d1cf0da0c\", amount=\"10000\"",
|
|
486
|
+
// "description": "支付0.01 USDC给A账户"
|
|
487
|
+
// },
|
|
488
|
+
// "autopay_toB": {
|
|
489
|
+
// "template": "首先使用 question 工具询问用户确认,选项包括:1) 确认(继续支付),2) 取消(不进行支付)。显示支付详情:向 0x1a85156c2943b63febeee7883bd84a7d1cf0da0c 支付 0.05 USDC,参数为:to=\"0x1a85156c2943b63febeee7883bd84a7d1cf0da0c\", amount=\"50000\"。只有用户选择确认时才继续支付。",
|
|
490
|
+
// "description": "支付0.05 USDC给A账户(需要确认)"
|
|
491
|
+
// },
|
|
492
|
+
// "autopay_buy_apikey_1day": {
|
|
493
|
+
// "template": "使用 buy_apikey 工具购买1天API Key,参数为:{\"duration\": 1}",
|
|
494
|
+
// "description": `购买1天API Key(${pricing["1day"]})`
|
|
495
|
+
// },
|
|
496
|
+
// "autopay_buy_apikey_7days": {
|
|
497
|
+
// "template": "使用 buy_apikey 工具购买7天API Key,参数为:{\"duration\": 7}",
|
|
498
|
+
// "description": `购买7天API Key(${pricing["7days"]})`
|
|
499
|
+
// },
|
|
500
|
+
// "autopay_buy_apikey_30days": {
|
|
501
|
+
// "template": "使用 buy_apikey 工具购买30天API Key,参数为:{\"duration\": 30}",
|
|
502
|
+
// "description": `购买30天API Key(${pricing["30days"]})`
|
|
503
|
+
// },
|
|
504
|
+
// "autopay_get_info": {
|
|
505
|
+
// "template": "使用 info 工具获取服务器信息(API Key 库存、价格、网络配置)",
|
|
506
|
+
// "description": "获取iAutoPay服务器信息"
|
|
507
|
+
// },
|
|
508
|
+
// "autopay_guide": {
|
|
509
|
+
// "template": "使用 guide 工具显示 iAutoPay 使用指南",
|
|
510
|
+
// "description": "显示iAutoPay使用指南"
|
|
511
|
+
// }
|
|
512
|
+
// };
|
|
513
|
+
// let addedCommands: string[] = [];
|
|
514
|
+
// let updatedCommands: string[] = [];
|
|
515
|
+
// if (!opencodeData.command) {
|
|
516
|
+
// opencodeData.command = {};
|
|
517
|
+
// }
|
|
518
|
+
// for (const [key, value] of Object.entries(requiredCommands)) {
|
|
519
|
+
// if (!opencodeData.command[key]) {
|
|
520
|
+
// opencodeData.command[key] = value;
|
|
521
|
+
// addedCommands.push(key);
|
|
522
|
+
// }
|
|
523
|
+
// }
|
|
524
|
+
// if (addedCommands.length > 0) {
|
|
525
|
+
// await fs.writeFile(opencodePath, JSON.stringify(opencodeData, null, 2), 'utf-8');
|
|
526
|
+
// return {
|
|
527
|
+
// content: [{
|
|
528
|
+
// type: "text",
|
|
529
|
+
// text: `✅ 已添加 ${addedCommands.length} 个命令到 opencode.json:\n${addedCommands.map(c => ` - ${c}`).join('\n')}`
|
|
530
|
+
// }]
|
|
531
|
+
// };
|
|
532
|
+
// } else {
|
|
533
|
+
// return {
|
|
534
|
+
// content: [{
|
|
535
|
+
// type: "text",
|
|
536
|
+
// text: "✅ 所有 autopay_ 命令已存在,无需更新"
|
|
537
|
+
// }]
|
|
538
|
+
// };
|
|
539
|
+
// }
|
|
540
|
+
// } catch (error) {
|
|
541
|
+
// throw new Error(`同步配置失败: ${error}`);
|
|
542
|
+
// }
|
|
543
|
+
// }
|
|
551
544
|
if (name === "refresh_pricing") {
|
|
552
545
|
const parsed = refreshPricingInput.parse(args);
|
|
553
546
|
try {
|
package/mcp-config.json.example
CHANGED
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);
|