globodai-mcp-payment-manager 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/.env.example +23 -0
  2. package/.github/workflows/ci.yml +26 -0
  3. package/.github/workflows/release.yml +82 -0
  4. package/LICENSE +21 -0
  5. package/README.md +362 -0
  6. package/dist/index.d.ts +31 -0
  7. package/dist/index.js +122 -0
  8. package/dist/lib/blockchain.d.ts +50 -0
  9. package/dist/lib/blockchain.js +287 -0
  10. package/dist/lib/cards.d.ts +83 -0
  11. package/dist/lib/cards.js +276 -0
  12. package/dist/lib/cli-runner.d.ts +31 -0
  13. package/dist/lib/cli-runner.js +77 -0
  14. package/dist/lib/crypto.d.ts +39 -0
  15. package/dist/lib/crypto.js +228 -0
  16. package/dist/lib/cvv-crypto.d.ts +23 -0
  17. package/dist/lib/cvv-crypto.js +67 -0
  18. package/dist/lib/mcp-core.d.ts +46 -0
  19. package/dist/lib/mcp-core.js +86 -0
  20. package/dist/lib/pin-manager.d.ts +69 -0
  21. package/dist/lib/pin-manager.js +199 -0
  22. package/dist/lib/wallets.d.ts +91 -0
  23. package/dist/lib/wallets.js +227 -0
  24. package/dist/tools/add-card.d.ts +65 -0
  25. package/dist/tools/add-card.js +97 -0
  26. package/dist/tools/add-wallet.d.ts +65 -0
  27. package/dist/tools/add-wallet.js +104 -0
  28. package/dist/tools/card-status.d.ts +20 -0
  29. package/dist/tools/card-status.js +26 -0
  30. package/dist/tools/confirm-payment.d.ts +44 -0
  31. package/dist/tools/confirm-payment.js +88 -0
  32. package/dist/tools/get-total-balance.d.ts +41 -0
  33. package/dist/tools/get-total-balance.js +98 -0
  34. package/dist/tools/get-transactions.d.ts +39 -0
  35. package/dist/tools/get-transactions.js +40 -0
  36. package/dist/tools/get-wallet-balance.d.ts +43 -0
  37. package/dist/tools/get-wallet-balance.js +69 -0
  38. package/dist/tools/list-cards.d.ts +36 -0
  39. package/dist/tools/list-cards.js +39 -0
  40. package/dist/tools/list-wallet-transactions.d.ts +63 -0
  41. package/dist/tools/list-wallet-transactions.js +76 -0
  42. package/dist/tools/list-wallets.d.ts +41 -0
  43. package/dist/tools/list-wallets.js +50 -0
  44. package/dist/tools/lock-cards.d.ts +16 -0
  45. package/dist/tools/lock-cards.js +23 -0
  46. package/dist/tools/prepare-crypto-tx.d.ts +69 -0
  47. package/dist/tools/prepare-crypto-tx.js +93 -0
  48. package/dist/tools/prepare-payment.d.ts +57 -0
  49. package/dist/tools/prepare-payment.js +93 -0
  50. package/dist/tools/remove-card.d.ts +25 -0
  51. package/dist/tools/remove-card.js +39 -0
  52. package/dist/tools/remove-wallet.d.ts +27 -0
  53. package/dist/tools/remove-wallet.js +40 -0
  54. package/dist/tools/setup-pin.d.ts +26 -0
  55. package/dist/tools/setup-pin.js +33 -0
  56. package/dist/tools/sign-crypto-tx.d.ts +42 -0
  57. package/dist/tools/sign-crypto-tx.js +75 -0
  58. package/dist/tools/unlock-cards.d.ts +35 -0
  59. package/dist/tools/unlock-cards.js +41 -0
  60. package/package.json +50 -0
  61. package/src/index.ts +139 -0
  62. package/src/lib/blockchain.ts +375 -0
  63. package/src/lib/cards.ts +372 -0
  64. package/src/lib/cli-runner.ts +113 -0
  65. package/src/lib/crypto.ts +284 -0
  66. package/src/lib/cvv-crypto.ts +81 -0
  67. package/src/lib/mcp-core.ts +127 -0
  68. package/src/lib/pin-manager.ts +252 -0
  69. package/src/lib/wallets.ts +331 -0
  70. package/src/tools/add-card.ts +108 -0
  71. package/src/tools/add-wallet.ts +114 -0
  72. package/src/tools/card-status.ts +32 -0
  73. package/src/tools/confirm-payment.ts +103 -0
  74. package/src/tools/get-total-balance.ts +123 -0
  75. package/src/tools/get-transactions.ts +49 -0
  76. package/src/tools/get-wallet-balance.ts +75 -0
  77. package/src/tools/list-cards.ts +52 -0
  78. package/src/tools/list-wallet-transactions.ts +83 -0
  79. package/src/tools/list-wallets.ts +63 -0
  80. package/src/tools/lock-cards.ts +31 -0
  81. package/src/tools/prepare-crypto-tx.ts +108 -0
  82. package/src/tools/prepare-payment.ts +108 -0
  83. package/src/tools/remove-card.ts +46 -0
  84. package/src/tools/remove-wallet.ts +47 -0
  85. package/src/tools/setup-pin.ts +39 -0
  86. package/src/tools/sign-crypto-tx.ts +90 -0
  87. package/src/tools/unlock-cards.ts +48 -0
  88. package/tsconfig.json +19 -0
@@ -0,0 +1,375 @@
1
+ /**
2
+ * Blockchain API Client
3
+ *
4
+ * Fetches balances and transactions from various blockchains.
5
+ * Uses public RPC endpoints and explorer APIs.
6
+ */
7
+
8
+ import { CHAIN_CONFIG, type Chain } from "./wallets";
9
+
10
+ // Default public RPC endpoints (can be overridden via env vars)
11
+ const DEFAULT_RPC: Record<Chain, string> = {
12
+ ethereum: "https://eth.llamarpc.com",
13
+ polygon: "https://polygon-rpc.com",
14
+ arbitrum: "https://arb1.arbitrum.io/rpc",
15
+ optimism: "https://mainnet.optimism.io",
16
+ base: "https://mainnet.base.org",
17
+ avalanche: "https://api.avax.network/ext/bc/C/rpc",
18
+ bsc: "https://bsc-dataseed.binance.org",
19
+ bitcoin: "", // Needs special handling
20
+ solana: "https://api.mainnet-beta.solana.com",
21
+ starknet: "https://starknet-mainnet.public.blastapi.io",
22
+ };
23
+
24
+ // Explorer APIs for transactions
25
+ const EXPLORER_API: Partial<Record<Chain, string>> = {
26
+ ethereum: "https://api.etherscan.io/api",
27
+ polygon: "https://api.polygonscan.com/api",
28
+ arbitrum: "https://api.arbiscan.io/api",
29
+ optimism: "https://api-optimistic.etherscan.io/api",
30
+ base: "https://api.basescan.org/api",
31
+ bsc: "https://api.bscscan.com/api",
32
+ };
33
+
34
+ export interface WalletBalance {
35
+ address: string;
36
+ chain: Chain;
37
+ nativeBalance: string;
38
+ nativeBalanceFormatted: string;
39
+ nativeCurrency: string;
40
+ usdValue?: number;
41
+ tokens?: TokenBalance[];
42
+ }
43
+
44
+ export interface TokenBalance {
45
+ symbol: string;
46
+ name: string;
47
+ balance: string;
48
+ balanceFormatted: string;
49
+ contractAddress: string;
50
+ decimals: number;
51
+ usdValue?: number;
52
+ }
53
+
54
+ export interface WalletTransaction {
55
+ hash: string;
56
+ from: string;
57
+ to: string;
58
+ value: string;
59
+ valueFormatted: string;
60
+ timestamp: number;
61
+ blockNumber: number;
62
+ isIncoming: boolean;
63
+ status: "success" | "failed" | "pending";
64
+ fee?: string;
65
+ tokenSymbol?: string;
66
+ }
67
+
68
+ /**
69
+ * Get RPC URL for a chain
70
+ */
71
+ function getRpcUrl(chain: Chain): string {
72
+ const envVar = CHAIN_CONFIG[chain]?.rpcEnvVar;
73
+ if (envVar && process.env[envVar]) {
74
+ return process.env[envVar]!;
75
+ }
76
+ return DEFAULT_RPC[chain] || "";
77
+ }
78
+
79
+ /**
80
+ * Get explorer API key (optional, for higher rate limits)
81
+ */
82
+ function getExplorerApiKey(chain: Chain): string | undefined {
83
+ const keyMap: Partial<Record<Chain, string>> = {
84
+ ethereum: "ETHERSCAN_API_KEY",
85
+ polygon: "POLYGONSCAN_API_KEY",
86
+ arbitrum: "ARBISCAN_API_KEY",
87
+ bsc: "BSCSCAN_API_KEY",
88
+ };
89
+ const envVar = keyMap[chain];
90
+ return envVar ? process.env[envVar] : undefined;
91
+ }
92
+
93
+ /**
94
+ * Make JSON-RPC call
95
+ */
96
+ async function rpcCall(chain: Chain, method: string, params: unknown[]): Promise<unknown> {
97
+ const url = getRpcUrl(chain);
98
+ if (!url) {
99
+ throw new Error(`No RPC URL configured for ${chain}`);
100
+ }
101
+
102
+ const response = await fetch(url, {
103
+ method: "POST",
104
+ headers: { "Content-Type": "application/json" },
105
+ body: JSON.stringify({
106
+ jsonrpc: "2.0",
107
+ id: 1,
108
+ method,
109
+ params,
110
+ }),
111
+ });
112
+
113
+ const data = await response.json();
114
+ if (data.error) {
115
+ throw new Error(data.error.message || "RPC error");
116
+ }
117
+ return data.result;
118
+ }
119
+
120
+ /**
121
+ * Format wei to ETH (or equivalent)
122
+ */
123
+ function formatWei(wei: string, decimals = 18): string {
124
+ const weiNum = BigInt(wei);
125
+ const divisor = BigInt(10 ** decimals);
126
+ const whole = weiNum / divisor;
127
+ const fraction = weiNum % divisor;
128
+ const fractionStr = fraction.toString().padStart(decimals, "0").slice(0, 6);
129
+ return `${whole}.${fractionStr}`.replace(/\.?0+$/, "") || "0";
130
+ }
131
+
132
+ /**
133
+ * Get native balance for EVM chains
134
+ */
135
+ async function getEvmBalance(address: string, chain: Chain): Promise<WalletBalance> {
136
+ const result = await rpcCall(chain, "eth_getBalance", [address, "latest"]) as string;
137
+ const config = CHAIN_CONFIG[chain];
138
+
139
+ return {
140
+ address,
141
+ chain,
142
+ nativeBalance: result,
143
+ nativeBalanceFormatted: formatWei(result),
144
+ nativeCurrency: config?.nativeCurrency || "ETH",
145
+ };
146
+ }
147
+
148
+ /**
149
+ * Get Solana balance
150
+ */
151
+ async function getSolanaBalance(address: string): Promise<WalletBalance> {
152
+ const url = getRpcUrl("solana");
153
+
154
+ const response = await fetch(url, {
155
+ method: "POST",
156
+ headers: { "Content-Type": "application/json" },
157
+ body: JSON.stringify({
158
+ jsonrpc: "2.0",
159
+ id: 1,
160
+ method: "getBalance",
161
+ params: [address],
162
+ }),
163
+ });
164
+
165
+ const data = await response.json();
166
+ const lamports = data.result?.value || 0;
167
+
168
+ return {
169
+ address,
170
+ chain: "solana",
171
+ nativeBalance: lamports.toString(),
172
+ nativeBalanceFormatted: (lamports / 1e9).toFixed(4),
173
+ nativeCurrency: "SOL",
174
+ };
175
+ }
176
+
177
+ /**
178
+ * Get Bitcoin balance via public API
179
+ */
180
+ async function getBitcoinBalance(address: string): Promise<WalletBalance> {
181
+ // Using blockchain.info API
182
+ const response = await fetch(`https://blockchain.info/balance?active=${address}`);
183
+ const data = await response.json();
184
+
185
+ const satoshis = data[address]?.final_balance || 0;
186
+
187
+ return {
188
+ address,
189
+ chain: "bitcoin",
190
+ nativeBalance: satoshis.toString(),
191
+ nativeBalanceFormatted: (satoshis / 1e8).toFixed(8),
192
+ nativeCurrency: "BTC",
193
+ };
194
+ }
195
+
196
+ /**
197
+ * Get wallet balance
198
+ */
199
+ export async function getWalletBalance(address: string, chain: Chain): Promise<WalletBalance> {
200
+ switch (chain) {
201
+ case "bitcoin":
202
+ return getBitcoinBalance(address);
203
+ case "solana":
204
+ return getSolanaBalance(address);
205
+ default:
206
+ // All EVM chains
207
+ return getEvmBalance(address, chain);
208
+ }
209
+ }
210
+
211
+ /**
212
+ * Get EVM transactions via explorer API
213
+ */
214
+ async function getEvmTransactions(
215
+ address: string,
216
+ chain: Chain,
217
+ limit = 20
218
+ ): Promise<WalletTransaction[]> {
219
+ const apiUrl = EXPLORER_API[chain];
220
+ if (!apiUrl) {
221
+ return []; // No explorer API for this chain
222
+ }
223
+
224
+ const apiKey = getExplorerApiKey(chain);
225
+ const url = new URL(apiUrl);
226
+ url.searchParams.set("module", "account");
227
+ url.searchParams.set("action", "txlist");
228
+ url.searchParams.set("address", address);
229
+ url.searchParams.set("sort", "desc");
230
+ url.searchParams.set("page", "1");
231
+ url.searchParams.set("offset", limit.toString());
232
+ if (apiKey) {
233
+ url.searchParams.set("apikey", apiKey);
234
+ }
235
+
236
+ const response = await fetch(url.toString());
237
+ const data = await response.json();
238
+
239
+ if (data.status !== "1" || !Array.isArray(data.result)) {
240
+ return [];
241
+ }
242
+
243
+ const config = CHAIN_CONFIG[chain];
244
+
245
+ return data.result.map((tx: any) => ({
246
+ hash: tx.hash,
247
+ from: tx.from,
248
+ to: tx.to,
249
+ value: tx.value,
250
+ valueFormatted: `${formatWei(tx.value)} ${config?.nativeCurrency || "ETH"}`,
251
+ timestamp: parseInt(tx.timeStamp) * 1000,
252
+ blockNumber: parseInt(tx.blockNumber),
253
+ isIncoming: tx.to.toLowerCase() === address.toLowerCase(),
254
+ status: tx.isError === "0" ? "success" : "failed",
255
+ fee: formatWei((BigInt(tx.gasUsed) * BigInt(tx.gasPrice)).toString()),
256
+ }));
257
+ }
258
+
259
+ /**
260
+ * Get Solana transactions
261
+ */
262
+ async function getSolanaTransactions(
263
+ address: string,
264
+ limit = 20
265
+ ): Promise<WalletTransaction[]> {
266
+ const url = getRpcUrl("solana");
267
+
268
+ // Get signatures
269
+ const sigResponse = await fetch(url, {
270
+ method: "POST",
271
+ headers: { "Content-Type": "application/json" },
272
+ body: JSON.stringify({
273
+ jsonrpc: "2.0",
274
+ id: 1,
275
+ method: "getSignaturesForAddress",
276
+ params: [address, { limit }],
277
+ }),
278
+ });
279
+
280
+ const sigData = await sigResponse.json();
281
+ const signatures = sigData.result || [];
282
+
283
+ return signatures.map((sig: any) => ({
284
+ hash: sig.signature,
285
+ from: address,
286
+ to: "",
287
+ value: "0",
288
+ valueFormatted: "N/A",
289
+ timestamp: sig.blockTime ? sig.blockTime * 1000 : Date.now(),
290
+ blockNumber: sig.slot,
291
+ isIncoming: false,
292
+ status: sig.err ? "failed" : "success",
293
+ }));
294
+ }
295
+
296
+ /**
297
+ * Get Bitcoin transactions
298
+ */
299
+ async function getBitcoinTransactions(
300
+ address: string,
301
+ limit = 20
302
+ ): Promise<WalletTransaction[]> {
303
+ const response = await fetch(
304
+ `https://blockchain.info/rawaddr/${address}?limit=${limit}`
305
+ );
306
+ const data = await response.json();
307
+
308
+ if (!data.txs) return [];
309
+
310
+ return data.txs.map((tx: any) => {
311
+ const isIncoming = tx.out.some(
312
+ (o: any) => o.addr === address
313
+ );
314
+ const value = tx.out
315
+ .filter((o: any) => (isIncoming ? o.addr === address : o.addr !== address))
316
+ .reduce((sum: number, o: any) => sum + o.value, 0);
317
+
318
+ return {
319
+ hash: tx.hash,
320
+ from: tx.inputs[0]?.prev_out?.addr || "coinbase",
321
+ to: tx.out[0]?.addr || "",
322
+ value: value.toString(),
323
+ valueFormatted: `${(value / 1e8).toFixed(8)} BTC`,
324
+ timestamp: tx.time * 1000,
325
+ blockNumber: tx.block_height || 0,
326
+ isIncoming,
327
+ status: "success",
328
+ };
329
+ });
330
+ }
331
+
332
+ /**
333
+ * Get wallet transactions
334
+ */
335
+ export async function getWalletTransactions(
336
+ address: string,
337
+ chain: Chain,
338
+ limit = 20
339
+ ): Promise<WalletTransaction[]> {
340
+ switch (chain) {
341
+ case "bitcoin":
342
+ return getBitcoinTransactions(address, limit);
343
+ case "solana":
344
+ return getSolanaTransactions(address, limit);
345
+ default:
346
+ return getEvmTransactions(address, chain, limit);
347
+ }
348
+ }
349
+
350
+ /**
351
+ * Get USD price for a currency (simple coingecko fetch)
352
+ */
353
+ export async function getUsdPrice(currency: string): Promise<number | null> {
354
+ const coinIds: Record<string, string> = {
355
+ ETH: "ethereum",
356
+ BTC: "bitcoin",
357
+ SOL: "solana",
358
+ MATIC: "matic-network",
359
+ AVAX: "avalanche-2",
360
+ BNB: "binancecoin",
361
+ };
362
+
363
+ const coinId = coinIds[currency.toUpperCase()];
364
+ if (!coinId) return null;
365
+
366
+ try {
367
+ const response = await fetch(
368
+ `https://api.coingecko.com/api/v3/simple/price?ids=${coinId}&vs_currencies=usd`
369
+ );
370
+ const data = await response.json();
371
+ return data[coinId]?.usd || null;
372
+ } catch {
373
+ return null;
374
+ }
375
+ }