@voidmob/mcp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,81 @@
1
+ import { z } from "zod";
2
+ import { state } from "../sandbox/state.js";
3
+ import { formatUsd, formatGb, formatTimeRemaining } from "../utils/format.js";
4
+ export function registerOrdersTools(server) {
5
+ server.tool("list_orders", "List all orders across SMS, eSIM, and proxy services. Filter by service type or status.", {
6
+ type: z
7
+ .enum(["sms", "esim", "proxy"])
8
+ .optional()
9
+ .describe("Filter by service type"),
10
+ status: z
11
+ .enum(["active", "completed", "cancelled", "expired", "all"])
12
+ .optional()
13
+ .describe("Filter by status (default: all)"),
14
+ }, async ({ type, status }) => {
15
+ const statusFilter = status ?? "all";
16
+ const orders = [];
17
+ if (!type || type === "sms") {
18
+ for (const rental of state.smsRentals.values()) {
19
+ if (statusFilter !== "all" && rental.status !== statusFilter)
20
+ continue;
21
+ orders.push({
22
+ type: "SMS",
23
+ name: `${rental.service} (${rental.country}) — ${rental.number}`,
24
+ id: rental.rentalId,
25
+ status: rental.status,
26
+ price: rental.price,
27
+ details: `Messages: ${rental.messages.length}`,
28
+ createdAt: rental.createdAt,
29
+ });
30
+ }
31
+ }
32
+ if (!type || type === "esim") {
33
+ for (const order of state.esimOrders.values()) {
34
+ if (statusFilter !== "all" && order.status !== statusFilter)
35
+ continue;
36
+ const dataRemaining = formatGb(Math.max(0, order.dataTotal - order.dataUsed));
37
+ const timeLeft = formatTimeRemaining(order.expiry);
38
+ orders.push({
39
+ type: "eSIM",
40
+ name: `${order.planName} (${order.country})`,
41
+ id: order.orderId,
42
+ status: order.status,
43
+ price: order.price,
44
+ details: `Data remaining: ${dataRemaining} | ${timeLeft}`,
45
+ createdAt: order.createdAt,
46
+ });
47
+ }
48
+ }
49
+ if (!type || type === "proxy") {
50
+ for (const proxy of state.proxies.values()) {
51
+ if (statusFilter !== "all" && proxy.status !== statusFilter)
52
+ continue;
53
+ orders.push({
54
+ type: "Proxy",
55
+ name: `${proxy.type.toUpperCase()} proxy — ${proxy.country} (${proxy.carrier})`,
56
+ id: proxy.proxyId,
57
+ status: proxy.status,
58
+ price: proxy.price,
59
+ details: `Bandwidth: ${formatGb(proxy.bandwidthUsed)} / ${formatGb(proxy.bandwidthTotal)}`,
60
+ createdAt: proxy.createdAt,
61
+ });
62
+ }
63
+ }
64
+ if (orders.length === 0) {
65
+ return {
66
+ content: [{ type: "text", text: "No orders found." }],
67
+ };
68
+ }
69
+ orders.sort((a, b) => b.createdAt - a.createdAt);
70
+ let text = `${orders.length} order(s):\n`;
71
+ for (const o of orders) {
72
+ const date = new Date(o.createdAt).toISOString().slice(0, 16).replace("T", " ");
73
+ text += `\n [${o.type}] ${o.name}`;
74
+ text += `\n ID: ${o.id}`;
75
+ text += `\n Status: ${o.status} | Price: ${formatUsd(o.price)}`;
76
+ text += `\n ${o.details}`;
77
+ text += `\n Created: ${date}\n`;
78
+ }
79
+ return { content: [{ type: "text", text }] };
80
+ });
81
+ }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerProxyTools(server: McpServer): void;
@@ -0,0 +1,232 @@
1
+ import { z } from "zod";
2
+ import { state } from "../sandbox/state.js";
3
+ import { validateCountry, ToolError, generateId } from "../utils/validation.js";
4
+ import { formatUsd, formatGb, formatTimeRemaining, generateProxyCredentials, generateIp, } from "../utils/format.js";
5
+ import { searchProxyOptions, getProxyPricing } from "../mock-data/proxy.js";
6
+ function errorResponse(message) {
7
+ return {
8
+ content: [{ type: "text", text: message }],
9
+ isError: true,
10
+ };
11
+ }
12
+ export function registerProxyTools(server) {
13
+ server.tool("search_proxies", "Search available mobile proxy options. Filter by country or proxy type (gb = pay-per-GB, dedicated = fixed monthly).", {
14
+ country: z
15
+ .string()
16
+ .optional()
17
+ .describe("ISO 3166-1 alpha-2 country code (e.g., US, GB, DE)"),
18
+ type: z
19
+ .enum(["gb", "dedicated"])
20
+ .optional()
21
+ .describe("Proxy type: 'gb' for pay-per-GB or 'dedicated' for fixed monthly"),
22
+ }, async ({ country, type }) => {
23
+ try {
24
+ const validatedCountry = country ? validateCountry(country) : undefined;
25
+ const results = searchProxyOptions(validatedCountry, type);
26
+ if (results.length === 0) {
27
+ return errorResponse("No proxy options found matching your criteria. Try a different country or type.");
28
+ }
29
+ let text = `Found ${results.length} proxy option(s):\n\n`;
30
+ for (const p of results) {
31
+ text += ` ${p.carrier} — ${p.country} (${p.type})\n`;
32
+ text += ` Network: ${p.network}\n`;
33
+ if (p.type === "gb") {
34
+ text += ` Price: ${formatUsd(p.pricePerGb)}/GB\n`;
35
+ }
36
+ else {
37
+ text += ` Price: ${formatUsd(p.pricePerMonth)}/month\n`;
38
+ text += ` Included bandwidth: ${formatGb(p.bandwidth)}\n`;
39
+ }
40
+ text += `\n`;
41
+ }
42
+ return { content: [{ type: "text", text }] };
43
+ }
44
+ catch (e) {
45
+ if (e instanceof ToolError)
46
+ return errorResponse(e.message);
47
+ throw e;
48
+ }
49
+ });
50
+ server.tool("get_proxy_pricing", "Get detailed pricing for a specific proxy type and country combination.", {
51
+ type: z
52
+ .enum(["gb", "dedicated"])
53
+ .describe("Proxy type: 'gb' for pay-per-GB or 'dedicated' for fixed monthly"),
54
+ country: z.string().describe("ISO 3166-1 alpha-2 country code (e.g., US, GB)"),
55
+ }, async ({ type, country }) => {
56
+ try {
57
+ const validatedCountry = validateCountry(country);
58
+ const options = getProxyPricing(type, validatedCountry);
59
+ if (options.length === 0) {
60
+ return errorResponse(`No ${type} proxies available in ${validatedCountry}. Use search_proxies to find available options.`);
61
+ }
62
+ let text = `${type === "gb" ? "Pay-per-GB" : "Dedicated"} proxy pricing — ${validatedCountry}:\n\n`;
63
+ for (const opt of options) {
64
+ text += ` ${opt.carrier} (${opt.network})\n`;
65
+ if (opt.type === "gb") {
66
+ text += ` Rate: ${formatUsd(opt.pricePerGb)}/GB\n`;
67
+ }
68
+ else {
69
+ text += ` Rate: ${formatUsd(opt.pricePerMonth)}/month\n`;
70
+ text += ` Bandwidth: ${formatGb(opt.bandwidth)} included\n`;
71
+ }
72
+ text += `\n`;
73
+ }
74
+ return { content: [{ type: "text", text }] };
75
+ }
76
+ catch (e) {
77
+ if (e instanceof ToolError)
78
+ return errorResponse(e.message);
79
+ throw e;
80
+ }
81
+ });
82
+ server.tool("purchase_proxy", "Purchase a mobile proxy. For GB type, quantity = GB to buy. For dedicated, quantity = months. Deducts cost from wallet.", {
83
+ type: z
84
+ .enum(["gb", "dedicated"])
85
+ .describe("Proxy type: 'gb' for pay-per-GB or 'dedicated' for fixed monthly"),
86
+ country: z.string().describe("ISO 3166-1 alpha-2 country code (e.g., US, GB)"),
87
+ quantity: z
88
+ .number()
89
+ .positive()
90
+ .optional()
91
+ .describe("GB for 'gb' type, months for 'dedicated' type (default: 1)"),
92
+ }, async ({ type, country, quantity }) => {
93
+ try {
94
+ const validatedCountry = validateCountry(country);
95
+ const qty = quantity ?? 1;
96
+ const options = getProxyPricing(type, validatedCountry);
97
+ if (options.length === 0) {
98
+ return errorResponse(`No ${type} proxies available in ${validatedCountry}. Use search_proxies to find available options.`);
99
+ }
100
+ // Pick the first carrier option
101
+ const option = options[0];
102
+ let totalCost;
103
+ let bandwidthTotal;
104
+ let expiryMs;
105
+ const now = Date.now();
106
+ if (option.type === "gb") {
107
+ totalCost = Math.round(qty * option.pricePerGb * 100) / 100;
108
+ bandwidthTotal = qty;
109
+ expiryMs = now + 30 * 24 * 60 * 60 * 1000; // 30 days
110
+ }
111
+ else {
112
+ totalCost = Math.round(qty * option.pricePerMonth * 100) / 100;
113
+ bandwidthTotal = option.bandwidth;
114
+ expiryMs = now + qty * 30 * 24 * 60 * 60 * 1000; // quantity * 30 days
115
+ }
116
+ if (!state.deductBalance(totalCost, "proxy_purchase", `Proxy: ${type} ${option.carrier} (${validatedCountry})`)) {
117
+ return errorResponse(`Insufficient balance. Need ${formatUsd(totalCost)} but have ${formatUsd(state.balance)}. Use deposit to add funds.`);
118
+ }
119
+ const proxyId = generateId("prx");
120
+ const credentials = generateProxyCredentials(validatedCountry);
121
+ const ip = generateIp();
122
+ state.proxies.set(proxyId, {
123
+ proxyId,
124
+ type,
125
+ country: validatedCountry,
126
+ carrier: option.carrier,
127
+ credentials,
128
+ bandwidthUsed: 0,
129
+ bandwidthTotal,
130
+ status: "active",
131
+ ip,
132
+ createdAt: now,
133
+ price: totalCost,
134
+ });
135
+ const connectionString = `${credentials.host}:${credentials.port}:${credentials.username}:${credentials.password}`;
136
+ const text = [
137
+ `Proxy purchased!`,
138
+ ``,
139
+ ` Proxy ID: ${proxyId}`,
140
+ ` Type: ${type === "gb" ? "Pay-per-GB" : "Dedicated"}`,
141
+ ` Carrier: ${option.carrier} (${option.network})`,
142
+ ` Country: ${validatedCountry}`,
143
+ ` Cost: ${formatUsd(totalCost)}`,
144
+ ` Bandwidth: ${formatGb(bandwidthTotal)}`,
145
+ ` Expires: ${formatTimeRemaining(expiryMs)}`,
146
+ ``,
147
+ ` Connection:`,
148
+ ` Host: ${credentials.host}`,
149
+ ` Port: ${credentials.port}`,
150
+ ` Username: ${credentials.username}`,
151
+ ` Password: ${credentials.password}`,
152
+ ` String: ${connectionString}`,
153
+ ` Current IP: ${ip}`,
154
+ ``,
155
+ `Use get_proxy_status to check bandwidth usage, or rotate_proxy to get a new IP.`,
156
+ ].join("\n");
157
+ return { content: [{ type: "text", text }] };
158
+ }
159
+ catch (e) {
160
+ if (e instanceof ToolError)
161
+ return errorResponse(e.message);
162
+ throw e;
163
+ }
164
+ });
165
+ server.tool("get_proxy_status", "Check status, bandwidth usage, and connection details for a proxy.", {
166
+ proxyId: z.string().describe("Proxy ID returned from purchase_proxy"),
167
+ }, async ({ proxyId }) => {
168
+ try {
169
+ const proxy = state.proxies.get(proxyId);
170
+ if (!proxy) {
171
+ return errorResponse(`Proxy not found: ${proxyId}`);
172
+ }
173
+ const now = Date.now();
174
+ // Simulate bandwidth usage: ~0.05 GB/hour elapsed, cap at 90% of total
175
+ const hoursElapsed = (now - proxy.createdAt) / 3600000;
176
+ const simulatedUsage = hoursElapsed * 0.05;
177
+ const maxUsage = proxy.bandwidthTotal * 0.9;
178
+ proxy.bandwidthUsed = Math.round(Math.min(simulatedUsage, maxUsage) * 100) / 100;
179
+ const bandwidthRemaining = Math.max(0, proxy.bandwidthTotal - proxy.bandwidthUsed);
180
+ const uptimeHours = Math.floor(hoursElapsed);
181
+ const uptimeMinutes = Math.floor((hoursElapsed - uptimeHours) * 60);
182
+ const text = [
183
+ `Proxy Status — ${proxy.carrier} (${proxy.country})`,
184
+ ``,
185
+ ` Proxy ID: ${proxy.proxyId}`,
186
+ ` Status: ${proxy.status}`,
187
+ ` Type: ${proxy.type === "gb" ? "Pay-per-GB" : "Dedicated"}`,
188
+ ` Location: ${proxy.country}`,
189
+ ` Current IP: ${proxy.ip}`,
190
+ ` Bandwidth used: ${formatGb(proxy.bandwidthUsed)}`,
191
+ ` Bandwidth remaining: ${formatGb(bandwidthRemaining)}`,
192
+ ` Bandwidth total: ${formatGb(proxy.bandwidthTotal)}`,
193
+ ` Uptime: ${uptimeHours}h ${uptimeMinutes}m`,
194
+ ].join("\n");
195
+ return { content: [{ type: "text", text }] };
196
+ }
197
+ catch (e) {
198
+ if (e instanceof ToolError)
199
+ return errorResponse(e.message);
200
+ throw e;
201
+ }
202
+ });
203
+ server.tool("rotate_proxy", "Rotate a proxy to get a new IP address. Only works on active proxies.", {
204
+ proxyId: z.string().describe("Proxy ID to rotate"),
205
+ }, async ({ proxyId }) => {
206
+ try {
207
+ const proxy = state.proxies.get(proxyId);
208
+ if (!proxy) {
209
+ return errorResponse(`Proxy not found: ${proxyId}`);
210
+ }
211
+ if (proxy.status !== "active") {
212
+ return errorResponse(`Proxy ${proxyId} is ${proxy.status}. Only active proxies can be rotated.`);
213
+ }
214
+ const oldIp = proxy.ip;
215
+ const newIp = generateIp();
216
+ proxy.ip = newIp;
217
+ const text = [
218
+ `IP rotated!`,
219
+ ``,
220
+ ` Proxy ID: ${proxy.proxyId}`,
221
+ ` Old IP: ${oldIp}`,
222
+ ` New IP: ${newIp}`,
223
+ ].join("\n");
224
+ return { content: [{ type: "text", text }] };
225
+ }
226
+ catch (e) {
227
+ if (e instanceof ToolError)
228
+ return errorResponse(e.message);
229
+ throw e;
230
+ }
231
+ });
232
+ }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerSmsTools(server: McpServer): void;
@@ -0,0 +1,158 @@
1
+ import { z } from "zod";
2
+ import { state } from "../sandbox/state.js";
3
+ import { generateId } from "../utils/validation.js";
4
+ import { formatUsd, generatePhoneNumber, generateVerificationCode, formatTimeRemaining, } from "../utils/format.js";
5
+ import { searchServices, getServicePrice } from "../mock-data/sms.js";
6
+ function errorResponse(message) {
7
+ return {
8
+ content: [{ type: "text", text: message }],
9
+ isError: true,
10
+ };
11
+ }
12
+ export function registerSmsTools(server) {
13
+ server.tool("search_sms_services", "Search available US non-VoIP SMS verification services. Filter by name or category.", {
14
+ query: z
15
+ .string()
16
+ .optional()
17
+ .describe("Search by service name or category (e.g., 'telegram', 'social')"),
18
+ }, async ({ query }) => {
19
+ const results = searchServices(query);
20
+ if (results.length === 0) {
21
+ return errorResponse("No services found matching your criteria. Try a different search term.");
22
+ }
23
+ let text = `Found ${results.length} US non-VoIP SMS service(s):\n\n`;
24
+ for (const s of results) {
25
+ text += ` ${s.name} (${s.id})\n`;
26
+ text += ` Category: ${s.category}\n`;
27
+ text += ` Country: US (non-VoIP)\n`;
28
+ text += ` Price: ${formatUsd(s.price)}\n`;
29
+ text += ` Delivery: ${s.estimatedDelivery}\n\n`;
30
+ }
31
+ return { content: [{ type: "text", text }] };
32
+ });
33
+ server.tool("get_sms_price", "Get pricing for a US non-VoIP SMS verification service.", {
34
+ service: z.string().describe("Service ID (e.g., 'whatsapp', 'telegram')"),
35
+ }, async ({ service }) => {
36
+ const result = getServicePrice(service);
37
+ if (!result) {
38
+ return errorResponse(`Service "${service}" not found. Use search_sms_services to find available options.`);
39
+ }
40
+ const text = [
41
+ `${result.service.name} — US (non-VoIP)`,
42
+ ``,
43
+ ` Price: ${formatUsd(result.price)}`,
44
+ ` Delivery: ${result.service.estimatedDelivery}`,
45
+ ].join("\n");
46
+ return { content: [{ type: "text", text }] };
47
+ });
48
+ server.tool("rent_number", "Rent a US non-VoIP phone number to receive an SMS verification code. Deducts cost from wallet. Number expires in 5 minutes.", {
49
+ service: z.string().describe("Service ID (e.g., 'whatsapp', 'telegram')"),
50
+ }, async ({ service }) => {
51
+ const result = getServicePrice(service);
52
+ if (!result) {
53
+ return errorResponse(`Service "${service}" not found. Use search_sms_services to find available options.`);
54
+ }
55
+ const { price } = result;
56
+ if (!state.deductBalance(price, "sms_rental", `SMS rental: ${result.service.name} (US)`)) {
57
+ return errorResponse(`Insufficient balance. Need ${formatUsd(price)} but have ${formatUsd(state.balance)}. Use deposit to add funds.`);
58
+ }
59
+ const rentalId = generateId("sms");
60
+ const number = generatePhoneNumber("US");
61
+ const now = Date.now();
62
+ state.smsRentals.set(rentalId, {
63
+ rentalId,
64
+ number,
65
+ service,
66
+ country: "US",
67
+ status: "active",
68
+ messages: [],
69
+ expiry: now + 5 * 60 * 1000,
70
+ createdAt: now,
71
+ price,
72
+ });
73
+ const text = [
74
+ `Number rented!`,
75
+ ``,
76
+ ` Rental ID: ${rentalId}`,
77
+ ` Number: ${number}`,
78
+ ` Service: ${result.service.name}`,
79
+ ` Country: US (non-VoIP)`,
80
+ ` Cost: ${formatUsd(price)}`,
81
+ ` Expires: ${formatTimeRemaining(now + 5 * 60 * 1000)}`,
82
+ ``,
83
+ `Use get_messages with the rental ID to check for incoming verification codes.`,
84
+ ].join("\n");
85
+ return { content: [{ type: "text", text }] };
86
+ });
87
+ server.tool("get_messages", "Check for incoming SMS messages on a rented number. Messages typically arrive within a few seconds.", {
88
+ rentalId: z.string().describe("Rental ID returned from rent_number"),
89
+ }, async ({ rentalId }) => {
90
+ const rental = state.smsRentals.get(rentalId);
91
+ if (!rental) {
92
+ return errorResponse(`Rental not found: ${rentalId}`);
93
+ }
94
+ if (rental.status === "active" && Date.now() >= rental.expiry) {
95
+ rental.status = "expired";
96
+ }
97
+ if (rental.status === "expired") {
98
+ return errorResponse(`Rental ${rentalId} has expired.`);
99
+ }
100
+ if (rental.status === "cancelled") {
101
+ return errorResponse(`Rental ${rentalId} has been cancelled.`);
102
+ }
103
+ // Lazy mock: generate a message after 5 seconds
104
+ if (rental.messages.length === 0) {
105
+ const elapsed = Date.now() - rental.createdAt;
106
+ if (elapsed < 5000) {
107
+ const text = [
108
+ `No messages yet for ${rental.number} (${rental.service}).`,
109
+ ``,
110
+ `Waiting for verification code... try again shortly.`,
111
+ `Time since rental: ${Math.floor(elapsed / 1000)}s`,
112
+ ].join("\n");
113
+ return { content: [{ type: "text", text }] };
114
+ }
115
+ const code = generateVerificationCode();
116
+ rental.messages.push({
117
+ from: rental.service,
118
+ text: `Your ${rental.service} verification code is: ${code}`,
119
+ receivedAt: Date.now(),
120
+ });
121
+ }
122
+ let text = `Messages for ${rental.number} (${rental.service}):\n\n`;
123
+ for (const msg of rental.messages) {
124
+ const time = new Date(msg.receivedAt).toISOString().slice(11, 19);
125
+ text += ` [${time}] From: ${msg.from}\n`;
126
+ text += ` ${msg.text}\n\n`;
127
+ }
128
+ return { content: [{ type: "text", text }] };
129
+ });
130
+ server.tool("cancel_rental", "Cancel an SMS rental. Refunds the full price only if no messages were received.", {
131
+ rentalId: z.string().describe("Rental ID to cancel"),
132
+ }, async ({ rentalId }) => {
133
+ const rental = state.smsRentals.get(rentalId);
134
+ if (!rental) {
135
+ return errorResponse(`Rental not found: ${rentalId}`);
136
+ }
137
+ if (rental.status !== "active") {
138
+ return errorResponse(`Rental ${rentalId} is ${rental.status} and cannot be cancelled.`);
139
+ }
140
+ rental.status = "cancelled";
141
+ if (rental.messages.length === 0) {
142
+ state.addBalance(rental.price, "refund", `Refund: ${rental.service} rental (US)`);
143
+ const text = [
144
+ `Rental ${rentalId} cancelled.`,
145
+ ``,
146
+ ` Refund: ${formatUsd(rental.price)} (no messages received)`,
147
+ ` New balance: ${formatUsd(state.balance)}`,
148
+ ].join("\n");
149
+ return { content: [{ type: "text", text }] };
150
+ }
151
+ const text = [
152
+ `Rental ${rentalId} cancelled.`,
153
+ ``,
154
+ ` No refund — messages were already received.`,
155
+ ].join("\n");
156
+ return { content: [{ type: "text", text }] };
157
+ });
158
+ }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerWalletTools(server: McpServer): void;
@@ -0,0 +1,66 @@
1
+ import { z } from "zod";
2
+ import { state } from "../sandbox/state.js";
3
+ import { generateId } from "../utils/validation.js";
4
+ import { formatUsd } from "../utils/format.js";
5
+ export function registerWalletTools(server) {
6
+ server.tool("get_balance", "Get wallet balance and recent transactions. Resolves any pending deposits first.", {}, async () => {
7
+ state.resolvePendingDeposits();
8
+ const recent = state.transactions
9
+ .slice()
10
+ .sort((a, b) => b.createdAt - a.createdAt)
11
+ .slice(0, 10);
12
+ const pendingDeposits = [...state.deposits.values()].filter((d) => d.status === "pending");
13
+ let text = `Balance: ${formatUsd(state.balance)}\n`;
14
+ if (pendingDeposits.length > 0) {
15
+ text += `\nPending deposits: ${pendingDeposits.length}\n`;
16
+ for (const d of pendingDeposits) {
17
+ text += ` - ${formatUsd(d.amount)} ${d.currency} (auto-confirms in ~5s)\n`;
18
+ }
19
+ }
20
+ if (recent.length > 0) {
21
+ text += `\nLast ${recent.length} transactions:\n`;
22
+ for (const tx of recent) {
23
+ const sign = tx.amount >= 0 ? "+" : "";
24
+ const date = new Date(tx.createdAt).toISOString().slice(0, 16);
25
+ text += ` ${date} ${sign}${formatUsd(Math.abs(tx.amount))} ${tx.description}\n`;
26
+ }
27
+ }
28
+ else {
29
+ text += "\nNo transactions yet.\n";
30
+ }
31
+ return {
32
+ content: [{ type: "text", text }],
33
+ };
34
+ });
35
+ server.tool("deposit", "Create a crypto deposit to add funds to the wallet. Returns a mock payment link that auto-confirms in ~5 seconds.", {
36
+ amount: z.number().positive().describe("Amount in USD to deposit"),
37
+ currency: z
38
+ .enum(["BTC", "ETH", "SOL"])
39
+ .default("BTC")
40
+ .describe("Cryptocurrency to pay with (default: BTC)"),
41
+ }, async ({ amount, currency }) => {
42
+ const invoiceId = generateId("inv");
43
+ state.deposits.set(invoiceId, {
44
+ invoiceId,
45
+ amount,
46
+ currency,
47
+ status: "pending",
48
+ createdAt: Date.now(),
49
+ });
50
+ const payUrl = `https://sandbox.voidmob.com/pay/${invoiceId}`;
51
+ const text = [
52
+ `Deposit created!`,
53
+ ``,
54
+ ` Amount: ${formatUsd(amount)}`,
55
+ ` Currency: ${currency}`,
56
+ ` Invoice: ${invoiceId}`,
57
+ ` Pay URL: ${payUrl}`,
58
+ ``,
59
+ `This is a sandbox deposit. It will auto-confirm in ~5 seconds.`,
60
+ `Call get_balance after a few seconds to see the updated balance.`,
61
+ ].join("\n");
62
+ return {
63
+ content: [{ type: "text", text }],
64
+ };
65
+ });
66
+ }
@@ -0,0 +1,12 @@
1
+ export declare function formatUsd(amount: number): string;
2
+ export declare function formatGb(gb: number): string;
3
+ export declare function formatTimeRemaining(expiryMs: number): string;
4
+ export declare function generatePhoneNumber(country: string): string;
5
+ export declare function generateProxyCredentials(country: string): {
6
+ host: string;
7
+ port: number;
8
+ username: string;
9
+ password: string;
10
+ };
11
+ export declare function generateIp(): string;
12
+ export declare function generateVerificationCode(): string;
@@ -0,0 +1,55 @@
1
+ export function formatUsd(amount) {
2
+ return `$${amount.toFixed(2)}`;
3
+ }
4
+ export function formatGb(gb) {
5
+ if (gb < 1)
6
+ return `${(gb * 1024).toFixed(0)} MB`;
7
+ return `${gb.toFixed(1)} GB`;
8
+ }
9
+ export function formatTimeRemaining(expiryMs) {
10
+ const remaining = expiryMs - Date.now();
11
+ if (remaining <= 0)
12
+ return "expired";
13
+ const hours = Math.floor(remaining / 3600000);
14
+ const minutes = Math.floor((remaining % 3600000) / 60000);
15
+ if (hours > 24)
16
+ return `${Math.floor(hours / 24)}d ${hours % 24}h`;
17
+ if (hours > 0)
18
+ return `${hours}h ${minutes}m`;
19
+ return `${minutes}m`;
20
+ }
21
+ export function generatePhoneNumber(country) {
22
+ const formats = {
23
+ US: () => `+1${rand(200, 999)}${rand(200, 999)}${rand(1000, 9999)}`,
24
+ GB: () => `+447${rand(100, 999)}${rand(100000, 999999)}`,
25
+ CA: () => `+1${rand(200, 999)}${rand(200, 999)}${rand(1000, 9999)}`,
26
+ DE: () => `+491${rand(50, 79)}${rand(1000000, 9999999)}`,
27
+ FR: () => `+336${rand(10000000, 99999999)}`,
28
+ NL: () => `+316${rand(10000000, 99999999)}`,
29
+ BR: () => `+5511${rand(90000, 99999)}${rand(1000, 9999)}`,
30
+ JP: () => `+8190${rand(1000, 9999)}${rand(1000, 9999)}`,
31
+ AU: () => `+614${rand(10, 99)}${rand(100000, 999999)}`,
32
+ IN: () => `+91${rand(70000, 99999)}${rand(10000, 99999)}`,
33
+ };
34
+ const gen = formats[country] || (() => `+${rand(1, 99)}${rand(100000000, 999999999)}`);
35
+ return gen();
36
+ }
37
+ function rand(min, max) {
38
+ return Math.floor(Math.random() * (max - min + 1)) + min;
39
+ }
40
+ export function generateProxyCredentials(country) {
41
+ const r = () => Math.random().toString(36).substring(2, 8);
42
+ return {
43
+ host: `${country.toLowerCase()}.proxy.voidmob.com`,
44
+ port: 10000 + Math.floor(Math.random() * 5000),
45
+ username: `vm_${r()}`,
46
+ password: r() + r(),
47
+ };
48
+ }
49
+ export function generateIp() {
50
+ const o = () => Math.floor(Math.random() * 254) + 1;
51
+ return `${o()}.${o()}.${o()}.${o()}`;
52
+ }
53
+ export function generateVerificationCode() {
54
+ return Math.floor(100000 + Math.random() * 900000).toString();
55
+ }
@@ -0,0 +1,6 @@
1
+ export declare function validateCountry(country: string): string;
2
+ export declare function requireField(value: unknown, fieldName: string): void;
3
+ export declare class ToolError extends Error {
4
+ constructor(message: string);
5
+ }
6
+ export declare function generateId(prefix: string): string;
@@ -0,0 +1,32 @@
1
+ // ISO 3166-1 alpha-2 country codes (subset of most common)
2
+ const VALID_COUNTRIES = new Set([
3
+ "US", "GB", "CA", "AU", "DE", "FR", "NL", "IT", "ES", "PT",
4
+ "BR", "MX", "AR", "CO", "CL", "PE", "JP", "KR", "CN", "IN",
5
+ "ID", "TH", "VN", "PH", "MY", "SG", "HK", "TW", "RU", "UA",
6
+ "PL", "CZ", "RO", "SE", "NO", "DK", "FI", "AT", "CH", "BE",
7
+ "IE", "NZ", "ZA", "EG", "NG", "KE", "IL", "TR", "SA", "AE",
8
+ ]);
9
+ export function validateCountry(country) {
10
+ const upper = country.toUpperCase();
11
+ if (!VALID_COUNTRIES.has(upper)) {
12
+ throw new ToolError(`Invalid country code: ${country}. Use ISO 3166-1 alpha-2 (e.g., US, GB, JP).`);
13
+ }
14
+ return upper;
15
+ }
16
+ export function requireField(value, fieldName) {
17
+ if (value === undefined || value === null || value === "") {
18
+ throw new ToolError(`Missing required field: ${fieldName}`);
19
+ }
20
+ }
21
+ export class ToolError extends Error {
22
+ constructor(message) {
23
+ super(message);
24
+ this.name = "ToolError";
25
+ }
26
+ }
27
+ let counter = 0;
28
+ export function generateId(prefix) {
29
+ counter++;
30
+ const rand = Math.random().toString(36).substring(2, 8);
31
+ return `${prefix}_${rand}${counter}`;
32
+ }