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,199 @@
1
+ /**
2
+ * PIN Manager - Master PIN for card access
3
+ *
4
+ * Security model:
5
+ * - CVV is encrypted with a key derived from the master PIN
6
+ * - PIN is stored in memory only (never persisted)
7
+ * - PIN auto-expires after inactivity timeout
8
+ * - Without PIN, cards cannot be used for payments
9
+ *
10
+ * Flow:
11
+ * 1. User sets PIN once (stored as hash for verification)
12
+ * 2. User unlocks with PIN → PIN held in memory
13
+ * 3. Payments work while unlocked
14
+ * 4. After timeout or explicit lock → PIN cleared from memory
15
+ */
16
+ import { createHash, scryptSync, randomBytes } from "crypto";
17
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
18
+ import { homedir } from "os";
19
+ import { join } from "path";
20
+ const CONFIG_DIR = join(homedir(), ".mcp-ecosystem");
21
+ const PIN_CONFIG_FILE = join(CONFIG_DIR, "pin-config.json");
22
+ // In-memory PIN storage (never persisted)
23
+ let currentPin = null;
24
+ let lastActivity = 0;
25
+ const SESSION_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes
26
+ function ensureConfigDir() {
27
+ if (!existsSync(CONFIG_DIR)) {
28
+ mkdirSync(CONFIG_DIR, { recursive: true });
29
+ }
30
+ }
31
+ /**
32
+ * Hash PIN for storage (verification only)
33
+ */
34
+ function hashPin(pin, salt) {
35
+ return createHash("sha256")
36
+ .update(pin + salt)
37
+ .digest("hex");
38
+ }
39
+ /**
40
+ * Derive encryption key from PIN (for CVV encryption)
41
+ */
42
+ export function deriveKeyFromPin(pin, salt) {
43
+ return scryptSync(pin, salt, 32);
44
+ }
45
+ /**
46
+ * Check if PIN is configured
47
+ */
48
+ export function isPinConfigured() {
49
+ return existsSync(PIN_CONFIG_FILE);
50
+ }
51
+ /**
52
+ * Get PIN config (without sensitive data)
53
+ */
54
+ function getPinConfig() {
55
+ if (!existsSync(PIN_CONFIG_FILE)) {
56
+ return null;
57
+ }
58
+ try {
59
+ return JSON.parse(readFileSync(PIN_CONFIG_FILE, "utf-8"));
60
+ }
61
+ catch {
62
+ return null;
63
+ }
64
+ }
65
+ /**
66
+ * Set up master PIN (first time)
67
+ */
68
+ export function setupPin(pin) {
69
+ if (pin.length < 4 || pin.length > 8) {
70
+ return { success: false, error: "PIN must be 4-8 characters" };
71
+ }
72
+ if (isPinConfigured()) {
73
+ return { success: false, error: "PIN already configured. Use change_pin to modify." };
74
+ }
75
+ ensureConfigDir();
76
+ const salt = randomBytes(16).toString("hex");
77
+ const cvvKeySalt = randomBytes(16).toString("hex");
78
+ const pinHash = hashPin(pin, salt);
79
+ const config = {
80
+ pinHash,
81
+ salt,
82
+ cvvKeySalt,
83
+ createdAt: new Date().toISOString(),
84
+ };
85
+ writeFileSync(PIN_CONFIG_FILE, JSON.stringify(config, null, 2), { mode: 0o600 });
86
+ // Auto-unlock after setup
87
+ currentPin = pin;
88
+ lastActivity = Date.now();
89
+ console.error("[pin-manager] Master PIN configured and unlocked");
90
+ return { success: true };
91
+ }
92
+ /**
93
+ * Change master PIN (requires current PIN)
94
+ */
95
+ export function changePin(currentPinInput, newPin) {
96
+ const config = getPinConfig();
97
+ if (!config) {
98
+ return { success: false, error: "No PIN configured" };
99
+ }
100
+ // Verify current PIN
101
+ if (hashPin(currentPinInput, config.salt) !== config.pinHash) {
102
+ return { success: false, error: "Current PIN is incorrect" };
103
+ }
104
+ if (newPin.length < 4 || newPin.length > 8) {
105
+ return { success: false, error: "New PIN must be 4-8 characters" };
106
+ }
107
+ // Generate new salts
108
+ const salt = randomBytes(16).toString("hex");
109
+ const cvvKeySalt = randomBytes(16).toString("hex");
110
+ const pinHash = hashPin(newPin, salt);
111
+ const newConfig = {
112
+ pinHash,
113
+ salt,
114
+ cvvKeySalt,
115
+ createdAt: new Date().toISOString(),
116
+ };
117
+ writeFileSync(PIN_CONFIG_FILE, JSON.stringify(newConfig, null, 2), { mode: 0o600 });
118
+ // Update in-memory PIN
119
+ currentPin = newPin;
120
+ lastActivity = Date.now();
121
+ console.error("[pin-manager] Master PIN changed");
122
+ return { success: true };
123
+ }
124
+ /**
125
+ * Unlock cards with PIN
126
+ */
127
+ export function unlock(pin) {
128
+ const config = getPinConfig();
129
+ if (!config) {
130
+ return { success: false, error: "No PIN configured. Use setup_pin first." };
131
+ }
132
+ if (hashPin(pin, config.salt) !== config.pinHash) {
133
+ return { success: false, error: "Invalid PIN" };
134
+ }
135
+ currentPin = pin;
136
+ lastActivity = Date.now();
137
+ console.error("[pin-manager] Cards unlocked");
138
+ return {
139
+ success: true,
140
+ expiresIn: SESSION_TIMEOUT_MS / 1000 / 60 // in minutes
141
+ };
142
+ }
143
+ /**
144
+ * Lock cards (clear PIN from memory)
145
+ */
146
+ export function lock() {
147
+ currentPin = null;
148
+ lastActivity = 0;
149
+ console.error("[pin-manager] Cards locked");
150
+ }
151
+ /**
152
+ * Check if cards are currently unlocked
153
+ */
154
+ export function isUnlocked() {
155
+ if (!currentPin) {
156
+ return false;
157
+ }
158
+ // Check timeout
159
+ if (Date.now() - lastActivity > SESSION_TIMEOUT_MS) {
160
+ lock();
161
+ return false;
162
+ }
163
+ return true;
164
+ }
165
+ /**
166
+ * Refresh activity (extend session)
167
+ */
168
+ export function refreshActivity() {
169
+ if (currentPin) {
170
+ lastActivity = Date.now();
171
+ }
172
+ }
173
+ /**
174
+ * Get CVV encryption key (only works when unlocked)
175
+ */
176
+ export function getCvvEncryptionKey() {
177
+ if (!isUnlocked()) {
178
+ return null;
179
+ }
180
+ const config = getPinConfig();
181
+ if (!config || !currentPin) {
182
+ return null;
183
+ }
184
+ refreshActivity();
185
+ return deriveKeyFromPin(currentPin, config.cvvKeySalt);
186
+ }
187
+ /**
188
+ * Get status
189
+ */
190
+ export function getStatus() {
191
+ const configured = isPinConfigured();
192
+ const unlocked = isUnlocked();
193
+ let remainingMinutes;
194
+ if (unlocked && lastActivity > 0) {
195
+ const elapsed = Date.now() - lastActivity;
196
+ remainingMinutes = Math.ceil((SESSION_TIMEOUT_MS - elapsed) / 1000 / 60);
197
+ }
198
+ return { configured, unlocked, remainingMinutes };
199
+ }
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Crypto Wallet Management
3
+ *
4
+ * Secure storage for crypto wallets with encrypted private keys.
5
+ * Supports multiple chains and watch-only wallets.
6
+ *
7
+ * Security model:
8
+ * - Private keys encrypted at rest (same as card numbers)
9
+ * - Watch-only wallets for balance checking without spending
10
+ * - Transaction signing requires explicit confirmation
11
+ * - Support for hardware wallet addresses (no key storage)
12
+ */
13
+ export type Chain = "ethereum" | "polygon" | "arbitrum" | "optimism" | "base" | "avalanche" | "bsc" | "bitcoin" | "solana" | "starknet";
14
+ export type WalletType = "hot" | "watch-only" | "hardware";
15
+ export interface CryptoWallet {
16
+ id: string;
17
+ nickname: string;
18
+ address: string;
19
+ chain: Chain;
20
+ type: WalletType;
21
+ privateKey?: string;
22
+ mnemonic?: string;
23
+ hardwareType?: "ledger" | "trezor" | "other";
24
+ derivationPath?: string;
25
+ enabled: boolean;
26
+ allowedOperations: ("send" | "swap" | "approve" | "sign")[];
27
+ limits?: {
28
+ perTransaction?: number;
29
+ daily?: number;
30
+ tokenSymbol: string;
31
+ };
32
+ addedAt: string;
33
+ lastUsedAt?: string;
34
+ }
35
+ export interface CryptoTransaction {
36
+ id: string;
37
+ walletId: string;
38
+ chain: Chain;
39
+ type: "send" | "swap" | "approve" | "contract";
40
+ status: "pending" | "signed" | "broadcast" | "confirmed" | "failed";
41
+ from: string;
42
+ to: string;
43
+ value?: string;
44
+ tokenAddress?: string;
45
+ tokenSymbol?: string;
46
+ tokenAmount?: string;
47
+ gasLimit?: string;
48
+ gasPrice?: string;
49
+ maxFeePerGas?: string;
50
+ maxPriorityFeePerGas?: string;
51
+ txHash?: string;
52
+ blockNumber?: number;
53
+ createdAt: string;
54
+ signedAt?: string;
55
+ broadcastAt?: string;
56
+ confirmedAt?: string;
57
+ description: string;
58
+ }
59
+ export declare const CHAIN_CONFIG: Record<Chain, {
60
+ name: string;
61
+ nativeCurrency: string;
62
+ explorerUrl: string;
63
+ rpcEnvVar: string;
64
+ }>;
65
+ /**
66
+ * Validate Ethereum-like address
67
+ */
68
+ export declare function isValidEvmAddress(address: string): boolean;
69
+ /**
70
+ * Validate Bitcoin address (basic check)
71
+ */
72
+ export declare function isValidBtcAddress(address: string): boolean;
73
+ /**
74
+ * Validate Solana address
75
+ */
76
+ export declare function isValidSolanaAddress(address: string): boolean;
77
+ /**
78
+ * Validate address based on chain
79
+ */
80
+ export declare function isValidAddress(address: string, chain: Chain): boolean;
81
+ export declare function getWallets(): Promise<CryptoWallet[]>;
82
+ export declare function getWallet(id: string): Promise<CryptoWallet | null>;
83
+ /**
84
+ * Get wallets without sensitive data (for listing)
85
+ */
86
+ export declare function getWalletsSafe(): Promise<Omit<CryptoWallet, "privateKey" | "mnemonic">[]>;
87
+ export declare function addWallet(wallet: CryptoWallet): Promise<void>;
88
+ export declare function removeWallet(id: string): Promise<boolean>;
89
+ export declare function getCryptoTransactions(limit?: number): Promise<CryptoTransaction[]>;
90
+ export declare function logCryptoTransaction(tx: CryptoTransaction): Promise<void>;
91
+ export declare function updateCryptoTransaction(id: string, updates: Partial<CryptoTransaction>): Promise<void>;
@@ -0,0 +1,227 @@
1
+ /**
2
+ * Crypto Wallet Management
3
+ *
4
+ * Secure storage for crypto wallets with encrypted private keys.
5
+ * Supports multiple chains and watch-only wallets.
6
+ *
7
+ * Security model:
8
+ * - Private keys encrypted at rest (same as card numbers)
9
+ * - Watch-only wallets for balance checking without spending
10
+ * - Transaction signing requires explicit confirmation
11
+ * - Support for hardware wallet addresses (no key storage)
12
+ */
13
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
14
+ import { homedir } from "os";
15
+ import { join } from "path";
16
+ import { encrypt, decrypt, isEncrypted } from "./crypto";
17
+ // Sensitive fields that must be encrypted
18
+ const SENSITIVE_FIELDS = ["privateKey", "mnemonic"];
19
+ const CONFIG_DIR = join(homedir(), ".mcp-ecosystem");
20
+ const WALLETS_FILE = join(CONFIG_DIR, "crypto-wallets.json");
21
+ const CRYPTO_TX_FILE = join(CONFIG_DIR, "crypto-transactions.json");
22
+ // Chain configurations
23
+ export const CHAIN_CONFIG = {
24
+ ethereum: { name: "Ethereum", nativeCurrency: "ETH", explorerUrl: "https://etherscan.io", rpcEnvVar: "ETH_RPC_URL" },
25
+ polygon: { name: "Polygon", nativeCurrency: "MATIC", explorerUrl: "https://polygonscan.com", rpcEnvVar: "POLYGON_RPC_URL" },
26
+ arbitrum: { name: "Arbitrum", nativeCurrency: "ETH", explorerUrl: "https://arbiscan.io", rpcEnvVar: "ARBITRUM_RPC_URL" },
27
+ optimism: { name: "Optimism", nativeCurrency: "ETH", explorerUrl: "https://optimistic.etherscan.io", rpcEnvVar: "OPTIMISM_RPC_URL" },
28
+ base: { name: "Base", nativeCurrency: "ETH", explorerUrl: "https://basescan.org", rpcEnvVar: "BASE_RPC_URL" },
29
+ avalanche: { name: "Avalanche", nativeCurrency: "AVAX", explorerUrl: "https://snowtrace.io", rpcEnvVar: "AVAX_RPC_URL" },
30
+ bsc: { name: "BNB Chain", nativeCurrency: "BNB", explorerUrl: "https://bscscan.com", rpcEnvVar: "BSC_RPC_URL" },
31
+ bitcoin: { name: "Bitcoin", nativeCurrency: "BTC", explorerUrl: "https://mempool.space", rpcEnvVar: "BTC_RPC_URL" },
32
+ solana: { name: "Solana", nativeCurrency: "SOL", explorerUrl: "https://solscan.io", rpcEnvVar: "SOLANA_RPC_URL" },
33
+ starknet: { name: "Starknet", nativeCurrency: "ETH", explorerUrl: "https://starkscan.co", rpcEnvVar: "STARKNET_RPC_URL" },
34
+ };
35
+ /**
36
+ * Validate Ethereum-like address
37
+ */
38
+ export function isValidEvmAddress(address) {
39
+ return /^0x[a-fA-F0-9]{40}$/.test(address);
40
+ }
41
+ /**
42
+ * Validate Bitcoin address (basic check)
43
+ */
44
+ export function isValidBtcAddress(address) {
45
+ // Supports legacy (1...), SegWit (3...), and native SegWit (bc1...)
46
+ return /^(1|3)[a-km-zA-HJ-NP-Z1-9]{25,34}$/.test(address) || /^bc1[a-z0-9]{39,59}$/.test(address);
47
+ }
48
+ /**
49
+ * Validate Solana address
50
+ */
51
+ export function isValidSolanaAddress(address) {
52
+ return /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(address);
53
+ }
54
+ /**
55
+ * Validate address based on chain
56
+ */
57
+ export function isValidAddress(address, chain) {
58
+ if (chain === "bitcoin")
59
+ return isValidBtcAddress(address);
60
+ if (chain === "solana")
61
+ return isValidSolanaAddress(address);
62
+ // All EVM chains
63
+ return isValidEvmAddress(address);
64
+ }
65
+ function ensureConfigDir() {
66
+ if (!existsSync(CONFIG_DIR)) {
67
+ mkdirSync(CONFIG_DIR, { recursive: true });
68
+ }
69
+ }
70
+ /**
71
+ * Decrypt sensitive fields in a wallet
72
+ */
73
+ async function decryptWallet(wallet) {
74
+ const decrypted = { ...wallet };
75
+ for (const field of SENSITIVE_FIELDS) {
76
+ const value = decrypted[field];
77
+ if (value && typeof value === "string" && isEncrypted(value)) {
78
+ try {
79
+ decrypted[field] = await decrypt(value);
80
+ }
81
+ catch (err) {
82
+ console.error(`[wallets] Failed to decrypt ${field} for wallet ${wallet.nickname}`);
83
+ }
84
+ }
85
+ }
86
+ return decrypted;
87
+ }
88
+ /**
89
+ * Encrypt sensitive fields in a wallet
90
+ */
91
+ async function encryptWallet(wallet) {
92
+ const encrypted = { ...wallet };
93
+ for (const field of SENSITIVE_FIELDS) {
94
+ const value = encrypted[field];
95
+ if (value && typeof value === "string" && !isEncrypted(value)) {
96
+ encrypted[field] = await encrypt(value);
97
+ }
98
+ }
99
+ return encrypted;
100
+ }
101
+ // ============================================================================
102
+ // Wallet CRUD
103
+ // ============================================================================
104
+ export async function getWallets() {
105
+ ensureConfigDir();
106
+ if (!existsSync(WALLETS_FILE)) {
107
+ return [];
108
+ }
109
+ try {
110
+ const data = readFileSync(WALLETS_FILE, "utf-8");
111
+ const wallets = JSON.parse(data);
112
+ return Promise.all(wallets.map(decryptWallet));
113
+ }
114
+ catch {
115
+ return [];
116
+ }
117
+ }
118
+ export async function getWallet(id) {
119
+ const wallets = await getWallets();
120
+ return wallets.find((w) => w.id === id) ?? null;
121
+ }
122
+ /**
123
+ * Get wallets without sensitive data (for listing)
124
+ */
125
+ export async function getWalletsSafe() {
126
+ const wallets = await getWallets();
127
+ return wallets.map(({ privateKey, mnemonic, ...safe }) => safe);
128
+ }
129
+ export async function addWallet(wallet) {
130
+ ensureConfigDir();
131
+ // Validate address
132
+ if (!isValidAddress(wallet.address, wallet.chain)) {
133
+ throw new Error(`Invalid ${wallet.chain} address`);
134
+ }
135
+ wallet.addedAt = new Date().toISOString();
136
+ // Load existing
137
+ let rawWallets = [];
138
+ if (existsSync(WALLETS_FILE)) {
139
+ try {
140
+ rawWallets = JSON.parse(readFileSync(WALLETS_FILE, "utf-8"));
141
+ }
142
+ catch {
143
+ rawWallets = [];
144
+ }
145
+ }
146
+ // Encrypt and save
147
+ const encryptedWallet = await encryptWallet(wallet);
148
+ const existingIndex = rawWallets.findIndex((w) => w.id === wallet.id);
149
+ if (existingIndex >= 0) {
150
+ rawWallets[existingIndex] = encryptedWallet;
151
+ }
152
+ else {
153
+ rawWallets.push(encryptedWallet);
154
+ }
155
+ writeFileSync(WALLETS_FILE, JSON.stringify(rawWallets, null, 2), { mode: 0o600 });
156
+ console.error(`[wallets] Saved wallet ${wallet.nickname} (${wallet.address.slice(0, 8)}...) - ${wallet.type}`);
157
+ }
158
+ export async function removeWallet(id) {
159
+ if (!existsSync(WALLETS_FILE)) {
160
+ return false;
161
+ }
162
+ let rawWallets = [];
163
+ try {
164
+ rawWallets = JSON.parse(readFileSync(WALLETS_FILE, "utf-8"));
165
+ }
166
+ catch {
167
+ return false;
168
+ }
169
+ const filtered = rawWallets.filter((w) => w.id !== id);
170
+ if (filtered.length === rawWallets.length) {
171
+ return false;
172
+ }
173
+ writeFileSync(WALLETS_FILE, JSON.stringify(filtered, null, 2), { mode: 0o600 });
174
+ console.error(`[wallets] Removed wallet ${id}`);
175
+ return true;
176
+ }
177
+ // ============================================================================
178
+ // Crypto Transaction Log
179
+ // ============================================================================
180
+ export async function getCryptoTransactions(limit = 50) {
181
+ ensureConfigDir();
182
+ if (!existsSync(CRYPTO_TX_FILE)) {
183
+ return [];
184
+ }
185
+ try {
186
+ const data = readFileSync(CRYPTO_TX_FILE, "utf-8");
187
+ const transactions = JSON.parse(data);
188
+ return transactions.slice(-limit);
189
+ }
190
+ catch {
191
+ return [];
192
+ }
193
+ }
194
+ export async function logCryptoTransaction(tx) {
195
+ ensureConfigDir();
196
+ let transactions = [];
197
+ if (existsSync(CRYPTO_TX_FILE)) {
198
+ try {
199
+ transactions = JSON.parse(readFileSync(CRYPTO_TX_FILE, "utf-8"));
200
+ }
201
+ catch {
202
+ transactions = [];
203
+ }
204
+ }
205
+ transactions.push(tx);
206
+ // Keep last 1000 transactions
207
+ if (transactions.length > 1000) {
208
+ transactions = transactions.slice(-1000);
209
+ }
210
+ writeFileSync(CRYPTO_TX_FILE, JSON.stringify(transactions, null, 2), { mode: 0o600 });
211
+ }
212
+ export async function updateCryptoTransaction(id, updates) {
213
+ if (!existsSync(CRYPTO_TX_FILE))
214
+ return;
215
+ let transactions = [];
216
+ try {
217
+ transactions = JSON.parse(readFileSync(CRYPTO_TX_FILE, "utf-8"));
218
+ }
219
+ catch {
220
+ return;
221
+ }
222
+ const idx = transactions.findIndex((t) => t.id === id);
223
+ if (idx >= 0) {
224
+ transactions[idx] = { ...transactions[idx], ...updates };
225
+ writeFileSync(CRYPTO_TX_FILE, JSON.stringify(transactions, null, 2), { mode: 0o600 });
226
+ }
227
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Add a new payment card (encrypted storage)
3
+ */
4
+ import { z } from "zod";
5
+ export declare const name = "add_card";
6
+ export declare const description = "Add a new payment card. Card number and expiration are encrypted. CVV is encrypted with your master PIN (requires unlock).";
7
+ export declare const parameters: z.ZodObject<{
8
+ nickname: z.ZodString;
9
+ card_number: z.ZodString;
10
+ expiration: z.ZodString;
11
+ cvv: z.ZodString;
12
+ cardholder_name: z.ZodString;
13
+ allowed_usage: z.ZodOptional<z.ZodArray<z.ZodEnum<["flight", "train", "hotel", "general", "all"]>, "many">>;
14
+ billing_street: z.ZodOptional<z.ZodString>;
15
+ billing_city: z.ZodOptional<z.ZodString>;
16
+ billing_postal_code: z.ZodOptional<z.ZodString>;
17
+ billing_country: z.ZodOptional<z.ZodString>;
18
+ per_transaction_limit: z.ZodOptional<z.ZodNumber>;
19
+ daily_limit: z.ZodOptional<z.ZodNumber>;
20
+ monthly_limit: z.ZodOptional<z.ZodNumber>;
21
+ limit_currency: z.ZodOptional<z.ZodString>;
22
+ }, "strip", z.ZodTypeAny, {
23
+ cvv: string;
24
+ nickname: string;
25
+ card_number: string;
26
+ expiration: string;
27
+ cardholder_name: string;
28
+ allowed_usage?: ("flight" | "train" | "hotel" | "general" | "all")[] | undefined;
29
+ billing_street?: string | undefined;
30
+ billing_city?: string | undefined;
31
+ billing_postal_code?: string | undefined;
32
+ billing_country?: string | undefined;
33
+ per_transaction_limit?: number | undefined;
34
+ daily_limit?: number | undefined;
35
+ monthly_limit?: number | undefined;
36
+ limit_currency?: string | undefined;
37
+ }, {
38
+ cvv: string;
39
+ nickname: string;
40
+ card_number: string;
41
+ expiration: string;
42
+ cardholder_name: string;
43
+ allowed_usage?: ("flight" | "train" | "hotel" | "general" | "all")[] | undefined;
44
+ billing_street?: string | undefined;
45
+ billing_city?: string | undefined;
46
+ billing_postal_code?: string | undefined;
47
+ billing_country?: string | undefined;
48
+ per_transaction_limit?: number | undefined;
49
+ daily_limit?: number | undefined;
50
+ monthly_limit?: number | undefined;
51
+ limit_currency?: string | undefined;
52
+ }>;
53
+ export declare function execute(args: z.infer<typeof parameters>): Promise<{
54
+ success: boolean;
55
+ error: string;
56
+ message?: undefined;
57
+ card_id?: undefined;
58
+ security?: undefined;
59
+ } | {
60
+ success: boolean;
61
+ message: string;
62
+ card_id: `${string}-${string}-${string}-${string}-${string}`;
63
+ security: string;
64
+ error?: undefined;
65
+ }>;
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Add a new payment card (encrypted storage)
3
+ */
4
+ import { z } from "zod";
5
+ import { randomUUID } from "crypto";
6
+ import { addCard } from "../lib/cards";
7
+ import { isUnlocked, isPinConfigured } from "../lib/pin-manager";
8
+ import { encryptCvv } from "../lib/cvv-crypto";
9
+ export const name = "add_card";
10
+ export const description = "Add a new payment card. Card number and expiration are encrypted. CVV is encrypted with your master PIN (requires unlock).";
11
+ export const parameters = z.object({
12
+ nickname: z.string().describe("Friendly name for the card (e.g., 'Visa perso', 'Amex pro')"),
13
+ card_number: z.string().describe("Full card number (will be encrypted)"),
14
+ expiration: z.string().describe("Expiration date in MM/YY format"),
15
+ cvv: z.string().describe("CVV/CVC (3-4 digits) - encrypted with your PIN, never shown"),
16
+ cardholder_name: z.string().describe("Name as shown on the card"),
17
+ allowed_usage: z.array(z.enum(["flight", "train", "hotel", "general", "all"])).optional()
18
+ .describe("What this card can be used for (default: all)"),
19
+ billing_street: z.string().optional().describe("Billing address street"),
20
+ billing_city: z.string().optional().describe("Billing address city"),
21
+ billing_postal_code: z.string().optional().describe("Billing postal code"),
22
+ billing_country: z.string().optional().describe("Billing country (2-letter code)"),
23
+ per_transaction_limit: z.number().optional().describe("Max amount per transaction"),
24
+ daily_limit: z.number().optional().describe("Max daily spending"),
25
+ monthly_limit: z.number().optional().describe("Max monthly spending"),
26
+ limit_currency: z.string().optional().describe("Currency for limits (default: EUR)"),
27
+ });
28
+ export async function execute(args) {
29
+ try {
30
+ // Check PIN is configured and unlocked
31
+ if (!isPinConfigured()) {
32
+ return {
33
+ success: false,
34
+ error: "Master PIN not configured. Use setup_pin first.",
35
+ };
36
+ }
37
+ if (!isUnlocked()) {
38
+ return {
39
+ success: false,
40
+ error: "Cards are locked. Use unlock_cards with your PIN first.",
41
+ };
42
+ }
43
+ // Validate CVV format
44
+ if (!/^\d{3,4}$/.test(args.cvv)) {
45
+ return {
46
+ success: false,
47
+ error: "Invalid CVV format (must be 3-4 digits)",
48
+ };
49
+ }
50
+ // Encrypt CVV with PIN-derived key
51
+ const encryptedCvv = encryptCvv(args.cvv);
52
+ if (!encryptedCvv) {
53
+ return {
54
+ success: false,
55
+ error: "Failed to encrypt CVV. Make sure cards are unlocked.",
56
+ };
57
+ }
58
+ const cardId = randomUUID();
59
+ await addCard({
60
+ id: cardId,
61
+ nickname: args.nickname,
62
+ cardNumber: args.card_number,
63
+ expirationDate: args.expiration,
64
+ cvv: encryptedCvv,
65
+ cardholderName: args.cardholder_name,
66
+ cardType: "other", // Will be auto-detected
67
+ lastFourDigits: "", // Will be set from card number
68
+ allowedUsage: args.allowed_usage ?? ["all"],
69
+ enabled: true,
70
+ billingAddress: args.billing_street ? {
71
+ street: args.billing_street,
72
+ city: args.billing_city ?? "",
73
+ postalCode: args.billing_postal_code ?? "",
74
+ country: args.billing_country ?? "FR",
75
+ } : undefined,
76
+ limits: args.per_transaction_limit || args.daily_limit || args.monthly_limit ? {
77
+ perTransaction: args.per_transaction_limit,
78
+ daily: args.daily_limit,
79
+ monthly: args.monthly_limit,
80
+ currency: args.limit_currency ?? "EUR",
81
+ } : undefined,
82
+ addedAt: new Date().toISOString(),
83
+ });
84
+ return {
85
+ success: true,
86
+ message: `Card "${args.nickname}" added successfully`,
87
+ card_id: cardId,
88
+ security: "Card number and CVV encrypted. CVV protected by your master PIN.",
89
+ };
90
+ }
91
+ catch (error) {
92
+ return {
93
+ success: false,
94
+ error: error instanceof Error ? error.message : "Failed to add card",
95
+ };
96
+ }
97
+ }