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.
- package/.env.example +23 -0
- package/.github/workflows/ci.yml +26 -0
- package/.github/workflows/release.yml +82 -0
- package/LICENSE +21 -0
- package/README.md +362 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.js +122 -0
- package/dist/lib/blockchain.d.ts +50 -0
- package/dist/lib/blockchain.js +287 -0
- package/dist/lib/cards.d.ts +83 -0
- package/dist/lib/cards.js +276 -0
- package/dist/lib/cli-runner.d.ts +31 -0
- package/dist/lib/cli-runner.js +77 -0
- package/dist/lib/crypto.d.ts +39 -0
- package/dist/lib/crypto.js +228 -0
- package/dist/lib/cvv-crypto.d.ts +23 -0
- package/dist/lib/cvv-crypto.js +67 -0
- package/dist/lib/mcp-core.d.ts +46 -0
- package/dist/lib/mcp-core.js +86 -0
- package/dist/lib/pin-manager.d.ts +69 -0
- package/dist/lib/pin-manager.js +199 -0
- package/dist/lib/wallets.d.ts +91 -0
- package/dist/lib/wallets.js +227 -0
- package/dist/tools/add-card.d.ts +65 -0
- package/dist/tools/add-card.js +97 -0
- package/dist/tools/add-wallet.d.ts +65 -0
- package/dist/tools/add-wallet.js +104 -0
- package/dist/tools/card-status.d.ts +20 -0
- package/dist/tools/card-status.js +26 -0
- package/dist/tools/confirm-payment.d.ts +44 -0
- package/dist/tools/confirm-payment.js +88 -0
- package/dist/tools/get-total-balance.d.ts +41 -0
- package/dist/tools/get-total-balance.js +98 -0
- package/dist/tools/get-transactions.d.ts +39 -0
- package/dist/tools/get-transactions.js +40 -0
- package/dist/tools/get-wallet-balance.d.ts +43 -0
- package/dist/tools/get-wallet-balance.js +69 -0
- package/dist/tools/list-cards.d.ts +36 -0
- package/dist/tools/list-cards.js +39 -0
- package/dist/tools/list-wallet-transactions.d.ts +63 -0
- package/dist/tools/list-wallet-transactions.js +76 -0
- package/dist/tools/list-wallets.d.ts +41 -0
- package/dist/tools/list-wallets.js +50 -0
- package/dist/tools/lock-cards.d.ts +16 -0
- package/dist/tools/lock-cards.js +23 -0
- package/dist/tools/prepare-crypto-tx.d.ts +69 -0
- package/dist/tools/prepare-crypto-tx.js +93 -0
- package/dist/tools/prepare-payment.d.ts +57 -0
- package/dist/tools/prepare-payment.js +93 -0
- package/dist/tools/remove-card.d.ts +25 -0
- package/dist/tools/remove-card.js +39 -0
- package/dist/tools/remove-wallet.d.ts +27 -0
- package/dist/tools/remove-wallet.js +40 -0
- package/dist/tools/setup-pin.d.ts +26 -0
- package/dist/tools/setup-pin.js +33 -0
- package/dist/tools/sign-crypto-tx.d.ts +42 -0
- package/dist/tools/sign-crypto-tx.js +75 -0
- package/dist/tools/unlock-cards.d.ts +35 -0
- package/dist/tools/unlock-cards.js +41 -0
- package/package.json +50 -0
- package/src/index.ts +139 -0
- package/src/lib/blockchain.ts +375 -0
- package/src/lib/cards.ts +372 -0
- package/src/lib/cli-runner.ts +113 -0
- package/src/lib/crypto.ts +284 -0
- package/src/lib/cvv-crypto.ts +81 -0
- package/src/lib/mcp-core.ts +127 -0
- package/src/lib/pin-manager.ts +252 -0
- package/src/lib/wallets.ts +331 -0
- package/src/tools/add-card.ts +108 -0
- package/src/tools/add-wallet.ts +114 -0
- package/src/tools/card-status.ts +32 -0
- package/src/tools/confirm-payment.ts +103 -0
- package/src/tools/get-total-balance.ts +123 -0
- package/src/tools/get-transactions.ts +49 -0
- package/src/tools/get-wallet-balance.ts +75 -0
- package/src/tools/list-cards.ts +52 -0
- package/src/tools/list-wallet-transactions.ts +83 -0
- package/src/tools/list-wallets.ts +63 -0
- package/src/tools/lock-cards.ts +31 -0
- package/src/tools/prepare-crypto-tx.ts +108 -0
- package/src/tools/prepare-payment.ts +108 -0
- package/src/tools/remove-card.ts +46 -0
- package/src/tools/remove-wallet.ts +47 -0
- package/src/tools/setup-pin.ts +39 -0
- package/src/tools/sign-crypto-tx.ts +90 -0
- package/src/tools/unlock-cards.ts +48 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Add a new crypto wallet
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { randomUUID } from "crypto";
|
|
7
|
+
import { addWallet } from "../lib/wallets";
|
|
8
|
+
import type { WalletType } from "../lib/wallets";
|
|
9
|
+
|
|
10
|
+
export const name = "add_wallet";
|
|
11
|
+
|
|
12
|
+
export const description = `Add a new crypto wallet. Supports hot wallets (with private key - encrypted), watch-only (address only), and hardware wallets (address + device info).
|
|
13
|
+
|
|
14
|
+
For hot wallets: private key will be encrypted at rest.
|
|
15
|
+
For watch-only: only the address is stored, no spending capability.
|
|
16
|
+
For hardware: address is stored, transactions require hardware signing.`;
|
|
17
|
+
|
|
18
|
+
export const parameters = z.object({
|
|
19
|
+
nickname: z.string().describe("Friendly name for the wallet (e.g., 'ETH principal', 'Trading wallet')"),
|
|
20
|
+
address: z.string().describe("Public wallet address"),
|
|
21
|
+
chain: z.enum(["ethereum", "polygon", "arbitrum", "optimism", "base", "avalanche", "bsc", "bitcoin", "solana", "starknet"])
|
|
22
|
+
.describe("Blockchain network"),
|
|
23
|
+
type: z.enum(["hot", "watch-only", "hardware"]).describe("Wallet type"),
|
|
24
|
+
private_key: z.string().optional().describe("Private key (for hot wallets only - will be encrypted)"),
|
|
25
|
+
mnemonic: z.string().optional().describe("Seed phrase (for hot wallets only - will be encrypted)"),
|
|
26
|
+
hardware_type: z.enum(["ledger", "trezor", "other"]).optional().describe("Hardware wallet type"),
|
|
27
|
+
derivation_path: z.string().optional().describe("Derivation path (e.g., m/44'/60'/0'/0/0)"),
|
|
28
|
+
allowed_operations: z.array(z.enum(["send", "swap", "approve", "sign"])).optional()
|
|
29
|
+
.describe("Allowed operations (default: all for hot, none for watch-only)"),
|
|
30
|
+
per_transaction_limit: z.number().optional().describe("Max amount per transaction"),
|
|
31
|
+
daily_limit: z.number().optional().describe("Max daily spending"),
|
|
32
|
+
limit_token: z.string().optional().describe("Token symbol for limits (e.g., 'ETH', 'USDC')"),
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
export async function execute(args: z.infer<typeof parameters>) {
|
|
36
|
+
try {
|
|
37
|
+
// Validate based on type
|
|
38
|
+
if (args.type === "hot" && !args.private_key && !args.mnemonic) {
|
|
39
|
+
return {
|
|
40
|
+
success: false,
|
|
41
|
+
error: "Hot wallets require either a private_key or mnemonic",
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (args.type === "watch-only" && (args.private_key || args.mnemonic)) {
|
|
46
|
+
return {
|
|
47
|
+
success: false,
|
|
48
|
+
error: "Watch-only wallets cannot have private keys",
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (args.type === "hardware" && !args.hardware_type) {
|
|
53
|
+
return {
|
|
54
|
+
success: false,
|
|
55
|
+
error: "Hardware wallets must specify hardware_type",
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const walletId = randomUUID();
|
|
60
|
+
|
|
61
|
+
// Default operations based on type
|
|
62
|
+
let operations = args.allowed_operations;
|
|
63
|
+
if (!operations) {
|
|
64
|
+
if (args.type === "hot") {
|
|
65
|
+
operations = ["send", "swap", "approve", "sign"];
|
|
66
|
+
} else if (args.type === "hardware") {
|
|
67
|
+
operations = ["send", "swap", "approve", "sign"]; // Requires hardware confirmation
|
|
68
|
+
} else {
|
|
69
|
+
operations = []; // Watch-only can't do anything
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
await addWallet({
|
|
74
|
+
id: walletId,
|
|
75
|
+
nickname: args.nickname,
|
|
76
|
+
address: args.address,
|
|
77
|
+
chain: args.chain,
|
|
78
|
+
type: args.type as WalletType,
|
|
79
|
+
privateKey: args.private_key,
|
|
80
|
+
mnemonic: args.mnemonic,
|
|
81
|
+
hardwareType: args.hardware_type,
|
|
82
|
+
derivationPath: args.derivation_path,
|
|
83
|
+
enabled: true,
|
|
84
|
+
allowedOperations: operations,
|
|
85
|
+
limits: args.per_transaction_limit || args.daily_limit ? {
|
|
86
|
+
perTransaction: args.per_transaction_limit,
|
|
87
|
+
daily: args.daily_limit,
|
|
88
|
+
tokenSymbol: args.limit_token ?? "ETH",
|
|
89
|
+
} : undefined,
|
|
90
|
+
addedAt: new Date().toISOString(),
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const typeInfo = {
|
|
94
|
+
hot: "Private key encrypted and stored securely",
|
|
95
|
+
"watch-only": "Address only - no spending capability",
|
|
96
|
+
hardware: `${args.hardware_type} wallet - requires device for signing`,
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
success: true,
|
|
101
|
+
message: `Wallet "${args.nickname}" added successfully`,
|
|
102
|
+
wallet_id: walletId,
|
|
103
|
+
type: args.type,
|
|
104
|
+
chain: args.chain,
|
|
105
|
+
address: `${args.address.slice(0, 10)}...${args.address.slice(-8)}`,
|
|
106
|
+
security_note: typeInfo[args.type as WalletType],
|
|
107
|
+
};
|
|
108
|
+
} catch (error) {
|
|
109
|
+
return {
|
|
110
|
+
success: false,
|
|
111
|
+
error: error instanceof Error ? error.message : "Failed to add wallet",
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get card lock status
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { getStatus, isPinConfigured } from "../lib/pin-manager";
|
|
7
|
+
import { getCardsSafe } from "../lib/cards";
|
|
8
|
+
|
|
9
|
+
export const name = "card_status";
|
|
10
|
+
|
|
11
|
+
export const description = "Check if cards are locked or unlocked, and how many cards are saved.";
|
|
12
|
+
|
|
13
|
+
export const parameters = z.object({});
|
|
14
|
+
|
|
15
|
+
export async function execute(_args: z.infer<typeof parameters>) {
|
|
16
|
+
const pinStatus = getStatus();
|
|
17
|
+
const cards = await getCardsSafe();
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
success: true,
|
|
21
|
+
pin_configured: pinStatus.configured,
|
|
22
|
+
cards_unlocked: pinStatus.unlocked,
|
|
23
|
+
remaining_minutes: pinStatus.remainingMinutes ?? null,
|
|
24
|
+
saved_cards: cards.length,
|
|
25
|
+
cards: cards.map((c) => ({
|
|
26
|
+
nickname: c.nickname,
|
|
27
|
+
type: c.cardType.toUpperCase(),
|
|
28
|
+
lastFour: `****${c.lastFourDigits}`,
|
|
29
|
+
enabled: c.enabled,
|
|
30
|
+
})),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Confirm and execute a prepared payment
|
|
3
|
+
*
|
|
4
|
+
* This is where the actual charge would happen.
|
|
5
|
+
* CVV is retrieved from encrypted storage (requires PIN unlock).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
import { getCard, getCardCvv, getTransactions, updateTransaction, updateCardUsage } from "../lib/cards";
|
|
10
|
+
import { isUnlocked } from "../lib/pin-manager";
|
|
11
|
+
|
|
12
|
+
export const name = "confirm_payment";
|
|
13
|
+
|
|
14
|
+
export const description = "Confirm and execute a prepared payment. Cards must be unlocked with your PIN. CVV is retrieved from secure storage.";
|
|
15
|
+
|
|
16
|
+
export const parameters = z.object({
|
|
17
|
+
transaction_id: z.string().describe("ID of the pending transaction to confirm"),
|
|
18
|
+
confirm: z.boolean().describe("Must be true to confirm you want to proceed with the charge"),
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export async function execute(args: z.infer<typeof parameters>) {
|
|
22
|
+
if (!args.confirm) {
|
|
23
|
+
return {
|
|
24
|
+
success: false,
|
|
25
|
+
error: "You must set confirm=true to proceed with the payment",
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Check cards are unlocked
|
|
30
|
+
if (!isUnlocked()) {
|
|
31
|
+
return {
|
|
32
|
+
success: false,
|
|
33
|
+
error: "Cards are locked. Use unlock_cards with your PIN first.",
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Find the pending transaction
|
|
38
|
+
const transactions = await getTransactions(100);
|
|
39
|
+
const tx = transactions.find((t) => t.id === args.transaction_id);
|
|
40
|
+
|
|
41
|
+
if (!tx) {
|
|
42
|
+
return {
|
|
43
|
+
success: false,
|
|
44
|
+
error: "Transaction not found",
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (tx.status !== "pending") {
|
|
49
|
+
return {
|
|
50
|
+
success: false,
|
|
51
|
+
error: `Transaction is already ${tx.status}`,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Get the card
|
|
56
|
+
const card = await getCard(tx.cardId);
|
|
57
|
+
if (!card) {
|
|
58
|
+
return {
|
|
59
|
+
success: false,
|
|
60
|
+
error: "Card no longer exists",
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Get CVV from encrypted storage
|
|
65
|
+
const cvv = await getCardCvv(tx.cardId);
|
|
66
|
+
if (!cvv) {
|
|
67
|
+
return {
|
|
68
|
+
success: false,
|
|
69
|
+
error: "Could not retrieve CVV. Make sure cards are unlocked.",
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Here we would integrate with actual payment providers
|
|
74
|
+
// CVV is available in `cvv` variable for the API call
|
|
75
|
+
// For now, we mark as confirmed and return what would be needed
|
|
76
|
+
|
|
77
|
+
await updateTransaction(tx.id, {
|
|
78
|
+
status: "confirmed",
|
|
79
|
+
confirmedAt: new Date().toISOString(),
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
await updateCardUsage(card.id);
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
success: true,
|
|
86
|
+
status: "confirmed",
|
|
87
|
+
transaction_id: tx.id,
|
|
88
|
+
message: `Payment of ${tx.amount} ${tx.currency} confirmed`,
|
|
89
|
+
summary: {
|
|
90
|
+
card: `${card.nickname} (****${card.lastFourDigits})`,
|
|
91
|
+
amount: `${tx.amount} ${tx.currency}`,
|
|
92
|
+
type: tx.type,
|
|
93
|
+
description: tx.description,
|
|
94
|
+
provider: tx.provider,
|
|
95
|
+
},
|
|
96
|
+
// In production, this would return actual booking reference
|
|
97
|
+
note: "Integration with payment provider needed. Transaction marked as confirmed.",
|
|
98
|
+
next_steps: [
|
|
99
|
+
"Integrate with Stripe/payment processor for actual charge",
|
|
100
|
+
"Integrate with booking APIs (Amadeus, Trainline, etc.) for reservations",
|
|
101
|
+
],
|
|
102
|
+
};
|
|
103
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get consolidated view of all balances
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { getCardsSafe } from "../lib/cards";
|
|
7
|
+
import { getWalletsSafe, type Chain } from "../lib/wallets";
|
|
8
|
+
import { getWalletBalance, getUsdPrice } from "../lib/blockchain";
|
|
9
|
+
|
|
10
|
+
export const name = "get_total_balance";
|
|
11
|
+
|
|
12
|
+
export const description = "Get a consolidated view of all financial accounts: cards and crypto wallets. Shows individual and total balances.";
|
|
13
|
+
|
|
14
|
+
export const parameters = z.object({
|
|
15
|
+
include_disabled: z.boolean().optional().describe("Include disabled accounts (default: false)"),
|
|
16
|
+
crypto_only: z.boolean().optional().describe("Only show crypto wallets"),
|
|
17
|
+
cards_only: z.boolean().optional().describe("Only show payment cards"),
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
interface BalanceItem {
|
|
21
|
+
id: string;
|
|
22
|
+
type: "card" | "wallet";
|
|
23
|
+
name: string;
|
|
24
|
+
chain?: string;
|
|
25
|
+
balance?: string;
|
|
26
|
+
currency?: string;
|
|
27
|
+
usdValue?: number;
|
|
28
|
+
status: "active" | "disabled" | "error";
|
|
29
|
+
error?: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function execute(args: z.infer<typeof parameters>) {
|
|
33
|
+
const includeDisabled = args.include_disabled ?? false;
|
|
34
|
+
const items: BalanceItem[] = [];
|
|
35
|
+
let totalUsdValue = 0;
|
|
36
|
+
|
|
37
|
+
// Get cards (if not crypto_only)
|
|
38
|
+
if (!args.crypto_only) {
|
|
39
|
+
const cards = await getCardsSafe();
|
|
40
|
+
const filteredCards = includeDisabled ? cards : cards.filter(c => c.enabled);
|
|
41
|
+
|
|
42
|
+
for (const card of filteredCards) {
|
|
43
|
+
items.push({
|
|
44
|
+
id: card.id,
|
|
45
|
+
type: "card",
|
|
46
|
+
name: `${card.nickname} (****${card.lastFourDigits})`,
|
|
47
|
+
currency: card.limits?.currency || "EUR",
|
|
48
|
+
balance: "N/A", // Requires bank integration
|
|
49
|
+
status: card.enabled ? "active" : "disabled",
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Get wallets (if not cards_only)
|
|
55
|
+
if (!args.cards_only) {
|
|
56
|
+
const wallets = await getWalletsSafe();
|
|
57
|
+
const filteredWallets = includeDisabled ? wallets : wallets.filter(w => w.enabled);
|
|
58
|
+
|
|
59
|
+
// Fetch balances in parallel
|
|
60
|
+
const balancePromises = filteredWallets.map(async (wallet) => {
|
|
61
|
+
try {
|
|
62
|
+
const balance = await getWalletBalance(wallet.address, wallet.chain as Chain);
|
|
63
|
+
const price = await getUsdPrice(balance.nativeCurrency);
|
|
64
|
+
const usdValue = price ? parseFloat(balance.nativeBalanceFormatted) * price : undefined;
|
|
65
|
+
|
|
66
|
+
if (usdValue) {
|
|
67
|
+
totalUsdValue += usdValue;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
id: wallet.id,
|
|
72
|
+
type: "wallet" as const,
|
|
73
|
+
name: wallet.nickname,
|
|
74
|
+
chain: wallet.chain,
|
|
75
|
+
balance: balance.nativeBalanceFormatted,
|
|
76
|
+
currency: balance.nativeCurrency,
|
|
77
|
+
usdValue,
|
|
78
|
+
status: wallet.enabled ? "active" as const : "disabled" as const,
|
|
79
|
+
};
|
|
80
|
+
} catch (err) {
|
|
81
|
+
return {
|
|
82
|
+
id: wallet.id,
|
|
83
|
+
type: "wallet" as const,
|
|
84
|
+
name: wallet.nickname,
|
|
85
|
+
chain: wallet.chain,
|
|
86
|
+
status: "error" as const,
|
|
87
|
+
error: err instanceof Error ? err.message : "Failed to fetch balance",
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const walletResults = await Promise.all(balancePromises);
|
|
93
|
+
items.push(...walletResults);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Separate by type for summary
|
|
97
|
+
const cardItems = items.filter(i => i.type === "card");
|
|
98
|
+
const walletItems = items.filter(i => i.type === "wallet");
|
|
99
|
+
const errorItems = items.filter(i => i.status === "error");
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
success: true,
|
|
103
|
+
summary: {
|
|
104
|
+
totalAccounts: items.length,
|
|
105
|
+
cards: cardItems.length,
|
|
106
|
+
wallets: walletItems.length,
|
|
107
|
+
errors: errorItems.length,
|
|
108
|
+
totalCryptoUsd: totalUsdValue > 0 ? `$${totalUsdValue.toFixed(2)}` : null,
|
|
109
|
+
note: cardItems.length > 0 ? "Card balances require bank integration (Plaid/Tink)" : undefined,
|
|
110
|
+
},
|
|
111
|
+
accounts: items.map(item => ({
|
|
112
|
+
id: item.id,
|
|
113
|
+
type: item.type,
|
|
114
|
+
name: item.name,
|
|
115
|
+
chain: item.chain,
|
|
116
|
+
balance: item.balance || "N/A",
|
|
117
|
+
currency: item.currency,
|
|
118
|
+
usdValue: item.usdValue ? `$${item.usdValue.toFixed(2)}` : null,
|
|
119
|
+
status: item.status,
|
|
120
|
+
...(item.error && { error: item.error }),
|
|
121
|
+
})),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get transaction history
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { getTransactions } from "../lib/cards";
|
|
7
|
+
|
|
8
|
+
export const name = "get_transactions";
|
|
9
|
+
|
|
10
|
+
export const description = "Get payment transaction history. Shows all bookings and payments made through the system.";
|
|
11
|
+
|
|
12
|
+
export const parameters = z.object({
|
|
13
|
+
limit: z.number().optional().describe("Number of transactions to return (default: 20)"),
|
|
14
|
+
status: z.enum(["pending", "confirmed", "completed", "failed", "refunded"]).optional()
|
|
15
|
+
.describe("Filter by transaction status"),
|
|
16
|
+
type: z.enum(["flight", "train", "hotel", "other"]).optional()
|
|
17
|
+
.describe("Filter by transaction type"),
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
export async function execute(args: z.infer<typeof parameters>) {
|
|
21
|
+
let transactions = await getTransactions(args.limit ?? 20);
|
|
22
|
+
|
|
23
|
+
if (args.status) {
|
|
24
|
+
transactions = transactions.filter((t) => t.status === args.status);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (args.type) {
|
|
28
|
+
transactions = transactions.filter((t) => t.type === args.type);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (transactions.length === 0) {
|
|
32
|
+
return { success: true, transactions: [], message: "No transactions found" };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
success: true,
|
|
37
|
+
count: transactions.length,
|
|
38
|
+
transactions: transactions.map((t) => ({
|
|
39
|
+
id: t.id,
|
|
40
|
+
type: t.type,
|
|
41
|
+
amount: `${t.amount} ${t.currency}`,
|
|
42
|
+
description: t.description,
|
|
43
|
+
provider: t.provider,
|
|
44
|
+
status: t.status,
|
|
45
|
+
reference: t.reference ?? "N/A",
|
|
46
|
+
date: t.createdAt,
|
|
47
|
+
})),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get wallet balance via blockchain API
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { getWallet, getWalletsSafe, type Chain } from "../lib/wallets";
|
|
7
|
+
import { getWalletBalance, getUsdPrice } from "../lib/blockchain";
|
|
8
|
+
|
|
9
|
+
export const name = "get_wallet_balance";
|
|
10
|
+
|
|
11
|
+
export const description = "Get the current balance of a crypto wallet. Fetches live data from the blockchain.";
|
|
12
|
+
|
|
13
|
+
export const parameters = z.object({
|
|
14
|
+
wallet_id: z.string().optional().describe("Wallet ID (use list_wallets to see available wallets)"),
|
|
15
|
+
address: z.string().optional().describe("Wallet address (alternative to wallet_id)"),
|
|
16
|
+
chain: z.enum(["ethereum", "polygon", "arbitrum", "optimism", "base", "avalanche", "bsc", "bitcoin", "solana", "starknet"]).optional()
|
|
17
|
+
.describe("Blockchain (required if using address)"),
|
|
18
|
+
include_usd: z.boolean().optional().describe("Include USD value conversion (default: true)"),
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export async function execute(args: z.infer<typeof parameters>) {
|
|
22
|
+
let address: string;
|
|
23
|
+
let chain: Chain;
|
|
24
|
+
|
|
25
|
+
// Get wallet by ID or use provided address
|
|
26
|
+
if (args.wallet_id) {
|
|
27
|
+
const wallet = await getWallet(args.wallet_id);
|
|
28
|
+
if (!wallet) {
|
|
29
|
+
return { success: false, error: `Wallet not found: ${args.wallet_id}` };
|
|
30
|
+
}
|
|
31
|
+
address = wallet.address;
|
|
32
|
+
chain = wallet.chain;
|
|
33
|
+
} else if (args.address && args.chain) {
|
|
34
|
+
address = args.address;
|
|
35
|
+
chain = args.chain;
|
|
36
|
+
} else {
|
|
37
|
+
return {
|
|
38
|
+
success: false,
|
|
39
|
+
error: "Provide either wallet_id or both address and chain"
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
const balance = await getWalletBalance(address, chain);
|
|
45
|
+
|
|
46
|
+
// Get USD price if requested
|
|
47
|
+
let usdValue: number | null = null;
|
|
48
|
+
if (args.include_usd !== false) {
|
|
49
|
+
const price = await getUsdPrice(balance.nativeCurrency);
|
|
50
|
+
if (price) {
|
|
51
|
+
usdValue = parseFloat(balance.nativeBalanceFormatted) * price;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
success: true,
|
|
57
|
+
wallet: {
|
|
58
|
+
address: `${address.slice(0, 10)}...${address.slice(-8)}`,
|
|
59
|
+
fullAddress: address,
|
|
60
|
+
chain,
|
|
61
|
+
},
|
|
62
|
+
balance: {
|
|
63
|
+
native: balance.nativeBalanceFormatted,
|
|
64
|
+
currency: balance.nativeCurrency,
|
|
65
|
+
raw: balance.nativeBalance,
|
|
66
|
+
usdValue: usdValue ? `$${usdValue.toFixed(2)}` : null,
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
} catch (err) {
|
|
70
|
+
return {
|
|
71
|
+
success: false,
|
|
72
|
+
error: `Failed to fetch balance: ${err instanceof Error ? err.message : err}`,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* List saved payment cards (safe view - no full card numbers)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { getCardsSafe } from "../lib/cards";
|
|
7
|
+
|
|
8
|
+
export const name = "list_cards";
|
|
9
|
+
|
|
10
|
+
export const description = "List all saved payment cards. Shows card type, last 4 digits, nickname, and allowed usage. Does NOT show full card numbers.";
|
|
11
|
+
|
|
12
|
+
export const parameters = z.object({
|
|
13
|
+
enabled_only: z.boolean().optional().describe("Only show enabled cards"),
|
|
14
|
+
usage_type: z.enum(["flight", "train", "hotel", "general", "all"]).optional().describe("Filter by allowed usage type"),
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
export async function execute(args: z.infer<typeof parameters>) {
|
|
18
|
+
const cards = await getCardsSafe();
|
|
19
|
+
|
|
20
|
+
let filtered = cards;
|
|
21
|
+
|
|
22
|
+
if (args.enabled_only) {
|
|
23
|
+
filtered = filtered.filter((c) => c.enabled);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (args.usage_type) {
|
|
27
|
+
filtered = filtered.filter((c) =>
|
|
28
|
+
c.allowedUsage.includes(args.usage_type!) || c.allowedUsage.includes("all")
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (filtered.length === 0) {
|
|
33
|
+
return { success: true, cards: [], message: "No cards found" };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const summary = filtered.map((c) => ({
|
|
37
|
+
id: c.id,
|
|
38
|
+
nickname: c.nickname,
|
|
39
|
+
type: c.cardType.toUpperCase(),
|
|
40
|
+
lastFour: `****${c.lastFourDigits}`,
|
|
41
|
+
cardholder: c.cardholderName,
|
|
42
|
+
usage: c.allowedUsage.join(", "),
|
|
43
|
+
enabled: c.enabled,
|
|
44
|
+
lastUsed: c.lastUsedAt ?? "Never",
|
|
45
|
+
}));
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
success: true,
|
|
49
|
+
count: summary.length,
|
|
50
|
+
cards: summary,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* List wallet transactions from blockchain
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { getWallet, type Chain } from "../lib/wallets";
|
|
7
|
+
import { getWalletTransactions } from "../lib/blockchain";
|
|
8
|
+
|
|
9
|
+
export const name = "list_wallet_transactions";
|
|
10
|
+
|
|
11
|
+
export const description = "List recent transactions for a crypto wallet. Fetches live data from blockchain explorers.";
|
|
12
|
+
|
|
13
|
+
export const parameters = z.object({
|
|
14
|
+
wallet_id: z.string().optional().describe("Wallet ID (use list_wallets to see available wallets)"),
|
|
15
|
+
address: z.string().optional().describe("Wallet address (alternative to wallet_id)"),
|
|
16
|
+
chain: z.enum(["ethereum", "polygon", "arbitrum", "optimism", "base", "avalanche", "bsc", "bitcoin", "solana", "starknet"]).optional()
|
|
17
|
+
.describe("Blockchain (required if using address)"),
|
|
18
|
+
limit: z.number().optional().describe("Number of transactions to return (default: 20, max: 50)"),
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export async function execute(args: z.infer<typeof parameters>) {
|
|
22
|
+
let address: string;
|
|
23
|
+
let chain: Chain;
|
|
24
|
+
|
|
25
|
+
// Get wallet by ID or use provided address
|
|
26
|
+
if (args.wallet_id) {
|
|
27
|
+
const wallet = await getWallet(args.wallet_id);
|
|
28
|
+
if (!wallet) {
|
|
29
|
+
return { success: false, error: `Wallet not found: ${args.wallet_id}` };
|
|
30
|
+
}
|
|
31
|
+
address = wallet.address;
|
|
32
|
+
chain = wallet.chain;
|
|
33
|
+
} else if (args.address && args.chain) {
|
|
34
|
+
address = args.address;
|
|
35
|
+
chain = args.chain;
|
|
36
|
+
} else {
|
|
37
|
+
return {
|
|
38
|
+
success: false,
|
|
39
|
+
error: "Provide either wallet_id or both address and chain"
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const limit = Math.min(args.limit ?? 20, 50);
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
const transactions = await getWalletTransactions(address, chain, limit);
|
|
47
|
+
|
|
48
|
+
if (transactions.length === 0) {
|
|
49
|
+
return {
|
|
50
|
+
success: true,
|
|
51
|
+
wallet: { address: `${address.slice(0, 10)}...${address.slice(-8)}`, chain },
|
|
52
|
+
transactions: [],
|
|
53
|
+
message: "No transactions found",
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
success: true,
|
|
59
|
+
wallet: {
|
|
60
|
+
address: `${address.slice(0, 10)}...${address.slice(-8)}`,
|
|
61
|
+
fullAddress: address,
|
|
62
|
+
chain,
|
|
63
|
+
},
|
|
64
|
+
count: transactions.length,
|
|
65
|
+
transactions: transactions.map((tx) => ({
|
|
66
|
+
hash: `${tx.hash.slice(0, 10)}...${tx.hash.slice(-8)}`,
|
|
67
|
+
fullHash: tx.hash,
|
|
68
|
+
type: tx.isIncoming ? "RECEIVED" : "SENT",
|
|
69
|
+
amount: tx.valueFormatted,
|
|
70
|
+
from: tx.from ? `${tx.from.slice(0, 8)}...` : "N/A",
|
|
71
|
+
to: tx.to ? `${tx.to.slice(0, 8)}...` : "N/A",
|
|
72
|
+
status: tx.status,
|
|
73
|
+
date: new Date(tx.timestamp).toISOString(),
|
|
74
|
+
fee: tx.fee || null,
|
|
75
|
+
})),
|
|
76
|
+
};
|
|
77
|
+
} catch (err) {
|
|
78
|
+
return {
|
|
79
|
+
success: false,
|
|
80
|
+
error: `Failed to fetch transactions: ${err instanceof Error ? err.message : err}`,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* List saved crypto wallets (safe view - no private keys)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { getWalletsSafe, CHAIN_CONFIG } from "../lib/wallets";
|
|
7
|
+
import type { Chain } from "../lib/wallets";
|
|
8
|
+
|
|
9
|
+
export const name = "list_wallets";
|
|
10
|
+
|
|
11
|
+
export const description = "List all saved crypto wallets. Shows address, chain, type, and allowed operations. Does NOT show private keys.";
|
|
12
|
+
|
|
13
|
+
export const parameters = z.object({
|
|
14
|
+
chain: z.enum(["ethereum", "polygon", "arbitrum", "optimism", "base", "avalanche", "bsc", "bitcoin", "solana", "starknet"]).optional()
|
|
15
|
+
.describe("Filter by blockchain"),
|
|
16
|
+
type: z.enum(["hot", "watch-only", "hardware"]).optional()
|
|
17
|
+
.describe("Filter by wallet type"),
|
|
18
|
+
enabled_only: z.boolean().optional().describe("Only show enabled wallets"),
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export async function execute(args: z.infer<typeof parameters>) {
|
|
22
|
+
const wallets = await getWalletsSafe();
|
|
23
|
+
|
|
24
|
+
let filtered = wallets;
|
|
25
|
+
|
|
26
|
+
if (args.chain) {
|
|
27
|
+
filtered = filtered.filter((w) => w.chain === args.chain);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (args.type) {
|
|
31
|
+
filtered = filtered.filter((w) => w.type === args.type);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (args.enabled_only) {
|
|
35
|
+
filtered = filtered.filter((w) => w.enabled);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (filtered.length === 0) {
|
|
39
|
+
return { success: true, wallets: [], message: "No wallets found" };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const summary = filtered.map((w) => {
|
|
43
|
+
const chainInfo = CHAIN_CONFIG[w.chain as Chain];
|
|
44
|
+
return {
|
|
45
|
+
id: w.id,
|
|
46
|
+
nickname: w.nickname,
|
|
47
|
+
chain: chainInfo?.name ?? w.chain,
|
|
48
|
+
nativeCurrency: chainInfo?.nativeCurrency ?? "?",
|
|
49
|
+
address: `${w.address.slice(0, 10)}...${w.address.slice(-8)}`,
|
|
50
|
+
fullAddress: w.address,
|
|
51
|
+
type: w.type,
|
|
52
|
+
operations: w.allowedOperations.join(", "),
|
|
53
|
+
enabled: w.enabled,
|
|
54
|
+
lastUsed: w.lastUsedAt ?? "Never",
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
success: true,
|
|
60
|
+
count: summary.length,
|
|
61
|
+
wallets: summary,
|
|
62
|
+
};
|
|
63
|
+
}
|