zano-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.
- package/README.md +195 -0
- package/dist/clients/daemon.d.ts +6 -0
- package/dist/clients/daemon.d.ts.map +1 -0
- package/dist/clients/daemon.js +30 -0
- package/dist/clients/daemon.js.map +1 -0
- package/dist/clients/trade.d.ts +8 -0
- package/dist/clients/trade.d.ts.map +1 -0
- package/dist/clients/trade.js +33 -0
- package/dist/clients/trade.js.map +1 -0
- package/dist/clients/wallet.d.ts +8 -0
- package/dist/clients/wallet.d.ts.map +1 -0
- package/dist/clients/wallet.js +45 -0
- package/dist/clients/wallet.js.map +1 -0
- package/dist/config.d.ts +30 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +42 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/logger.d.ts +9 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +28 -0
- package/dist/logger.js.map +1 -0
- package/dist/prompts/index.d.ts +4 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +54 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/resources/index.d.ts +4 -0
- package/dist/resources/index.d.ts.map +1 -0
- package/dist/resources/index.js +60 -0
- package/dist/resources/index.js.map +1 -0
- package/dist/server.d.ts +4 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +22 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/assets/definitions.d.ts +127 -0
- package/dist/tools/assets/definitions.d.ts.map +1 -0
- package/dist/tools/assets/definitions.js +43 -0
- package/dist/tools/assets/definitions.js.map +1 -0
- package/dist/tools/assets/handlers.d.ts +21 -0
- package/dist/tools/assets/handlers.d.ts.map +1 -0
- package/dist/tools/assets/handlers.js +115 -0
- package/dist/tools/assets/handlers.js.map +1 -0
- package/dist/tools/daemon/definitions.d.ts +139 -0
- package/dist/tools/daemon/definitions.d.ts.map +1 -0
- package/dist/tools/daemon/definitions.js +55 -0
- package/dist/tools/daemon/definitions.js.map +1 -0
- package/dist/tools/daemon/handlers.d.ts +30 -0
- package/dist/tools/daemon/handlers.d.ts.map +1 -0
- package/dist/tools/daemon/handlers.js +274 -0
- package/dist/tools/daemon/handlers.js.map +1 -0
- package/dist/tools/register.d.ts +4 -0
- package/dist/tools/register.d.ts.map +1 -0
- package/dist/tools/register.js +93 -0
- package/dist/tools/register.js.map +1 -0
- package/dist/tools/swap/definitions.d.ts +103 -0
- package/dist/tools/swap/definitions.d.ts.map +1 -0
- package/dist/tools/swap/definitions.js +28 -0
- package/dist/tools/swap/definitions.js.map +1 -0
- package/dist/tools/swap/handlers.d.ts +17 -0
- package/dist/tools/swap/handlers.d.ts.map +1 -0
- package/dist/tools/swap/handlers.js +98 -0
- package/dist/tools/swap/handlers.js.map +1 -0
- package/dist/tools/trade/definitions.d.ts +137 -0
- package/dist/tools/trade/definitions.d.ts.map +1 -0
- package/dist/tools/trade/definitions.js +48 -0
- package/dist/tools/trade/definitions.js.map +1 -0
- package/dist/tools/trade/handlers.d.ts +23 -0
- package/dist/tools/trade/handlers.d.ts.map +1 -0
- package/dist/tools/trade/handlers.js +196 -0
- package/dist/tools/trade/handlers.js.map +1 -0
- package/dist/tools/wallet/definitions.d.ts +158 -0
- package/dist/tools/wallet/definitions.d.ts.map +1 -0
- package/dist/tools/wallet/definitions.js +73 -0
- package/dist/tools/wallet/definitions.js.map +1 -0
- package/dist/tools/wallet/handlers.d.ts +28 -0
- package/dist/tools/wallet/handlers.d.ts.map +1 -0
- package/dist/tools/wallet/handlers.js +214 -0
- package/dist/tools/wallet/handlers.js.map +1 -0
- package/dist/utils/constants.d.ts +25 -0
- package/dist/utils/constants.d.ts.map +1 -0
- package/dist/utils/constants.js +19 -0
- package/dist/utils/constants.js.map +1 -0
- package/dist/utils/formatting.d.ts +14 -0
- package/dist/utils/formatting.d.ts.map +1 -0
- package/dist/utils/formatting.js +68 -0
- package/dist/utils/formatting.js.map +1 -0
- package/package.json +43 -0
- package/src/clients/daemon.ts +41 -0
- package/src/clients/trade.ts +51 -0
- package/src/clients/wallet.ts +59 -0
- package/src/config.ts +56 -0
- package/src/index.ts +20 -0
- package/src/logger.ts +33 -0
- package/src/prompts/index.ts +80 -0
- package/src/resources/index.ts +73 -0
- package/src/server.ts +26 -0
- package/src/tools/assets/definitions.ts +58 -0
- package/src/tools/assets/handlers.ts +140 -0
- package/src/tools/daemon/definitions.ts +86 -0
- package/src/tools/daemon/handlers.ts +349 -0
- package/src/tools/register.ts +194 -0
- package/src/tools/swap/definitions.ts +36 -0
- package/src/tools/swap/handlers.ts +139 -0
- package/src/tools/trade/definitions.ts +67 -0
- package/src/tools/trade/handlers.ts +264 -0
- package/src/tools/wallet/definitions.ts +92 -0
- package/src/tools/wallet/handlers.ts +268 -0
- package/src/utils/constants.ts +24 -0
- package/src/utils/formatting.ts +78 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { TradeClient } from "../../clients/trade.js";
|
|
2
|
+
import type {
|
|
3
|
+
GetTradingPairInput,
|
|
4
|
+
GetOrderBookInput,
|
|
5
|
+
DexAuthenticateInput,
|
|
6
|
+
CreateOrderInput,
|
|
7
|
+
CancelOrderInput,
|
|
8
|
+
GetMyOrdersInput,
|
|
9
|
+
ApplyOrderInput,
|
|
10
|
+
ConfirmTradeInput,
|
|
11
|
+
GetActiveTradeInput,
|
|
12
|
+
} from "./definitions.js";
|
|
13
|
+
|
|
14
|
+
type ToolResult = { content: Array<{ type: "text"; text: string }> };
|
|
15
|
+
function textResult(text: string): ToolResult {
|
|
16
|
+
return { content: [{ type: "text", text }] };
|
|
17
|
+
}
|
|
18
|
+
function errorResult(error: unknown): ToolResult {
|
|
19
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
20
|
+
return textResult(`Error: ${msg}`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class TradeHandlers {
|
|
24
|
+
private client: TradeClient;
|
|
25
|
+
|
|
26
|
+
constructor(client: TradeClient) {
|
|
27
|
+
this.client = client;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async getTradingPair(input: GetTradingPairInput): Promise<ToolResult> {
|
|
31
|
+
try {
|
|
32
|
+
const data = await this.client.post<Record<string, unknown>>(
|
|
33
|
+
"/api/dex/get-pair",
|
|
34
|
+
{ id: input.id },
|
|
35
|
+
);
|
|
36
|
+
const pair = data;
|
|
37
|
+
const first = (pair.first_currency as Record<string, unknown>) || {};
|
|
38
|
+
const second = (pair.second_currency as Record<string, unknown>) || {};
|
|
39
|
+
const lines = [
|
|
40
|
+
`Trading Pair #${pair.id}`,
|
|
41
|
+
` ${first.code || first.name || "?"} / ${second.code || second.name || "?"}`,
|
|
42
|
+
` Rate: ${pair.rate}`,
|
|
43
|
+
` 24h High: ${pair.high}`,
|
|
44
|
+
` 24h Low: ${pair.low}`,
|
|
45
|
+
` Volume: ${pair.volume}`,
|
|
46
|
+
];
|
|
47
|
+
return textResult(lines.join("\n"));
|
|
48
|
+
} catch (e) {
|
|
49
|
+
return errorResult(e);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async getOrderBook(input: GetOrderBookInput): Promise<ToolResult> {
|
|
54
|
+
try {
|
|
55
|
+
const orders = await this.client.post<Array<Record<string, unknown>>>(
|
|
56
|
+
"/api/orders/get-page",
|
|
57
|
+
{ pairId: input.pairId },
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
if (!orders || orders.length === 0) {
|
|
61
|
+
return textResult(`No orders found for pair ${input.pairId}.`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const buys = orders.filter((o) => o.type === "buy").sort((a, b) => parseFloat(String(b.price)) - parseFloat(String(a.price)));
|
|
65
|
+
const sells = orders.filter((o) => o.type === "sell").sort((a, b) => parseFloat(String(a.price)) - parseFloat(String(b.price)));
|
|
66
|
+
|
|
67
|
+
const lines = [`Order Book for Pair ${input.pairId} (${orders.length} orders):`];
|
|
68
|
+
|
|
69
|
+
if (sells.length > 0) {
|
|
70
|
+
lines.push("", " SELLS (asks):");
|
|
71
|
+
for (const o of sells) {
|
|
72
|
+
const user = (o.user as Record<string, unknown>) || {};
|
|
73
|
+
const instant = o.isInstant ? " [INSTANT]" : "";
|
|
74
|
+
lines.push(
|
|
75
|
+
` ${o.price} | ${o.left} remaining (of ${o.amount}) | ${user.alias || "anon"}${instant}`,
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (buys.length > 0) {
|
|
81
|
+
lines.push("", " BUYS (bids):");
|
|
82
|
+
for (const o of buys) {
|
|
83
|
+
const user = (o.user as Record<string, unknown>) || {};
|
|
84
|
+
const instant = o.isInstant ? " [INSTANT]" : "";
|
|
85
|
+
lines.push(
|
|
86
|
+
` ${o.price} | ${o.left} remaining (of ${o.amount}) | ${user.alias || "anon"}${instant}`,
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (buys.length > 0 && sells.length > 0) {
|
|
92
|
+
const bestBid = parseFloat(String(buys[0].price));
|
|
93
|
+
const bestAsk = parseFloat(String(sells[0].price));
|
|
94
|
+
const spread = bestAsk - bestBid;
|
|
95
|
+
const spreadPct = ((spread / bestAsk) * 100).toFixed(2);
|
|
96
|
+
lines.push("", ` Spread: ${spread.toFixed(8)} (${spreadPct}%)`);
|
|
97
|
+
lines.push(` Best bid: ${bestBid} | Best ask: ${bestAsk}`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return textResult(lines.join("\n"));
|
|
101
|
+
} catch (e) {
|
|
102
|
+
return errorResult(e);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async dexAuthenticate(input: DexAuthenticateInput): Promise<ToolResult> {
|
|
107
|
+
try {
|
|
108
|
+
const data = await this.client.post<Record<string, unknown>>(
|
|
109
|
+
"/api/auth",
|
|
110
|
+
{
|
|
111
|
+
data: {
|
|
112
|
+
address: input.address,
|
|
113
|
+
alias: input.alias || "",
|
|
114
|
+
message: input.message,
|
|
115
|
+
signature: input.signature,
|
|
116
|
+
},
|
|
117
|
+
neverExpires: true,
|
|
118
|
+
},
|
|
119
|
+
);
|
|
120
|
+
if (data && typeof data === "string") {
|
|
121
|
+
this.client.setToken(data);
|
|
122
|
+
return textResult(`Authenticated with Trade API. Token stored.`);
|
|
123
|
+
}
|
|
124
|
+
return textResult(`Authentication response: ${JSON.stringify(data)}`);
|
|
125
|
+
} catch (e) {
|
|
126
|
+
return errorResult(e);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
async createOrder(input: CreateOrderInput): Promise<ToolResult> {
|
|
131
|
+
try {
|
|
132
|
+
const data = await this.client.post<Record<string, unknown>>(
|
|
133
|
+
"/api/orders/create",
|
|
134
|
+
{
|
|
135
|
+
orderData: {
|
|
136
|
+
type: input.type,
|
|
137
|
+
side: "limit",
|
|
138
|
+
price: input.price,
|
|
139
|
+
amount: input.amount,
|
|
140
|
+
pairId: input.pairId,
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
true,
|
|
144
|
+
);
|
|
145
|
+
return textResult(
|
|
146
|
+
`Order created!\n ID: ${data.id}\n Type: ${data.type}\n Price: ${data.price}\n Amount: ${data.amount}\n Status: ${data.status}`,
|
|
147
|
+
);
|
|
148
|
+
} catch (e) {
|
|
149
|
+
return errorResult(e);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async cancelOrder(input: CancelOrderInput): Promise<ToolResult> {
|
|
154
|
+
try {
|
|
155
|
+
await this.client.post(
|
|
156
|
+
"/api/orders/cancel",
|
|
157
|
+
{ orderId: input.orderId },
|
|
158
|
+
true,
|
|
159
|
+
);
|
|
160
|
+
return textResult(`Order ${input.orderId} cancelled.`);
|
|
161
|
+
} catch (e) {
|
|
162
|
+
return errorResult(e);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async getMyOrders(input: GetMyOrdersInput): Promise<ToolResult> {
|
|
167
|
+
try {
|
|
168
|
+
const data = await this.client.post<Record<string, unknown>>(
|
|
169
|
+
"/api/orders/get-user-page",
|
|
170
|
+
{ pairId: input.pairId },
|
|
171
|
+
true,
|
|
172
|
+
);
|
|
173
|
+
const orders = (data.orders || []) as Array<Record<string, unknown>>;
|
|
174
|
+
const tips = (data.applyTips || []) as Array<Record<string, unknown>>;
|
|
175
|
+
|
|
176
|
+
const lines = [`Your Orders for Pair ${input.pairId}:`];
|
|
177
|
+
|
|
178
|
+
if (orders.length === 0) {
|
|
179
|
+
lines.push(" No active orders.");
|
|
180
|
+
} else {
|
|
181
|
+
for (const o of orders) {
|
|
182
|
+
const instant = o.isInstant ? " [INSTANT]" : "";
|
|
183
|
+
lines.push(
|
|
184
|
+
` #${o.id} ${o.type} ${o.left}/${o.amount} @ ${o.price}${instant}`,
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (tips.length > 0) {
|
|
190
|
+
lines.push("", ` Pending Tips (${tips.length}):`);
|
|
191
|
+
for (const t of tips) {
|
|
192
|
+
const user = (t.user as Record<string, unknown>) || {};
|
|
193
|
+
const hasTx = t.transaction ? " [HAS TX]" : "";
|
|
194
|
+
lines.push(
|
|
195
|
+
` Tip #${t.id} for order #${t.connected_order_id} | ${t.left} @ ${t.price} | ${user.alias || "anon"}${hasTx}`,
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return textResult(lines.join("\n"));
|
|
201
|
+
} catch (e) {
|
|
202
|
+
return errorResult(e);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async applyOrder(input: ApplyOrderInput): Promise<ToolResult> {
|
|
207
|
+
try {
|
|
208
|
+
await this.client.post(
|
|
209
|
+
"/api/orders/apply-order",
|
|
210
|
+
{
|
|
211
|
+
orderData: {
|
|
212
|
+
id: input.id,
|
|
213
|
+
connected_order_id: input.connected_order_id,
|
|
214
|
+
hex_raw_proposal: input.hex_raw_proposal,
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
true,
|
|
218
|
+
);
|
|
219
|
+
return textResult(`Applied to order. Tip ID: ${input.id}, connected to order: ${input.connected_order_id}`);
|
|
220
|
+
} catch (e) {
|
|
221
|
+
return errorResult(e);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async confirmTrade(input: ConfirmTradeInput): Promise<ToolResult> {
|
|
226
|
+
try {
|
|
227
|
+
await this.client.post(
|
|
228
|
+
"/api/transactions/confirm",
|
|
229
|
+
{ transactionId: input.transactionId },
|
|
230
|
+
true,
|
|
231
|
+
);
|
|
232
|
+
return textResult(`Trade ${input.transactionId} confirmed.`);
|
|
233
|
+
} catch (e) {
|
|
234
|
+
return errorResult(e);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
async getActiveTrade(input: GetActiveTradeInput): Promise<ToolResult> {
|
|
239
|
+
try {
|
|
240
|
+
const data = await this.client.post<Record<string, unknown>>(
|
|
241
|
+
"/api/transactions/get-active-tx-by-orders-ids",
|
|
242
|
+
{
|
|
243
|
+
firstOrderId: input.firstOrderId,
|
|
244
|
+
secondOrderId: input.secondOrderId,
|
|
245
|
+
},
|
|
246
|
+
true,
|
|
247
|
+
);
|
|
248
|
+
const lines = [
|
|
249
|
+
`Active Trade:`,
|
|
250
|
+
` Buy order: ${data.buy_order_id}`,
|
|
251
|
+
` Sell order: ${data.sell_order_id}`,
|
|
252
|
+
` Amount: ${data.amount}`,
|
|
253
|
+
` Status: ${data.status}`,
|
|
254
|
+
` Creator: ${data.creator || "N/A"}`,
|
|
255
|
+
];
|
|
256
|
+
if (data.hex_raw_proposal) {
|
|
257
|
+
lines.push(` Has proposal hex: Yes (${String(data.hex_raw_proposal).length} chars)`);
|
|
258
|
+
}
|
|
259
|
+
return textResult(lines.join("\n"));
|
|
260
|
+
} catch (e) {
|
|
261
|
+
return errorResult(e);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export const GetBalanceShape = {};
|
|
4
|
+
export const GetAddressShape = {};
|
|
5
|
+
export const GetWalletStatusShape = {};
|
|
6
|
+
|
|
7
|
+
export const TransferShape = {
|
|
8
|
+
address: z.string().describe("Destination address"),
|
|
9
|
+
amount: z.string().describe("Amount in human-readable units (e.g. '1.5')"),
|
|
10
|
+
asset_id: z
|
|
11
|
+
.string()
|
|
12
|
+
.optional()
|
|
13
|
+
.describe("Asset ID to send. Omit for ZANO"),
|
|
14
|
+
payment_id: z.string().optional().describe("Payment ID (optional)"),
|
|
15
|
+
comment: z.string().optional().describe("Transaction comment (optional)"),
|
|
16
|
+
fee: z
|
|
17
|
+
.string()
|
|
18
|
+
.optional()
|
|
19
|
+
.describe("Fee in human-readable ZANO (default: 0.01)"),
|
|
20
|
+
mixin: z
|
|
21
|
+
.number()
|
|
22
|
+
.int()
|
|
23
|
+
.optional()
|
|
24
|
+
.describe("Mixin count (default: 15 normal, 0 auditable)"),
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const GetRecentTransactionsShape = {
|
|
28
|
+
offset: z.number().int().min(0).default(0).describe("Offset"),
|
|
29
|
+
count: z.number().int().min(1).max(100).default(20).describe("Number of transactions"),
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const SearchTransactionsShape = {
|
|
33
|
+
tx_id: z.string().optional().describe("Transaction hash to search for"),
|
|
34
|
+
in: z.boolean().optional().describe("Include incoming transactions"),
|
|
35
|
+
out: z.boolean().optional().describe("Include outgoing transactions"),
|
|
36
|
+
pool: z.boolean().optional().describe("Include pool transactions"),
|
|
37
|
+
filter_by_height: z.boolean().optional(),
|
|
38
|
+
min_height: z.number().int().optional(),
|
|
39
|
+
max_height: z.number().int().optional(),
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const SignMessageShape = {
|
|
43
|
+
message: z.string().describe("Message to sign"),
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const SaveWalletShape = {};
|
|
47
|
+
|
|
48
|
+
export const MakeIntegratedAddressShape = {
|
|
49
|
+
payment_id: z
|
|
50
|
+
.string()
|
|
51
|
+
.optional()
|
|
52
|
+
.describe("Payment ID (auto-generated if omitted)"),
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const SplitIntegratedAddressShape = {
|
|
56
|
+
integrated_address: z.string().describe("Integrated address to decode"),
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
export const GetMiningHistoryShape = {
|
|
60
|
+
v: z.number().int().default(0).describe("Version (0)"),
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
export const SweepBelowShape = {
|
|
64
|
+
address: z.string().describe("Address to sweep to"),
|
|
65
|
+
amount: z
|
|
66
|
+
.string()
|
|
67
|
+
.describe("Threshold in human-readable ZANO - sweep outputs below this"),
|
|
68
|
+
mixin: z.number().int().optional().describe("Mixin count (default: 15)"),
|
|
69
|
+
fee: z
|
|
70
|
+
.string()
|
|
71
|
+
.optional()
|
|
72
|
+
.describe("Fee in human-readable ZANO (default: 0.01)"),
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// Schemas and types
|
|
76
|
+
export const TransferSchema = z.object(TransferShape);
|
|
77
|
+
export const GetRecentTransactionsSchema = z.object(GetRecentTransactionsShape);
|
|
78
|
+
export const SearchTransactionsSchema = z.object(SearchTransactionsShape);
|
|
79
|
+
export const SignMessageSchema = z.object(SignMessageShape);
|
|
80
|
+
export const MakeIntegratedAddressSchema = z.object(MakeIntegratedAddressShape);
|
|
81
|
+
export const SplitIntegratedAddressSchema = z.object(SplitIntegratedAddressShape);
|
|
82
|
+
export const GetMiningHistorySchema = z.object(GetMiningHistoryShape);
|
|
83
|
+
export const SweepBelowSchema = z.object(SweepBelowShape);
|
|
84
|
+
|
|
85
|
+
export type TransferInput = z.infer<typeof TransferSchema>;
|
|
86
|
+
export type GetRecentTransactionsInput = z.infer<typeof GetRecentTransactionsSchema>;
|
|
87
|
+
export type SearchTransactionsInput = z.infer<typeof SearchTransactionsSchema>;
|
|
88
|
+
export type SignMessageInput = z.infer<typeof SignMessageSchema>;
|
|
89
|
+
export type MakeIntegratedAddressInput = z.infer<typeof MakeIntegratedAddressSchema>;
|
|
90
|
+
export type SplitIntegratedAddressInput = z.infer<typeof SplitIntegratedAddressSchema>;
|
|
91
|
+
export type GetMiningHistoryInput = z.infer<typeof GetMiningHistorySchema>;
|
|
92
|
+
export type SweepBelowInput = z.infer<typeof SweepBelowSchema>;
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { WalletClient } from "../../clients/wallet.js";
|
|
2
|
+
import { DaemonClient } from "../../clients/daemon.js";
|
|
3
|
+
import {
|
|
4
|
+
atomicToHuman,
|
|
5
|
+
humanToAtomic,
|
|
6
|
+
formatAssetAmount,
|
|
7
|
+
formatTimestamp,
|
|
8
|
+
formatZano,
|
|
9
|
+
getAssetInfo,
|
|
10
|
+
registerAsset,
|
|
11
|
+
} from "../../utils/formatting.js";
|
|
12
|
+
import { ZANO_ASSET_ID, ZANO_DECIMALS, DEFAULT_FEE, DEFAULT_MIXIN } from "../../utils/constants.js";
|
|
13
|
+
import type {
|
|
14
|
+
TransferInput,
|
|
15
|
+
GetRecentTransactionsInput,
|
|
16
|
+
SearchTransactionsInput,
|
|
17
|
+
SignMessageInput,
|
|
18
|
+
MakeIntegratedAddressInput,
|
|
19
|
+
SplitIntegratedAddressInput,
|
|
20
|
+
GetMiningHistoryInput,
|
|
21
|
+
SweepBelowInput,
|
|
22
|
+
} from "./definitions.js";
|
|
23
|
+
|
|
24
|
+
type ToolResult = { content: Array<{ type: "text"; text: string }> };
|
|
25
|
+
function textResult(text: string): ToolResult {
|
|
26
|
+
return { content: [{ type: "text", text }] };
|
|
27
|
+
}
|
|
28
|
+
function errorResult(error: unknown): ToolResult {
|
|
29
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
30
|
+
return textResult(`Error: ${msg}`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export class WalletHandlers {
|
|
34
|
+
private client: WalletClient;
|
|
35
|
+
private daemon: DaemonClient;
|
|
36
|
+
|
|
37
|
+
constructor(client: WalletClient, daemon: DaemonClient) {
|
|
38
|
+
this.client = client;
|
|
39
|
+
this.daemon = daemon;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async getBalance(): Promise<ToolResult> {
|
|
43
|
+
try {
|
|
44
|
+
const res = await this.client.call<Record<string, unknown>>("getbalance");
|
|
45
|
+
const lines = ["Wallet Balance:"];
|
|
46
|
+
|
|
47
|
+
const balance = BigInt(String(res.balance || 0));
|
|
48
|
+
const unlocked = BigInt(String(res.unlocked_balance || 0));
|
|
49
|
+
lines.push(` ZANO: ${atomicToHuman(unlocked, ZANO_DECIMALS)} (locked: ${atomicToHuman(balance - unlocked, ZANO_DECIMALS)})`);
|
|
50
|
+
|
|
51
|
+
const balances = res.balances as Array<Record<string, unknown>> | undefined;
|
|
52
|
+
if (balances && balances.length > 0) {
|
|
53
|
+
for (const b of balances) {
|
|
54
|
+
const assetId = String(b.asset_id || "");
|
|
55
|
+
if (assetId === ZANO_ASSET_ID) continue;
|
|
56
|
+
const assetInfo = (b.asset_info as Record<string, unknown>) || {};
|
|
57
|
+
const ticker = String(assetInfo.ticker || assetId.slice(0, 8));
|
|
58
|
+
const decimals = Number(assetInfo.decimal_point ?? 12);
|
|
59
|
+
registerAsset(assetId, ticker, decimals);
|
|
60
|
+
|
|
61
|
+
const bal = BigInt(String(b.balance || 0));
|
|
62
|
+
const ubal = BigInt(String(b.unlocked || 0));
|
|
63
|
+
lines.push(` ${ticker}: ${atomicToHuman(ubal, decimals)} (locked: ${atomicToHuman(bal - ubal, decimals)})`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return textResult(lines.join("\n"));
|
|
68
|
+
} catch (e) {
|
|
69
|
+
return errorResult(e);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async getAddress(): Promise<ToolResult> {
|
|
74
|
+
try {
|
|
75
|
+
const res = await this.client.call<Record<string, unknown>>("getaddress");
|
|
76
|
+
return textResult(`Wallet address: ${res.address}`);
|
|
77
|
+
} catch (e) {
|
|
78
|
+
return errorResult(e);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async getWalletStatus(): Promise<ToolResult> {
|
|
83
|
+
try {
|
|
84
|
+
const res = await this.client.call<Record<string, unknown>>("get_wallet_info");
|
|
85
|
+
const lines = [
|
|
86
|
+
"Wallet Status:",
|
|
87
|
+
` Address: ${res.address || "N/A"}`,
|
|
88
|
+
` Current height: ${res.current_height || "N/A"}`,
|
|
89
|
+
` Daemon height: ${res.current_daemon_height || "N/A"}`,
|
|
90
|
+
` Watch only: ${res.is_whatch_only ? "Yes" : "No"}`,
|
|
91
|
+
` In audit: ${res.is_auditable ? "Yes" : "No"}`,
|
|
92
|
+
` Min confirmations: ${res.mincounted_transfer_count ?? "N/A"}`,
|
|
93
|
+
` Transfer count: ${res.transfer_entries_count ?? "N/A"}`,
|
|
94
|
+
];
|
|
95
|
+
return textResult(lines.join("\n"));
|
|
96
|
+
} catch (e) {
|
|
97
|
+
return errorResult(e);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async transfer(input: TransferInput): Promise<ToolResult> {
|
|
102
|
+
try {
|
|
103
|
+
const assetId = input.asset_id || ZANO_ASSET_ID;
|
|
104
|
+
const info = getAssetInfo(assetId);
|
|
105
|
+
const decimals = info?.decimals ?? ZANO_DECIMALS;
|
|
106
|
+
const atomicAmount = humanToAtomic(input.amount, decimals);
|
|
107
|
+
const mixin = input.mixin ?? DEFAULT_MIXIN;
|
|
108
|
+
const fee = input.fee ? humanToAtomic(input.fee, ZANO_DECIMALS) : String(DEFAULT_FEE);
|
|
109
|
+
|
|
110
|
+
const dest: Record<string, unknown> = {
|
|
111
|
+
address: input.address,
|
|
112
|
+
amount: atomicAmount,
|
|
113
|
+
};
|
|
114
|
+
if (assetId !== ZANO_ASSET_ID) {
|
|
115
|
+
dest.asset_id = assetId;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const params: Record<string, unknown> = {
|
|
119
|
+
destinations: [dest],
|
|
120
|
+
fee: Number(fee),
|
|
121
|
+
mixin,
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
if (input.payment_id) params.payment_id = input.payment_id;
|
|
125
|
+
if (input.comment) params.comment = input.comment;
|
|
126
|
+
|
|
127
|
+
const res = await this.client.call<Record<string, unknown>>("transfer", params);
|
|
128
|
+
const ticker = info?.ticker || "ZANO";
|
|
129
|
+
return textResult(
|
|
130
|
+
`Transfer sent: ${input.amount} ${ticker} to ${input.address}\nTX hash: ${res.tx_hash}\nTX size: ${res.tx_size || "N/A"} bytes`,
|
|
131
|
+
);
|
|
132
|
+
} catch (e) {
|
|
133
|
+
return errorResult(e);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async getRecentTransactions(input: GetRecentTransactionsInput): Promise<ToolResult> {
|
|
138
|
+
try {
|
|
139
|
+
const res = await this.client.call<Record<string, unknown>>(
|
|
140
|
+
"get_recent_txs_and_info",
|
|
141
|
+
{ offset: input.offset, count: input.count, update_provision_info: true },
|
|
142
|
+
);
|
|
143
|
+
const transfers = (res.transfers || []) as Array<Record<string, unknown>>;
|
|
144
|
+
if (transfers.length === 0) {
|
|
145
|
+
return textResult("No recent transactions found.");
|
|
146
|
+
}
|
|
147
|
+
const lines = [`Recent Transactions (${transfers.length}):`];
|
|
148
|
+
for (const tx of transfers) {
|
|
149
|
+
const dir = tx.is_income ? "IN" : "OUT";
|
|
150
|
+
const amount = tx.amount ? formatZano(tx.amount as number) : "N/A";
|
|
151
|
+
const ts = formatTimestamp(Number(tx.timestamp || 0));
|
|
152
|
+
const hash = String(tx.tx_hash || "").slice(0, 16);
|
|
153
|
+
const comment = tx.comment ? ` "${tx.comment}"` : "";
|
|
154
|
+
lines.push(` [${dir}] ${amount} | ${ts} | ${hash}...${comment}`);
|
|
155
|
+
}
|
|
156
|
+
return textResult(lines.join("\n"));
|
|
157
|
+
} catch (e) {
|
|
158
|
+
return errorResult(e);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async searchTransactions(input: SearchTransactionsInput): Promise<ToolResult> {
|
|
163
|
+
try {
|
|
164
|
+
const res = await this.client.call<Record<string, unknown>>(
|
|
165
|
+
"search_for_transactions",
|
|
166
|
+
input,
|
|
167
|
+
);
|
|
168
|
+
return textResult(JSON.stringify(res, null, 2));
|
|
169
|
+
} catch (e) {
|
|
170
|
+
return errorResult(e);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async signMessage(input: SignMessageInput): Promise<ToolResult> {
|
|
175
|
+
try {
|
|
176
|
+
// sign_message requires base64 encoded message
|
|
177
|
+
const b64 = Buffer.from(input.message).toString("base64");
|
|
178
|
+
const res = await this.client.call<{ sig: string }>("sign_message", { buff: b64 });
|
|
179
|
+
return textResult(`Signature: ${res.sig}`);
|
|
180
|
+
} catch (e) {
|
|
181
|
+
return errorResult(e);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async saveWallet(): Promise<ToolResult> {
|
|
186
|
+
try {
|
|
187
|
+
await this.client.call("store");
|
|
188
|
+
return textResult("Wallet state saved.");
|
|
189
|
+
} catch (e) {
|
|
190
|
+
return errorResult(e);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
async makeIntegratedAddress(input: MakeIntegratedAddressInput): Promise<ToolResult> {
|
|
195
|
+
try {
|
|
196
|
+
const params: Record<string, unknown> = {};
|
|
197
|
+
if (input.payment_id) params.payment_id = input.payment_id;
|
|
198
|
+
const res = await this.client.call<Record<string, unknown>>(
|
|
199
|
+
"make_integrated_address",
|
|
200
|
+
params,
|
|
201
|
+
);
|
|
202
|
+
return textResult(
|
|
203
|
+
`Integrated address: ${res.integrated_address}\nPayment ID: ${res.payment_id}`,
|
|
204
|
+
);
|
|
205
|
+
} catch (e) {
|
|
206
|
+
return errorResult(e);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async splitIntegratedAddress(input: SplitIntegratedAddressInput): Promise<ToolResult> {
|
|
211
|
+
try {
|
|
212
|
+
const res = await this.client.call<Record<string, unknown>>(
|
|
213
|
+
"split_integrated_address",
|
|
214
|
+
{ integrated_address: input.integrated_address },
|
|
215
|
+
);
|
|
216
|
+
return textResult(
|
|
217
|
+
`Standard address: ${res.standard_address}\nPayment ID: ${res.payment_id}`,
|
|
218
|
+
);
|
|
219
|
+
} catch (e) {
|
|
220
|
+
return errorResult(e);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async getMiningHistory(input: GetMiningHistoryInput): Promise<ToolResult> {
|
|
225
|
+
try {
|
|
226
|
+
const res = await this.client.call<Record<string, unknown>>(
|
|
227
|
+
"get_mining_history",
|
|
228
|
+
{ v: input.v },
|
|
229
|
+
);
|
|
230
|
+
const history = (res.mined_entries || []) as Array<Record<string, unknown>>;
|
|
231
|
+
if (history.length === 0) {
|
|
232
|
+
return textResult("No staking history found.");
|
|
233
|
+
}
|
|
234
|
+
const lines = [`Staking History (${history.length} entries):`];
|
|
235
|
+
for (const entry of history) {
|
|
236
|
+
const amount = entry.a ? formatZano(entry.a as number) : "N/A";
|
|
237
|
+
const ts = formatTimestamp(Number(entry.t || 0));
|
|
238
|
+
lines.push(` ${ts} | ${amount} | Block ${entry.h || "N/A"}`);
|
|
239
|
+
}
|
|
240
|
+
return textResult(lines.join("\n"));
|
|
241
|
+
} catch (e) {
|
|
242
|
+
return errorResult(e);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async sweepBelow(input: SweepBelowInput): Promise<ToolResult> {
|
|
247
|
+
try {
|
|
248
|
+
const atomicAmount = humanToAtomic(input.amount, ZANO_DECIMALS);
|
|
249
|
+
const mixin = input.mixin ?? DEFAULT_MIXIN;
|
|
250
|
+
const fee = input.fee ? humanToAtomic(input.fee, ZANO_DECIMALS) : String(DEFAULT_FEE);
|
|
251
|
+
|
|
252
|
+
const res = await this.client.call<Record<string, unknown>>(
|
|
253
|
+
"sweep_below",
|
|
254
|
+
{
|
|
255
|
+
address: input.address,
|
|
256
|
+
amount: Number(atomicAmount),
|
|
257
|
+
mixin,
|
|
258
|
+
fee: Number(fee),
|
|
259
|
+
},
|
|
260
|
+
);
|
|
261
|
+
return textResult(
|
|
262
|
+
`Sweep complete.\nTX hash: ${res.tx_hash}\nAmount swept: ${res.amount ? formatZano(res.amount as number) : "N/A"}`,
|
|
263
|
+
);
|
|
264
|
+
} catch (e) {
|
|
265
|
+
return errorResult(e);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export const ZANO_ASSET_ID =
|
|
2
|
+
"d6329b5b1f7c0805b5c345f4957554002a2f557845f64d7645dae0e051a6498a";
|
|
3
|
+
|
|
4
|
+
export const DEFAULT_PORTS = {
|
|
5
|
+
mainnet: { daemon: 11211, wallet: 11212 },
|
|
6
|
+
testnet: { daemon: 12211, wallet: 12212 },
|
|
7
|
+
} as const;
|
|
8
|
+
|
|
9
|
+
export const PUBLIC_NODES = {
|
|
10
|
+
mainnet: "http://37.27.100.59:10500/json_rpc",
|
|
11
|
+
testnet: "http://37.27.100.59:10505/json_rpc",
|
|
12
|
+
} as const;
|
|
13
|
+
|
|
14
|
+
export const ASSET_WHITELIST_URLS = {
|
|
15
|
+
mainnet: "https://api.zano.org/assets_whitelist.json",
|
|
16
|
+
testnet: "https://api.zano.org/assets_whitelist_testnet.json",
|
|
17
|
+
} as const;
|
|
18
|
+
|
|
19
|
+
export const TRADE_API_URL = "https://api.trade.zano.org";
|
|
20
|
+
|
|
21
|
+
export const ZANO_DECIMALS = 12;
|
|
22
|
+
export const DEFAULT_MIXIN = 15;
|
|
23
|
+
export const AUDITABLE_MIXIN = 0;
|
|
24
|
+
export const DEFAULT_FEE = 10000000000; // 0.01 ZANO
|