clawdentials-mcp 0.1.0 → 0.7.2

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.
Files changed (35) hide show
  1. package/README.md +310 -58
  2. package/dist/index.d.ts +1 -1
  3. package/dist/index.js +225 -18
  4. package/dist/schemas/index.d.ts +141 -0
  5. package/dist/schemas/index.js +54 -0
  6. package/dist/services/firestore.d.ts +45 -2
  7. package/dist/services/firestore.js +410 -6
  8. package/dist/services/payments/alby.d.ts +104 -0
  9. package/dist/services/payments/alby.js +239 -0
  10. package/dist/services/payments/breez.d.ts +91 -0
  11. package/dist/services/payments/breez.js +267 -0
  12. package/dist/services/payments/cashu.d.ts +127 -0
  13. package/dist/services/payments/cashu.js +248 -0
  14. package/dist/services/payments/coinremitter.d.ts +84 -0
  15. package/dist/services/payments/coinremitter.js +176 -0
  16. package/dist/services/payments/index.d.ts +132 -0
  17. package/dist/services/payments/index.js +180 -0
  18. package/dist/services/payments/oxapay.d.ts +89 -0
  19. package/dist/services/payments/oxapay.js +221 -0
  20. package/dist/services/payments/x402.d.ts +61 -0
  21. package/dist/services/payments/x402.js +94 -0
  22. package/dist/services/payments/zbd.d.ts +88 -0
  23. package/dist/services/payments/zbd.js +221 -0
  24. package/dist/tools/admin.d.ts +195 -0
  25. package/dist/tools/admin.js +210 -0
  26. package/dist/tools/agent.d.ts +197 -0
  27. package/dist/tools/agent.js +200 -0
  28. package/dist/tools/escrow.d.ts +74 -16
  29. package/dist/tools/escrow.js +139 -28
  30. package/dist/tools/index.d.ts +3 -0
  31. package/dist/tools/index.js +3 -0
  32. package/dist/tools/payment.d.ts +144 -0
  33. package/dist/tools/payment.js +376 -0
  34. package/dist/types/index.d.ts +44 -1
  35. package/package.json +18 -2
@@ -0,0 +1,376 @@
1
+ /**
2
+ * Payment MCP Tools
3
+ *
4
+ * Tools for deposits and withdrawals in USDC, USDT, and BTC
5
+ */
6
+ import { paymentService, getPaymentConfig, oxapayService, } from '../services/payments/index.js';
7
+ import { validateApiKey, getAgent, creditBalance, createWithdrawal, getBalance, } from '../services/firestore.js';
8
+ import { collections, getDb } from '../services/firestore.js';
9
+ import { Timestamp } from 'firebase-admin/firestore';
10
+ // ============ DEPOSIT TOOLS ============
11
+ export const paymentTools = {
12
+ /**
13
+ * Create a deposit request (USDC, USDT, or BTC)
14
+ */
15
+ deposit_create: {
16
+ description: 'Create a deposit request to add funds to your Clawdentials balance. Returns payment instructions (address/invoice) for the selected currency. Supported: USDC (Base), USDT (TRC-20), BTC (Lightning).',
17
+ handler: async (args) => {
18
+ // Validate API key
19
+ const isValid = await validateApiKey(args.agentId, args.apiKey);
20
+ if (!isValid) {
21
+ return { success: false, error: 'Invalid API key' };
22
+ }
23
+ // Validate amount
24
+ if (args.amount <= 0) {
25
+ return { success: false, error: 'Amount must be positive' };
26
+ }
27
+ // Minimum amounts
28
+ const minimums = {
29
+ USDC: 1,
30
+ USDT: 1,
31
+ BTC: 0.5, // ~500 sats minimum
32
+ };
33
+ if (args.amount < minimums[args.currency]) {
34
+ return {
35
+ success: false,
36
+ error: `Minimum deposit for ${args.currency}: $${minimums[args.currency]}`,
37
+ };
38
+ }
39
+ // Create deposit request
40
+ const result = await paymentService.createDeposit({
41
+ agentId: args.agentId,
42
+ amount: args.amount,
43
+ currency: args.currency,
44
+ description: `Deposit for agent ${args.agentId}`,
45
+ });
46
+ if (!result.success) {
47
+ return { success: false, error: result.error };
48
+ }
49
+ // Store deposit record in Firestore
50
+ if (result.deposit) {
51
+ const depositRef = collections.deposits
52
+ ? collections.deposits().doc(result.deposit.id)
53
+ : getDb().collection('deposits').doc(result.deposit.id);
54
+ await depositRef.set({
55
+ ...result.deposit,
56
+ createdAt: Timestamp.fromDate(result.deposit.createdAt),
57
+ expiresAt: result.deposit.expiresAt
58
+ ? Timestamp.fromDate(result.deposit.expiresAt)
59
+ : null,
60
+ });
61
+ }
62
+ return {
63
+ success: true,
64
+ depositId: result.deposit?.id,
65
+ currency: args.currency,
66
+ amount: args.amount,
67
+ paymentInstructions: result.paymentInstructions,
68
+ message: getPaymentMessage(args.currency, result.paymentInstructions),
69
+ };
70
+ },
71
+ },
72
+ /**
73
+ * Check deposit status - verifies with payment provider and auto-credits if paid
74
+ */
75
+ deposit_status: {
76
+ description: 'Check the status of a deposit request. If payment is confirmed, balance is automatically credited.',
77
+ handler: async (args) => {
78
+ const depositRef = getDb().collection('deposits').doc(args.depositId);
79
+ const doc = await depositRef.get();
80
+ if (!doc.exists) {
81
+ return { success: false, error: 'Deposit not found' };
82
+ }
83
+ const data = doc.data();
84
+ let currentStatus = data.status;
85
+ let completedAt = data.completedAt;
86
+ let txHash = data.txHash;
87
+ let balanceCredited = false;
88
+ // If deposit is still pending, check with the payment provider
89
+ if (currentStatus === 'pending' || currentStatus === 'confirming') {
90
+ const verificationResult = await verifyDepositWithProvider(data);
91
+ if (verificationResult.paid) {
92
+ // Payment confirmed! Update deposit and credit balance
93
+ currentStatus = 'completed';
94
+ completedAt = Timestamp.fromDate(new Date());
95
+ txHash = verificationResult.txId || null;
96
+ // Credit the agent's balance
97
+ await creditBalance(data.agentId, data.amount, `Deposit ${args.depositId} confirmed`);
98
+ balanceCredited = true;
99
+ // Update deposit record
100
+ await depositRef.update({
101
+ status: 'completed',
102
+ completedAt,
103
+ txHash,
104
+ });
105
+ }
106
+ else if (verificationResult.status === 'expired' || verificationResult.status === 'failed') {
107
+ currentStatus = verificationResult.status;
108
+ await depositRef.update({ status: currentStatus });
109
+ }
110
+ else if (verificationResult.status === 'confirming') {
111
+ currentStatus = 'confirming';
112
+ await depositRef.update({ status: 'confirming' });
113
+ }
114
+ }
115
+ const response = {
116
+ success: true,
117
+ deposit: {
118
+ id: doc.id,
119
+ agentId: data.agentId,
120
+ amount: data.amount,
121
+ currency: data.currency,
122
+ network: data.network,
123
+ status: currentStatus,
124
+ provider: data.provider,
125
+ createdAt: data.createdAt?.toDate?.() || data.createdAt,
126
+ expiresAt: data.expiresAt?.toDate?.() || data.expiresAt,
127
+ completedAt: completedAt?.toDate?.() || completedAt,
128
+ txHash,
129
+ },
130
+ };
131
+ if (balanceCredited) {
132
+ const newBalance = await getBalance(data.agentId);
133
+ response.message = `Payment confirmed! $${data.amount} credited to your balance.`;
134
+ response.newBalance = newBalance;
135
+ }
136
+ return response;
137
+ },
138
+ },
139
+ /**
140
+ * Get payment configuration status
141
+ */
142
+ payment_config: {
143
+ description: 'Check which payment methods are configured and available for deposits/withdrawals.',
144
+ handler: async () => {
145
+ const config = getPaymentConfig();
146
+ return {
147
+ success: true,
148
+ paymentMethods: config,
149
+ supported: {
150
+ USDC: config.usdc.configured,
151
+ USDT: config.usdt.configured,
152
+ BTC: config.btc.configured,
153
+ },
154
+ };
155
+ },
156
+ },
157
+ /**
158
+ * Request crypto withdrawal
159
+ */
160
+ withdraw_crypto: {
161
+ description: 'Request a withdrawal in cryptocurrency. Provide your wallet address (USDC/USDT) or Lightning invoice/address (BTC).',
162
+ handler: async (args) => {
163
+ // Validate API key
164
+ const isValid = await validateApiKey(args.agentId, args.apiKey);
165
+ if (!isValid) {
166
+ return { success: false, error: 'Invalid API key' };
167
+ }
168
+ // Validate amount
169
+ if (args.amount <= 0) {
170
+ return { success: false, error: 'Amount must be positive' };
171
+ }
172
+ // Check balance
173
+ const balance = await getBalance(args.agentId);
174
+ if (balance < args.amount) {
175
+ return {
176
+ success: false,
177
+ error: `Insufficient balance: have $${balance}, need $${args.amount}`,
178
+ };
179
+ }
180
+ // Validate destination format
181
+ const validationError = validateDestination(args.currency, args.destination);
182
+ if (validationError) {
183
+ return { success: false, error: validationError };
184
+ }
185
+ // Create withdrawal record (this debits balance)
186
+ const withdrawal = await createWithdrawal(args.agentId, args.amount, args.currency, `${args.currency}: ${args.destination}`);
187
+ // Attempt automatic withdrawal
188
+ const sendResult = await paymentService.sendWithdrawal({
189
+ currency: args.currency,
190
+ amount: args.amount,
191
+ destination: args.destination,
192
+ });
193
+ if (sendResult.success) {
194
+ // Update withdrawal status to completed
195
+ const withdrawalRef = getDb().collection('withdrawals').doc(withdrawal.id);
196
+ await withdrawalRef.update({
197
+ status: 'completed',
198
+ processedAt: Timestamp.fromDate(new Date()),
199
+ notes: `Auto-processed. TxID: ${sendResult.txId}`,
200
+ });
201
+ return {
202
+ success: true,
203
+ withdrawalId: withdrawal.id,
204
+ status: 'completed',
205
+ txId: sendResult.txId,
206
+ message: `Withdrawal of $${args.amount} ${args.currency} sent successfully.`,
207
+ };
208
+ }
209
+ else {
210
+ // Withdrawal created but needs manual processing
211
+ return {
212
+ success: true,
213
+ withdrawalId: withdrawal.id,
214
+ status: 'pending',
215
+ message: `Withdrawal request created. ${sendResult.error || 'Will be processed manually.'}`,
216
+ newBalance: balance - args.amount,
217
+ };
218
+ }
219
+ },
220
+ },
221
+ /**
222
+ * Set wallet addresses for an agent
223
+ */
224
+ agent_set_wallets: {
225
+ description: 'Set your wallet addresses for receiving withdrawals. You can set addresses for USDC (Base), USDT (TRC-20), and BTC (Lightning Address).',
226
+ handler: async (args) => {
227
+ // Validate API key
228
+ const isValid = await validateApiKey(args.agentId, args.apiKey);
229
+ if (!isValid) {
230
+ return { success: false, error: 'Invalid API key' };
231
+ }
232
+ const wallets = {};
233
+ // Validate and set Base address
234
+ if (args.baseAddress) {
235
+ if (!/^0x[a-fA-F0-9]{40}$/.test(args.baseAddress)) {
236
+ return { success: false, error: 'Invalid Base address format. Expected: 0x...' };
237
+ }
238
+ wallets.base = args.baseAddress;
239
+ }
240
+ // Validate and set TRC-20 address
241
+ if (args.trc20Address) {
242
+ if (!/^T[a-zA-Z0-9]{33}$/.test(args.trc20Address)) {
243
+ return { success: false, error: 'Invalid TRC-20 address format. Expected: T...' };
244
+ }
245
+ wallets.trc20 = args.trc20Address;
246
+ }
247
+ // Validate and set Lightning address
248
+ if (args.lightningAddress) {
249
+ if (!args.lightningAddress.includes('@')) {
250
+ return {
251
+ success: false,
252
+ error: 'Invalid Lightning Address format. Expected: user@domain.com',
253
+ };
254
+ }
255
+ wallets.lightning = args.lightningAddress;
256
+ }
257
+ if (Object.keys(wallets).length === 0) {
258
+ return { success: false, error: 'No wallet addresses provided' };
259
+ }
260
+ // Update agent record
261
+ const agentRef = getDb().collection('agents').doc(args.agentId);
262
+ const agent = await getAgent(args.agentId);
263
+ if (!agent) {
264
+ return { success: false, error: 'Agent not found' };
265
+ }
266
+ const existingWallets = agent.wallets || {};
267
+ const updatedWallets = { ...existingWallets, ...wallets };
268
+ await agentRef.update({ wallets: updatedWallets });
269
+ return {
270
+ success: true,
271
+ wallets: updatedWallets,
272
+ message: 'Wallet addresses updated successfully.',
273
+ };
274
+ },
275
+ },
276
+ };
277
+ /**
278
+ * Generate a human-readable payment message
279
+ */
280
+ function getPaymentMessage(currency, instructions) {
281
+ if (!instructions) {
282
+ return 'Payment instructions unavailable.';
283
+ }
284
+ const expiry = instructions.expiresAt
285
+ ? ` Expires: ${instructions.expiresAt.toISOString()}`
286
+ : '';
287
+ switch (currency) {
288
+ case 'USDC':
289
+ return `Send ${instructions.amount} USDC to ${instructions.address} on Base network.${expiry}`;
290
+ case 'USDT':
291
+ if (instructions.url) {
292
+ return `Pay ${instructions.amount} USDT at: ${instructions.url}${expiry}`;
293
+ }
294
+ return `Send ${instructions.amount} USDT to ${instructions.address} on Tron (TRC-20).${expiry}`;
295
+ case 'BTC':
296
+ return `Pay this Lightning invoice: ${instructions.address?.substring(0, 50)}...${expiry}`;
297
+ default:
298
+ return `Send ${instructions.amount} ${currency} to ${instructions.address}`;
299
+ }
300
+ }
301
+ /**
302
+ * Validate destination address format
303
+ */
304
+ function validateDestination(currency, destination) {
305
+ switch (currency) {
306
+ case 'USDC':
307
+ if (!/^0x[a-fA-F0-9]{40}$/.test(destination)) {
308
+ return 'Invalid Base/EVM address. Expected format: 0x...';
309
+ }
310
+ break;
311
+ case 'USDT':
312
+ if (!/^T[a-zA-Z0-9]{33}$/.test(destination)) {
313
+ return 'Invalid TRC-20 address. Expected format: T...';
314
+ }
315
+ break;
316
+ case 'BTC':
317
+ // Accept Lightning invoice (lnbc...) or Lightning Address (user@domain)
318
+ if (!destination.startsWith('lnbc') && !destination.includes('@')) {
319
+ return 'Invalid Lightning destination. Provide a Lightning invoice (lnbc...) or Lightning Address (user@domain).';
320
+ }
321
+ break;
322
+ }
323
+ return null;
324
+ }
325
+ /**
326
+ * Verify deposit with payment provider
327
+ */
328
+ async function verifyDepositWithProvider(deposit) {
329
+ const provider = deposit.provider;
330
+ const externalId = deposit.externalId;
331
+ // Check if deposit has expired
332
+ if (deposit.expiresAt) {
333
+ const expiresAt = deposit.expiresAt?.toDate?.() || deposit.expiresAt;
334
+ if (new Date() > expiresAt) {
335
+ return { paid: false, status: 'expired' };
336
+ }
337
+ }
338
+ switch (provider) {
339
+ case 'oxapay': {
340
+ // USDT via OxaPay
341
+ if (!externalId) {
342
+ return { paid: false, status: 'pending' };
343
+ }
344
+ const result = await oxapayService.getPaymentStatus(externalId);
345
+ if (!result.success) {
346
+ return { paid: false, status: 'pending' };
347
+ }
348
+ // OxaPay statuses: Waiting, Confirming, Paid, Failed, Expired
349
+ if (result.paid) {
350
+ return { paid: true, status: 'completed', txId: result.txId };
351
+ }
352
+ else if (result.status === 'Confirming') {
353
+ return { paid: false, status: 'confirming' };
354
+ }
355
+ else if (result.status === 'Failed') {
356
+ return { paid: false, status: 'failed' };
357
+ }
358
+ else if (result.status === 'Expired') {
359
+ return { paid: false, status: 'expired' };
360
+ }
361
+ return { paid: false, status: 'pending' };
362
+ }
363
+ case 'x402': {
364
+ // USDC via x402 - manual verification for now
365
+ // TODO: Add Base RPC verification when needed
366
+ return { paid: false, status: 'pending' };
367
+ }
368
+ case 'breez': {
369
+ // BTC via Breez - SDK handles verification internally
370
+ // For now, return pending (Breez callbacks would update this)
371
+ return { paid: false, status: 'pending' };
372
+ }
373
+ default:
374
+ return { paid: false, status: 'pending' };
375
+ }
376
+ }
@@ -1,5 +1,22 @@
1
1
  export type EscrowStatus = 'pending' | 'in_progress' | 'completed' | 'disputed' | 'cancelled';
2
- export type Currency = 'USD' | 'USDC' | 'BTC';
2
+ export type Currency = 'USD' | 'USDC' | 'USDT' | 'BTC';
3
+ export type PaymentNetwork = 'base' | 'trc20' | 'lightning';
4
+ export interface Deposit {
5
+ id: string;
6
+ agentId: string;
7
+ amount: number;
8
+ currency: Currency;
9
+ network: PaymentNetwork;
10
+ status: 'pending' | 'confirming' | 'completed' | 'expired' | 'failed';
11
+ provider: 'x402' | 'oxapay' | 'cashu' | 'breez' | 'alby' | 'coinremitter' | 'zbd';
12
+ externalId: string | null;
13
+ paymentAddress: string | null;
14
+ paymentUrl: string | null;
15
+ createdAt: Date;
16
+ expiresAt: Date | null;
17
+ completedAt: Date | null;
18
+ txHash: string | null;
19
+ }
3
20
  export type SubscriptionTier = 'free' | 'verified' | 'pro';
4
21
  export interface Escrow {
5
22
  id: string;
@@ -7,11 +24,14 @@ export interface Escrow {
7
24
  providerAgentId: string;
8
25
  taskDescription: string;
9
26
  amount: number;
27
+ fee: number;
28
+ feeRate: number;
10
29
  currency: Currency;
11
30
  status: EscrowStatus;
12
31
  createdAt: Date;
13
32
  completedAt: Date | null;
14
33
  proofOfWork: string | null;
34
+ disputeReason: string | null;
15
35
  }
16
36
  export interface Agent {
17
37
  id: string;
@@ -22,12 +42,35 @@ export interface Agent {
22
42
  verified: boolean;
23
43
  subscriptionTier: SubscriptionTier;
24
44
  stats: AgentStats;
45
+ apiKeyHash: string;
46
+ balance: number;
47
+ nostrPubkey?: string;
48
+ nip05?: string;
49
+ wallets?: {
50
+ base?: string;
51
+ trc20?: string;
52
+ lightning?: string;
53
+ };
54
+ }
55
+ export type WithdrawalStatus = 'pending' | 'processing' | 'completed' | 'rejected';
56
+ export interface Withdrawal {
57
+ id: string;
58
+ agentId: string;
59
+ amount: number;
60
+ currency: Currency;
61
+ status: WithdrawalStatus;
62
+ paymentMethod: string;
63
+ requestedAt: Date;
64
+ processedAt: Date | null;
65
+ notes: string | null;
25
66
  }
26
67
  export interface AgentStats {
27
68
  tasksCompleted: number;
28
69
  totalEarned: number;
29
70
  successRate: number;
30
71
  avgCompletionTime: number;
72
+ disputeCount: number;
73
+ disputeRate: number;
31
74
  }
32
75
  export interface Task {
33
76
  id: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawdentials-mcp",
3
- "version": "0.1.0",
3
+ "version": "0.7.2",
4
4
  "description": "MCP server for Clawdentials - escrow and reputation for AI agent commerce",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -16,6 +16,8 @@
16
16
  "dev": "tsc --watch",
17
17
  "start": "node dist/index.js",
18
18
  "test": "tsx scripts/test-tools.ts",
19
+ "e2e": "tsx scripts/e2e-test.ts",
20
+ "e2e:real": "tsx scripts/e2e-test.ts --real",
19
21
  "typecheck": "tsc --noEmit",
20
22
  "prepublishOnly": "npm run build"
21
23
  },
@@ -26,7 +28,16 @@
26
28
  "escrow",
27
29
  "reputation",
28
30
  "claude",
29
- "anthropic"
31
+ "anthropic",
32
+ "crypto",
33
+ "usdc",
34
+ "usdt",
35
+ "bitcoin",
36
+ "lightning",
37
+ "cashu",
38
+ "ecash",
39
+ "x402",
40
+ "payments"
30
41
  ],
31
42
  "author": "Fernando Nikolic",
32
43
  "license": "MIT",
@@ -40,9 +51,14 @@
40
51
  "url": "https://github.com/fernikolic/clawdentials/issues"
41
52
  },
42
53
  "dependencies": {
54
+ "@breeztech/breez-sdk-liquid": "0.11.13",
55
+ "@cashu/cashu-ts": "2.5.3",
43
56
  "@google-cloud/firestore": "8.2.0",
44
57
  "@modelcontextprotocol/sdk": "^1.0.0",
58
+ "@noble/hashes": "2.0.1",
59
+ "dotenv": "17.2.3",
45
60
  "firebase-admin": "^12.0.0",
61
+ "nostr-tools": "2.22.1",
46
62
  "zod": "^3.23.0"
47
63
  },
48
64
  "devDependencies": {