@agent-pay/mcp 2.0.0
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 +91 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +689 -0
- package/dist/cli.js.map +1 -0
- package/dist/gateway-client.d.ts +26 -0
- package/dist/gateway-client.js +91 -0
- package/dist/gateway-client.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +54 -0
- package/dist/index.js.map +1 -0
- package/dist/tools/check-deployment.d.ts +3 -0
- package/dist/tools/check-deployment.js +73 -0
- package/dist/tools/check-deployment.js.map +1 -0
- package/dist/tools/get-compute-quote.d.ts +4 -0
- package/dist/tools/get-compute-quote.js +90 -0
- package/dist/tools/get-compute-quote.js.map +1 -0
- package/dist/tools/list-deployments.d.ts +3 -0
- package/dist/tools/list-deployments.js +65 -0
- package/dist/tools/list-deployments.js.map +1 -0
- package/dist/tools/provision-compute.d.ts +4 -0
- package/dist/tools/provision-compute.js +251 -0
- package/dist/tools/provision-compute.js.map +1 -0
- package/dist/tools/stop-deployment.d.ts +3 -0
- package/dist/tools/stop-deployment.js +42 -0
- package/dist/tools/stop-deployment.js.map +1 -0
- package/dist/types.d.ts +227 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/wallet.d.ts +27 -0
- package/dist/wallet.js +119 -0
- package/dist/wallet.js.map +1 -0
- package/package.json +51 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,689 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { SignClient } from "@walletconnect/sign-client";
|
|
3
|
+
import QRCode from "qrcode-terminal";
|
|
4
|
+
import { encodeFunctionData, parseUnits, createPublicClient, http } from "viem";
|
|
5
|
+
import { base, baseSepolia } from "viem/chains";
|
|
6
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
|
|
7
|
+
import { homedir } from "os";
|
|
8
|
+
import { join } from "path";
|
|
9
|
+
// Configuration
|
|
10
|
+
const WALLETCONNECT_PROJECT_ID = process.env.WALLETCONNECT_PROJECT_ID || "7195fdf3f03fb2c3e50485e0821196d7";
|
|
11
|
+
const GATEWAY_URL = process.env.AGENT_PAY_GATEWAY_URL || "https://gateway.agentpay.dev";
|
|
12
|
+
const NETWORK = process.env.AGENT_PAY_NETWORK || "base";
|
|
13
|
+
// USDC addresses
|
|
14
|
+
const USDC_ADDRESSES = {
|
|
15
|
+
"base-sepolia": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
|
|
16
|
+
"base": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
17
|
+
};
|
|
18
|
+
// Chain configs
|
|
19
|
+
const CHAINS = {
|
|
20
|
+
"base-sepolia": baseSepolia,
|
|
21
|
+
"base": base,
|
|
22
|
+
};
|
|
23
|
+
// Chain IDs for WalletConnect
|
|
24
|
+
const CHAIN_IDS = {
|
|
25
|
+
"base-sepolia": "eip155:84532",
|
|
26
|
+
"base": "eip155:8453",
|
|
27
|
+
};
|
|
28
|
+
// ERC20 approve function ABI
|
|
29
|
+
const APPROVE_ABI = [
|
|
30
|
+
{
|
|
31
|
+
name: "approve",
|
|
32
|
+
type: "function",
|
|
33
|
+
stateMutability: "nonpayable",
|
|
34
|
+
inputs: [
|
|
35
|
+
{ name: "spender", type: "address" },
|
|
36
|
+
{ name: "amount", type: "uint256" },
|
|
37
|
+
],
|
|
38
|
+
outputs: [{ name: "", type: "bool" }],
|
|
39
|
+
},
|
|
40
|
+
];
|
|
41
|
+
// Escrow contract deposit ABI
|
|
42
|
+
const ESCROW_DEPOSIT_ABI = [
|
|
43
|
+
{
|
|
44
|
+
name: "deposit",
|
|
45
|
+
type: "function",
|
|
46
|
+
stateMutability: "nonpayable",
|
|
47
|
+
inputs: [
|
|
48
|
+
{ name: "specsHash", type: "bytes32" },
|
|
49
|
+
{ name: "quotedAmount", type: "uint256" },
|
|
50
|
+
{ name: "depositAmount", type: "uint256" },
|
|
51
|
+
],
|
|
52
|
+
outputs: [{ name: "escrowId", type: "bytes32" }],
|
|
53
|
+
},
|
|
54
|
+
];
|
|
55
|
+
// Escrow contract read ABI
|
|
56
|
+
const ESCROW_READ_ABI = [
|
|
57
|
+
{
|
|
58
|
+
name: "escrows",
|
|
59
|
+
type: "function",
|
|
60
|
+
stateMutability: "view",
|
|
61
|
+
inputs: [{ name: "", type: "bytes32" }],
|
|
62
|
+
outputs: [
|
|
63
|
+
{ name: "user", type: "address" },
|
|
64
|
+
{ name: "depositAmount", type: "uint256" },
|
|
65
|
+
{ name: "quotedAmount", type: "uint256" },
|
|
66
|
+
{ name: "specsHash", type: "bytes32" },
|
|
67
|
+
{ name: "createdAt", type: "uint256" },
|
|
68
|
+
{ name: "status", type: "uint8" },
|
|
69
|
+
{ name: "akashDseq", type: "string" },
|
|
70
|
+
{ name: "akashProvider", type: "string" },
|
|
71
|
+
{ name: "actualCost", type: "uint256" },
|
|
72
|
+
],
|
|
73
|
+
},
|
|
74
|
+
];
|
|
75
|
+
// Config file path
|
|
76
|
+
const CONFIG_DIR = join(homedir(), ".agent-pay");
|
|
77
|
+
const CONFIG_PATH = join(CONFIG_DIR, "config.json");
|
|
78
|
+
function loadConfig() {
|
|
79
|
+
if (!existsSync(CONFIG_PATH))
|
|
80
|
+
return null;
|
|
81
|
+
try {
|
|
82
|
+
return JSON.parse(readFileSync(CONFIG_PATH, "utf-8"));
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function saveConfig(config) {
|
|
89
|
+
if (!existsSync(CONFIG_DIR)) {
|
|
90
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
91
|
+
}
|
|
92
|
+
writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
|
|
93
|
+
}
|
|
94
|
+
async function initWalletConnect() {
|
|
95
|
+
try {
|
|
96
|
+
return await SignClient.init({
|
|
97
|
+
projectId: WALLETCONNECT_PROJECT_ID,
|
|
98
|
+
metadata: {
|
|
99
|
+
name: "Agent-Pay",
|
|
100
|
+
description: "Pay for compute with USDC",
|
|
101
|
+
url: "https://agentpay.dev",
|
|
102
|
+
icons: ["https://agentpay.dev/icon.png"],
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
console.error("Failed to initialize WalletConnect:", error);
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
async function connectWallet(signClient) {
|
|
112
|
+
const chainId = CHAIN_IDS[NETWORK] || CHAIN_IDS["base"];
|
|
113
|
+
const { uri, approval } = await signClient.connect({
|
|
114
|
+
requiredNamespaces: {
|
|
115
|
+
eip155: {
|
|
116
|
+
methods: ["eth_sendTransaction", "personal_sign"],
|
|
117
|
+
chains: [chainId],
|
|
118
|
+
events: ["accountsChanged", "chainChanged"],
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
if (!uri) {
|
|
123
|
+
console.error("Failed to generate WalletConnect URI");
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
|
126
|
+
console.log("Scan this QR code with your mobile wallet:\n");
|
|
127
|
+
console.log("(MetaMask, Coinbase Wallet, Rainbow, Trust Wallet, etc.)\n");
|
|
128
|
+
QRCode.generate(uri, { small: true }, (qr) => {
|
|
129
|
+
console.log(qr);
|
|
130
|
+
});
|
|
131
|
+
console.log("\nWaiting for wallet connection...\n");
|
|
132
|
+
let session;
|
|
133
|
+
try {
|
|
134
|
+
session = await approval();
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
console.error("\nWallet connection was rejected or timed out.");
|
|
138
|
+
process.exit(1);
|
|
139
|
+
}
|
|
140
|
+
const accounts = session.namespaces.eip155?.accounts || [];
|
|
141
|
+
if (accounts.length === 0) {
|
|
142
|
+
console.error("No accounts found in wallet session");
|
|
143
|
+
process.exit(1);
|
|
144
|
+
}
|
|
145
|
+
const walletAddress = accounts[0].split(":")[2];
|
|
146
|
+
return { session, walletAddress };
|
|
147
|
+
}
|
|
148
|
+
// ============================================================================
|
|
149
|
+
// SETUP COMMAND
|
|
150
|
+
// ============================================================================
|
|
151
|
+
async function setup() {
|
|
152
|
+
console.log(`
|
|
153
|
+
╔═══════════════════════════════════════════════════════════════╗
|
|
154
|
+
║ Agent-Pay Setup ║
|
|
155
|
+
╚═══════════════════════════════════════════════════════════════╝
|
|
156
|
+
`);
|
|
157
|
+
console.log("Initializing WalletConnect...\n");
|
|
158
|
+
const signClient = await initWalletConnect();
|
|
159
|
+
const { session, walletAddress } = await connectWallet(signClient);
|
|
160
|
+
console.log(`✓ Connected: ${walletAddress}`);
|
|
161
|
+
console.log(` Network: ${NETWORK}\n`);
|
|
162
|
+
// Register with gateway
|
|
163
|
+
console.log("Registering with gateway...\n");
|
|
164
|
+
let token;
|
|
165
|
+
try {
|
|
166
|
+
const response = await fetch(`${GATEWAY_URL}/auth/register`, {
|
|
167
|
+
method: "POST",
|
|
168
|
+
headers: { "Content-Type": "application/json" },
|
|
169
|
+
body: JSON.stringify({ walletAddress }),
|
|
170
|
+
});
|
|
171
|
+
if (!response.ok) {
|
|
172
|
+
throw new Error(`Registration failed: ${response.statusText}`);
|
|
173
|
+
}
|
|
174
|
+
const result = await response.json();
|
|
175
|
+
token = result.token;
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
console.error("Failed to register with gateway:", error);
|
|
179
|
+
process.exit(1);
|
|
180
|
+
}
|
|
181
|
+
console.log("✓ Registered with gateway\n");
|
|
182
|
+
// Save config
|
|
183
|
+
const config = {
|
|
184
|
+
walletAddress,
|
|
185
|
+
token,
|
|
186
|
+
gatewayUrl: GATEWAY_URL,
|
|
187
|
+
network: NETWORK,
|
|
188
|
+
};
|
|
189
|
+
saveConfig(config);
|
|
190
|
+
// Update Claude settings
|
|
191
|
+
const claudeConfigDir = join(homedir(), ".claude");
|
|
192
|
+
const settingsPath = join(claudeConfigDir, "settings.local.json");
|
|
193
|
+
if (!existsSync(claudeConfigDir)) {
|
|
194
|
+
mkdirSync(claudeConfigDir, { recursive: true });
|
|
195
|
+
}
|
|
196
|
+
let settings = {};
|
|
197
|
+
if (existsSync(settingsPath)) {
|
|
198
|
+
try {
|
|
199
|
+
settings = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
|
200
|
+
}
|
|
201
|
+
catch {
|
|
202
|
+
settings = {};
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
const mcpServers = settings.mcpServers || {};
|
|
206
|
+
mcpServers["agent-pay"] = {
|
|
207
|
+
command: "npx",
|
|
208
|
+
args: ["@agent-pay/mcp"],
|
|
209
|
+
env: {
|
|
210
|
+
AGENT_PAY_TOKEN: token,
|
|
211
|
+
AGENT_PAY_GATEWAY_URL: GATEWAY_URL,
|
|
212
|
+
AGENT_PAY_NETWORK: NETWORK,
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
settings.mcpServers = mcpServers;
|
|
216
|
+
writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
217
|
+
console.log(`✓ Configuration saved\n`);
|
|
218
|
+
// Disconnect WalletConnect
|
|
219
|
+
await signClient.disconnect({
|
|
220
|
+
topic: session.topic,
|
|
221
|
+
reason: { code: 6000, message: "Setup complete" },
|
|
222
|
+
});
|
|
223
|
+
console.log(`
|
|
224
|
+
╔═══════════════════════════════════════════════════════════════╗
|
|
225
|
+
║ Setup Complete! ║
|
|
226
|
+
╚═══════════════════════════════════════════════════════════════╝
|
|
227
|
+
|
|
228
|
+
Your wallet: ${walletAddress}
|
|
229
|
+
Network: ${NETWORK}
|
|
230
|
+
Gateway: ${GATEWAY_URL}
|
|
231
|
+
|
|
232
|
+
You can now use agent-pay in Claude Code:
|
|
233
|
+
|
|
234
|
+
"provision a GPU server for ML training"
|
|
235
|
+
"get me a compute quote for 2 CPUs and 4GB RAM"
|
|
236
|
+
|
|
237
|
+
When you provision compute, you'll be asked to approve the exact
|
|
238
|
+
amount needed. No pre-approval required!
|
|
239
|
+
`);
|
|
240
|
+
process.exit(0);
|
|
241
|
+
}
|
|
242
|
+
// ============================================================================
|
|
243
|
+
// APPROVE COMMAND
|
|
244
|
+
// ============================================================================
|
|
245
|
+
async function approve(amountArg) {
|
|
246
|
+
const config = loadConfig();
|
|
247
|
+
if (!config) {
|
|
248
|
+
console.error("Not set up yet. Run: npx @agent-pay/mcp setup");
|
|
249
|
+
process.exit(1);
|
|
250
|
+
}
|
|
251
|
+
// Parse amount
|
|
252
|
+
const amount = parseFloat(amountArg || "100");
|
|
253
|
+
if (isNaN(amount) || amount <= 0) {
|
|
254
|
+
console.error("Invalid amount. Usage: npx @agent-pay/mcp approve <amount>");
|
|
255
|
+
console.error("Example: npx @agent-pay/mcp approve 50");
|
|
256
|
+
process.exit(1);
|
|
257
|
+
}
|
|
258
|
+
console.log(`
|
|
259
|
+
╔═══════════════════════════════════════════════════════════════╗
|
|
260
|
+
║ Approve USDC Spending ║
|
|
261
|
+
╚═══════════════════════════════════════════════════════════════╝
|
|
262
|
+
`);
|
|
263
|
+
console.log(`Amount: $${amount.toFixed(2)} USDC`);
|
|
264
|
+
console.log(`Wallet: ${config.walletAddress}\n`);
|
|
265
|
+
console.log("This approval allows Agent-Pay to charge your wallet");
|
|
266
|
+
console.log("for compute deployments, up to the amount you specify.");
|
|
267
|
+
console.log("You can revoke this anytime via your wallet or revoke.cash\n");
|
|
268
|
+
// Get gateway address
|
|
269
|
+
let gatewayAddress;
|
|
270
|
+
try {
|
|
271
|
+
const response = await fetch(`${config.gatewayUrl}/`);
|
|
272
|
+
const info = await response.json();
|
|
273
|
+
gatewayAddress = (info.payment?.gatewayAddress || "");
|
|
274
|
+
if (!gatewayAddress || gatewayAddress === "0x0000000000000000000000000000000000000000") {
|
|
275
|
+
throw new Error("Gateway address not configured");
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
catch (error) {
|
|
279
|
+
console.error("Failed to fetch gateway info:", error);
|
|
280
|
+
process.exit(1);
|
|
281
|
+
}
|
|
282
|
+
console.log("Initializing WalletConnect...\n");
|
|
283
|
+
const signClient = await initWalletConnect();
|
|
284
|
+
const { session, walletAddress } = await connectWallet(signClient);
|
|
285
|
+
if (walletAddress.toLowerCase() !== config.walletAddress.toLowerCase()) {
|
|
286
|
+
console.error(`\nWallet mismatch!`);
|
|
287
|
+
console.error(` Expected: ${config.walletAddress}`);
|
|
288
|
+
console.error(` Got: ${walletAddress}`);
|
|
289
|
+
console.error(`\nPlease connect the same wallet you used during setup.`);
|
|
290
|
+
process.exit(1);
|
|
291
|
+
}
|
|
292
|
+
console.log(`✓ Connected: ${walletAddress}\n`);
|
|
293
|
+
// Request approval
|
|
294
|
+
console.log("Requesting approval...\n");
|
|
295
|
+
console.log("┌─────────────────────────────────────────────────────────────┐");
|
|
296
|
+
console.log("│ Check your wallet app to approve the transaction. │");
|
|
297
|
+
console.log("│ │");
|
|
298
|
+
console.log(`│ Amount: $${amount.toFixed(2)} USDC`);
|
|
299
|
+
console.log("│ This is your spending limit, not an immediate charge. │");
|
|
300
|
+
console.log("└─────────────────────────────────────────────────────────────┘\n");
|
|
301
|
+
const chainId = CHAIN_IDS[config.network] || CHAIN_IDS["base"];
|
|
302
|
+
const usdcAddress = USDC_ADDRESSES[config.network] || USDC_ADDRESSES["base"];
|
|
303
|
+
const approvalAmount = parseUnits(amount.toString(), 6);
|
|
304
|
+
const approveData = encodeFunctionData({
|
|
305
|
+
abi: APPROVE_ABI,
|
|
306
|
+
functionName: "approve",
|
|
307
|
+
args: [gatewayAddress, approvalAmount],
|
|
308
|
+
});
|
|
309
|
+
try {
|
|
310
|
+
const txHash = await signClient.request({
|
|
311
|
+
topic: session.topic,
|
|
312
|
+
chainId,
|
|
313
|
+
request: {
|
|
314
|
+
method: "eth_sendTransaction",
|
|
315
|
+
params: [
|
|
316
|
+
{
|
|
317
|
+
from: walletAddress,
|
|
318
|
+
to: usdcAddress,
|
|
319
|
+
data: approveData,
|
|
320
|
+
},
|
|
321
|
+
],
|
|
322
|
+
},
|
|
323
|
+
});
|
|
324
|
+
console.log(`✓ Approval submitted!`);
|
|
325
|
+
console.log(` Transaction: ${txHash}\n`);
|
|
326
|
+
console.log("Waiting for confirmation...\n");
|
|
327
|
+
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
328
|
+
console.log(`
|
|
329
|
+
╔═══════════════════════════════════════════════════════════════╗
|
|
330
|
+
║ Approval Complete! ║
|
|
331
|
+
╚═══════════════════════════════════════════════════════════════╝
|
|
332
|
+
|
|
333
|
+
You've approved up to $${amount.toFixed(2)} USDC for compute spending.
|
|
334
|
+
|
|
335
|
+
Go back to Claude Code and try provisioning compute again!
|
|
336
|
+
`);
|
|
337
|
+
}
|
|
338
|
+
catch (error) {
|
|
339
|
+
console.error("\nApproval was rejected or failed.");
|
|
340
|
+
process.exit(1);
|
|
341
|
+
}
|
|
342
|
+
await signClient.disconnect({
|
|
343
|
+
topic: session.topic,
|
|
344
|
+
reason: { code: 6000, message: "Approval complete" },
|
|
345
|
+
});
|
|
346
|
+
process.exit(0);
|
|
347
|
+
}
|
|
348
|
+
// ============================================================================
|
|
349
|
+
// STATUS COMMAND
|
|
350
|
+
// ============================================================================
|
|
351
|
+
async function status() {
|
|
352
|
+
const config = loadConfig();
|
|
353
|
+
if (!config) {
|
|
354
|
+
console.error("Not set up yet. Run: npx @agent-pay/mcp setup");
|
|
355
|
+
process.exit(1);
|
|
356
|
+
}
|
|
357
|
+
console.log(`
|
|
358
|
+
╔═══════════════════════════════════════════════════════════════╗
|
|
359
|
+
║ Agent-Pay Status ║
|
|
360
|
+
╚═══════════════════════════════════════════════════════════════╝
|
|
361
|
+
`);
|
|
362
|
+
console.log(`Wallet: ${config.walletAddress}`);
|
|
363
|
+
console.log(`Network: ${config.network}`);
|
|
364
|
+
console.log(`Gateway: ${config.gatewayUrl}\n`);
|
|
365
|
+
// Get balance and allowance from gateway
|
|
366
|
+
try {
|
|
367
|
+
const response = await fetch(`${config.gatewayUrl}/auth/info`, {
|
|
368
|
+
headers: { Authorization: `Bearer ${config.token}` },
|
|
369
|
+
});
|
|
370
|
+
if (!response.ok) {
|
|
371
|
+
throw new Error(`Failed to fetch info: ${response.statusText}`);
|
|
372
|
+
}
|
|
373
|
+
const info = await response.json();
|
|
374
|
+
console.log(`USDC Balance: $${info.balance?.usdc || "0.00"}`);
|
|
375
|
+
console.log(`Spending Limit: $${info.allowance?.usdc || "0.00"}\n`);
|
|
376
|
+
const allowance = parseFloat(info.allowance?.usdc || "0");
|
|
377
|
+
if (allowance === 0) {
|
|
378
|
+
console.log("No spending limit set. To approve spending, run:");
|
|
379
|
+
console.log(" npx @agent-pay/mcp approve 50\n");
|
|
380
|
+
}
|
|
381
|
+
else if (allowance < 10) {
|
|
382
|
+
console.log("Low spending limit. To increase, run:");
|
|
383
|
+
console.log(" npx @agent-pay/mcp approve 50\n");
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
catch (error) {
|
|
387
|
+
console.error("Failed to fetch wallet info:", error);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
// ============================================================================
|
|
391
|
+
// DEPOSIT COMMAND
|
|
392
|
+
// ============================================================================
|
|
393
|
+
async function deposit(quoteIdArg) {
|
|
394
|
+
const config = loadConfig();
|
|
395
|
+
if (!config) {
|
|
396
|
+
console.error("Not set up yet. Run: npx @agent-pay/mcp setup");
|
|
397
|
+
process.exit(1);
|
|
398
|
+
}
|
|
399
|
+
if (!quoteIdArg) {
|
|
400
|
+
console.error("Usage: npx @agent-pay/mcp deposit <quoteId>");
|
|
401
|
+
console.error("\nGet a quote first by asking Claude for compute.");
|
|
402
|
+
process.exit(1);
|
|
403
|
+
}
|
|
404
|
+
console.log(`
|
|
405
|
+
╔═══════════════════════════════════════════════════════════════╗
|
|
406
|
+
║ Deposit into Escrow ║
|
|
407
|
+
╚═══════════════════════════════════════════════════════════════╝
|
|
408
|
+
`);
|
|
409
|
+
// Fetch quote details from gateway
|
|
410
|
+
console.log("Fetching quote details...\n");
|
|
411
|
+
let quote;
|
|
412
|
+
try {
|
|
413
|
+
const response = await fetch(`${config.gatewayUrl}/compute/quote/${quoteIdArg}`, {
|
|
414
|
+
headers: { Authorization: `Bearer ${config.token}` },
|
|
415
|
+
});
|
|
416
|
+
if (!response.ok) {
|
|
417
|
+
// Quote endpoint might not exist, try to get from quotes list
|
|
418
|
+
console.error("Quote not found. Please request a new quote via Claude.");
|
|
419
|
+
process.exit(1);
|
|
420
|
+
}
|
|
421
|
+
quote = await response.json();
|
|
422
|
+
}
|
|
423
|
+
catch (error) {
|
|
424
|
+
console.error("Failed to fetch quote. Please request a new quote via Claude.");
|
|
425
|
+
process.exit(1);
|
|
426
|
+
}
|
|
427
|
+
if (!quote.escrow) {
|
|
428
|
+
console.error("This quote does not support escrow deposits.");
|
|
429
|
+
console.error("The gateway may not have escrow enabled.");
|
|
430
|
+
process.exit(1);
|
|
431
|
+
}
|
|
432
|
+
const escrowContract = quote.escrow.contract;
|
|
433
|
+
const specsHash = quote.specsHash;
|
|
434
|
+
const quotedAmount = BigInt(quote.escrow.quotedAmount);
|
|
435
|
+
const depositAmount = BigInt(quote.escrow.suggestedDeposit);
|
|
436
|
+
const depositUsd = (Number(depositAmount) / 1_000_000).toFixed(2);
|
|
437
|
+
const quotedUsd = (Number(quotedAmount) / 1_000_000).toFixed(2);
|
|
438
|
+
console.log("Quote Details:");
|
|
439
|
+
console.log(` Specs: ${quote.specs.cpu} CPU, ${quote.specs.memory} RAM, ${quote.specs.storage} storage`);
|
|
440
|
+
console.log(` Image: ${quote.specs.image}`);
|
|
441
|
+
console.log(` Duration: ${quote.specs.hours} hours`);
|
|
442
|
+
console.log(` Cost: $${quotedUsd} USDC\n`);
|
|
443
|
+
console.log(`Deposit Amount: $${depositUsd} USDC (includes buffer)\n`);
|
|
444
|
+
console.log("Your Protections:");
|
|
445
|
+
console.log(" ✓ Funds held in escrow (not sent to gateway yet)");
|
|
446
|
+
console.log(" ✓ Full refund if deployment fails");
|
|
447
|
+
console.log(" ✓ Excess refunded after deployment");
|
|
448
|
+
console.log(" ✓ All transactions recorded on-chain\n");
|
|
449
|
+
// Initialize WalletConnect
|
|
450
|
+
console.log("Initializing WalletConnect...\n");
|
|
451
|
+
const signClient = await initWalletConnect();
|
|
452
|
+
console.log("Scan QR code to connect and approve deposit:\n");
|
|
453
|
+
const { session, walletAddress } = await connectWallet(signClient);
|
|
454
|
+
if (walletAddress.toLowerCase() !== config.walletAddress.toLowerCase()) {
|
|
455
|
+
console.error(`\nWallet mismatch!`);
|
|
456
|
+
console.error(` Expected: ${config.walletAddress}`);
|
|
457
|
+
console.error(` Got: ${walletAddress}`);
|
|
458
|
+
process.exit(1);
|
|
459
|
+
}
|
|
460
|
+
console.log(`✓ Connected: ${walletAddress}\n`);
|
|
461
|
+
const chainId = CHAIN_IDS[config.network] || CHAIN_IDS["base"];
|
|
462
|
+
const usdcAddress = USDC_ADDRESSES[config.network] || USDC_ADDRESSES["base"];
|
|
463
|
+
// Step 1: Approve USDC spending for escrow contract
|
|
464
|
+
console.log("Step 1/2: Approving USDC spending for escrow...\n");
|
|
465
|
+
console.log("┌─────────────────────────────────────────────────────────────┐");
|
|
466
|
+
console.log("│ Check your wallet to approve USDC spending. │");
|
|
467
|
+
console.log(`│ Amount: $${depositUsd} USDC `);
|
|
468
|
+
console.log("│ Spender: AgentPayEscrow (verified contract) │");
|
|
469
|
+
console.log("└─────────────────────────────────────────────────────────────┘\n");
|
|
470
|
+
const approveData = encodeFunctionData({
|
|
471
|
+
abi: APPROVE_ABI,
|
|
472
|
+
functionName: "approve",
|
|
473
|
+
args: [escrowContract, depositAmount],
|
|
474
|
+
});
|
|
475
|
+
try {
|
|
476
|
+
const approveTxHash = await signClient.request({
|
|
477
|
+
topic: session.topic,
|
|
478
|
+
chainId,
|
|
479
|
+
request: {
|
|
480
|
+
method: "eth_sendTransaction",
|
|
481
|
+
params: [
|
|
482
|
+
{
|
|
483
|
+
from: walletAddress,
|
|
484
|
+
to: usdcAddress,
|
|
485
|
+
data: approveData,
|
|
486
|
+
},
|
|
487
|
+
],
|
|
488
|
+
},
|
|
489
|
+
});
|
|
490
|
+
console.log(`✓ Approval submitted: ${approveTxHash}`);
|
|
491
|
+
console.log("Waiting for confirmation...\n");
|
|
492
|
+
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
493
|
+
}
|
|
494
|
+
catch (error) {
|
|
495
|
+
console.error("\nApproval was rejected.");
|
|
496
|
+
process.exit(1);
|
|
497
|
+
}
|
|
498
|
+
// Step 2: Deposit into escrow
|
|
499
|
+
console.log("Step 2/2: Depositing into escrow...\n");
|
|
500
|
+
console.log("┌─────────────────────────────────────────────────────────────┐");
|
|
501
|
+
console.log("│ Check your wallet to confirm the deposit. │");
|
|
502
|
+
console.log(`│ Amount: $${depositUsd} USDC into escrow `);
|
|
503
|
+
console.log("└─────────────────────────────────────────────────────────────┘\n");
|
|
504
|
+
const depositData = encodeFunctionData({
|
|
505
|
+
abi: ESCROW_DEPOSIT_ABI,
|
|
506
|
+
functionName: "deposit",
|
|
507
|
+
args: [specsHash, quotedAmount, depositAmount],
|
|
508
|
+
});
|
|
509
|
+
try {
|
|
510
|
+
const depositTxHash = await signClient.request({
|
|
511
|
+
topic: session.topic,
|
|
512
|
+
chainId,
|
|
513
|
+
request: {
|
|
514
|
+
method: "eth_sendTransaction",
|
|
515
|
+
params: [
|
|
516
|
+
{
|
|
517
|
+
from: walletAddress,
|
|
518
|
+
to: escrowContract,
|
|
519
|
+
data: depositData,
|
|
520
|
+
},
|
|
521
|
+
],
|
|
522
|
+
},
|
|
523
|
+
});
|
|
524
|
+
console.log(`✓ Deposit submitted!`);
|
|
525
|
+
console.log(` Transaction: ${depositTxHash}\n`);
|
|
526
|
+
console.log("Waiting for confirmation...\n");
|
|
527
|
+
await new Promise((resolve) => setTimeout(resolve, 10000));
|
|
528
|
+
console.log(`
|
|
529
|
+
╔═══════════════════════════════════════════════════════════════╗
|
|
530
|
+
║ Deposit Complete! ║
|
|
531
|
+
╚═══════════════════════════════════════════════════════════════╝
|
|
532
|
+
|
|
533
|
+
Amount: $${depositUsd} USDC
|
|
534
|
+
Status: Deposited - waiting for deployment
|
|
535
|
+
|
|
536
|
+
The gateway will now deploy your compute.
|
|
537
|
+
You'll be notified in Claude when it's ready.
|
|
538
|
+
|
|
539
|
+
Track on Basescan: https://basescan.org/tx/${depositTxHash}
|
|
540
|
+
`);
|
|
541
|
+
}
|
|
542
|
+
catch (error) {
|
|
543
|
+
console.error("\nDeposit was rejected.");
|
|
544
|
+
process.exit(1);
|
|
545
|
+
}
|
|
546
|
+
await signClient.disconnect({
|
|
547
|
+
topic: session.topic,
|
|
548
|
+
reason: { code: 6000, message: "Deposit complete" },
|
|
549
|
+
});
|
|
550
|
+
process.exit(0);
|
|
551
|
+
}
|
|
552
|
+
// ============================================================================
|
|
553
|
+
// ESCROW STATUS COMMAND
|
|
554
|
+
// ============================================================================
|
|
555
|
+
async function escrowStatus(escrowIdArg) {
|
|
556
|
+
const config = loadConfig();
|
|
557
|
+
if (!config) {
|
|
558
|
+
console.error("Not set up yet. Run: npx @agent-pay/mcp setup");
|
|
559
|
+
process.exit(1);
|
|
560
|
+
}
|
|
561
|
+
if (!escrowIdArg) {
|
|
562
|
+
console.error("Usage: npx @agent-pay/mcp escrow-status <escrowId>");
|
|
563
|
+
process.exit(1);
|
|
564
|
+
}
|
|
565
|
+
console.log(`
|
|
566
|
+
╔═══════════════════════════════════════════════════════════════╗
|
|
567
|
+
║ Escrow Status ║
|
|
568
|
+
╚═══════════════════════════════════════════════════════════════╝
|
|
569
|
+
`);
|
|
570
|
+
// Get escrow contract address from gateway
|
|
571
|
+
let escrowContract;
|
|
572
|
+
try {
|
|
573
|
+
const response = await fetch(`${config.gatewayUrl}/`);
|
|
574
|
+
const info = await response.json();
|
|
575
|
+
escrowContract = info.escrow?.contract;
|
|
576
|
+
if (!escrowContract) {
|
|
577
|
+
console.error("Escrow contract not configured on gateway.");
|
|
578
|
+
process.exit(1);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
catch (error) {
|
|
582
|
+
console.error("Failed to fetch gateway info.");
|
|
583
|
+
process.exit(1);
|
|
584
|
+
}
|
|
585
|
+
// Read escrow status from chain
|
|
586
|
+
const chain = CHAINS[config.network] || CHAINS["base"];
|
|
587
|
+
const client = createPublicClient({
|
|
588
|
+
chain,
|
|
589
|
+
transport: http(),
|
|
590
|
+
});
|
|
591
|
+
try {
|
|
592
|
+
const result = await client.readContract({
|
|
593
|
+
address: escrowContract,
|
|
594
|
+
abi: ESCROW_READ_ABI,
|
|
595
|
+
functionName: "escrows",
|
|
596
|
+
args: [escrowIdArg],
|
|
597
|
+
});
|
|
598
|
+
const [user, depositAmount, quotedAmount, specsHash, createdAt, status, akashDseq, akashProvider, actualCost] = result;
|
|
599
|
+
const statusNames = ["None", "Deposited", "Released", "Refunded"];
|
|
600
|
+
const statusName = statusNames[status] || "Unknown";
|
|
601
|
+
const depositUsd = (Number(depositAmount) / 1_000_000).toFixed(2);
|
|
602
|
+
const quotedUsd = (Number(quotedAmount) / 1_000_000).toFixed(2);
|
|
603
|
+
const actualUsd = (Number(actualCost) / 1_000_000).toFixed(2);
|
|
604
|
+
console.log(`Escrow ID: ${escrowIdArg}`);
|
|
605
|
+
console.log(`Status: ${statusName}`);
|
|
606
|
+
console.log(`User: ${user}`);
|
|
607
|
+
console.log(`Deposit: $${depositUsd} USDC`);
|
|
608
|
+
console.log(`Quoted: $${quotedUsd} USDC`);
|
|
609
|
+
if (status === 2) { // Released
|
|
610
|
+
console.log(`Actual Cost: $${actualUsd} USDC`);
|
|
611
|
+
const refund = Number(depositAmount - actualCost) / 1_000_000;
|
|
612
|
+
console.log(`Refund: $${refund.toFixed(2)} USDC`);
|
|
613
|
+
}
|
|
614
|
+
if (akashDseq) {
|
|
615
|
+
console.log(`\nAkash Deployment:`);
|
|
616
|
+
console.log(` DSEQ: ${akashDseq}`);
|
|
617
|
+
console.log(` Provider: ${akashProvider}`);
|
|
618
|
+
}
|
|
619
|
+
console.log(`\nCreated: ${new Date(Number(createdAt) * 1000).toISOString()}`);
|
|
620
|
+
}
|
|
621
|
+
catch (error) {
|
|
622
|
+
console.error("Failed to read escrow status:", error);
|
|
623
|
+
process.exit(1);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
// ============================================================================
|
|
627
|
+
// CLI ENTRY POINT
|
|
628
|
+
// ============================================================================
|
|
629
|
+
const command = process.argv[2];
|
|
630
|
+
const arg = process.argv[3];
|
|
631
|
+
if (command === "setup") {
|
|
632
|
+
setup().catch((error) => {
|
|
633
|
+
console.error("Setup failed:", error);
|
|
634
|
+
process.exit(1);
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
else if (command === "approve") {
|
|
638
|
+
approve(arg).catch((error) => {
|
|
639
|
+
console.error("Approval failed:", error);
|
|
640
|
+
process.exit(1);
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
else if (command === "status") {
|
|
644
|
+
status().catch((error) => {
|
|
645
|
+
console.error("Status check failed:", error);
|
|
646
|
+
process.exit(1);
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
else if (command === "deposit") {
|
|
650
|
+
deposit(arg).catch((error) => {
|
|
651
|
+
console.error("Deposit failed:", error);
|
|
652
|
+
process.exit(1);
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
else if (command === "escrow-status") {
|
|
656
|
+
escrowStatus(arg).catch((error) => {
|
|
657
|
+
console.error("Escrow status check failed:", error);
|
|
658
|
+
process.exit(1);
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
else if (command === "--help" || command === "-h" || !command) {
|
|
662
|
+
console.log(`
|
|
663
|
+
Agent-Pay CLI
|
|
664
|
+
|
|
665
|
+
Commands:
|
|
666
|
+
setup Connect your wallet and configure agent-pay
|
|
667
|
+
approve <amount> Approve USDC spending (e.g., approve 50 for $50)
|
|
668
|
+
status Check your wallet balance and spending limit
|
|
669
|
+
deposit <quoteId> Deposit into escrow for a quote
|
|
670
|
+
escrow-status <escrowId> Check escrow status on-chain
|
|
671
|
+
|
|
672
|
+
Usage:
|
|
673
|
+
npx @agent-pay/mcp setup
|
|
674
|
+
npx @agent-pay/mcp approve 100
|
|
675
|
+
npx @agent-pay/mcp status
|
|
676
|
+
npx @agent-pay/mcp deposit quote_abc123
|
|
677
|
+
npx @agent-pay/mcp escrow-status 0x...
|
|
678
|
+
|
|
679
|
+
Environment variables:
|
|
680
|
+
AGENT_PAY_GATEWAY_URL Gateway URL (default: https://gateway.agentpay.dev)
|
|
681
|
+
AGENT_PAY_NETWORK Network: "base" or "base-sepolia" (default: base)
|
|
682
|
+
`);
|
|
683
|
+
}
|
|
684
|
+
else {
|
|
685
|
+
console.error(`Unknown command: ${command}`);
|
|
686
|
+
console.error('Run "npx @agent-pay/mcp --help" for usage.');
|
|
687
|
+
process.exit(1);
|
|
688
|
+
}
|
|
689
|
+
//# sourceMappingURL=cli.js.map
|