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.
Files changed (113) hide show
  1. package/README.md +195 -0
  2. package/dist/clients/daemon.d.ts +6 -0
  3. package/dist/clients/daemon.d.ts.map +1 -0
  4. package/dist/clients/daemon.js +30 -0
  5. package/dist/clients/daemon.js.map +1 -0
  6. package/dist/clients/trade.d.ts +8 -0
  7. package/dist/clients/trade.d.ts.map +1 -0
  8. package/dist/clients/trade.js +33 -0
  9. package/dist/clients/trade.js.map +1 -0
  10. package/dist/clients/wallet.d.ts +8 -0
  11. package/dist/clients/wallet.d.ts.map +1 -0
  12. package/dist/clients/wallet.js +45 -0
  13. package/dist/clients/wallet.js.map +1 -0
  14. package/dist/config.d.ts +30 -0
  15. package/dist/config.d.ts.map +1 -0
  16. package/dist/config.js +42 -0
  17. package/dist/config.js.map +1 -0
  18. package/dist/index.d.ts +3 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +15 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/logger.d.ts +9 -0
  23. package/dist/logger.d.ts.map +1 -0
  24. package/dist/logger.js +28 -0
  25. package/dist/logger.js.map +1 -0
  26. package/dist/prompts/index.d.ts +4 -0
  27. package/dist/prompts/index.d.ts.map +1 -0
  28. package/dist/prompts/index.js +54 -0
  29. package/dist/prompts/index.js.map +1 -0
  30. package/dist/resources/index.d.ts +4 -0
  31. package/dist/resources/index.d.ts.map +1 -0
  32. package/dist/resources/index.js +60 -0
  33. package/dist/resources/index.js.map +1 -0
  34. package/dist/server.d.ts +4 -0
  35. package/dist/server.d.ts.map +1 -0
  36. package/dist/server.js +22 -0
  37. package/dist/server.js.map +1 -0
  38. package/dist/tools/assets/definitions.d.ts +127 -0
  39. package/dist/tools/assets/definitions.d.ts.map +1 -0
  40. package/dist/tools/assets/definitions.js +43 -0
  41. package/dist/tools/assets/definitions.js.map +1 -0
  42. package/dist/tools/assets/handlers.d.ts +21 -0
  43. package/dist/tools/assets/handlers.d.ts.map +1 -0
  44. package/dist/tools/assets/handlers.js +115 -0
  45. package/dist/tools/assets/handlers.js.map +1 -0
  46. package/dist/tools/daemon/definitions.d.ts +139 -0
  47. package/dist/tools/daemon/definitions.d.ts.map +1 -0
  48. package/dist/tools/daemon/definitions.js +55 -0
  49. package/dist/tools/daemon/definitions.js.map +1 -0
  50. package/dist/tools/daemon/handlers.d.ts +30 -0
  51. package/dist/tools/daemon/handlers.d.ts.map +1 -0
  52. package/dist/tools/daemon/handlers.js +274 -0
  53. package/dist/tools/daemon/handlers.js.map +1 -0
  54. package/dist/tools/register.d.ts +4 -0
  55. package/dist/tools/register.d.ts.map +1 -0
  56. package/dist/tools/register.js +93 -0
  57. package/dist/tools/register.js.map +1 -0
  58. package/dist/tools/swap/definitions.d.ts +103 -0
  59. package/dist/tools/swap/definitions.d.ts.map +1 -0
  60. package/dist/tools/swap/definitions.js +28 -0
  61. package/dist/tools/swap/definitions.js.map +1 -0
  62. package/dist/tools/swap/handlers.d.ts +17 -0
  63. package/dist/tools/swap/handlers.d.ts.map +1 -0
  64. package/dist/tools/swap/handlers.js +98 -0
  65. package/dist/tools/swap/handlers.js.map +1 -0
  66. package/dist/tools/trade/definitions.d.ts +137 -0
  67. package/dist/tools/trade/definitions.d.ts.map +1 -0
  68. package/dist/tools/trade/definitions.js +48 -0
  69. package/dist/tools/trade/definitions.js.map +1 -0
  70. package/dist/tools/trade/handlers.d.ts +23 -0
  71. package/dist/tools/trade/handlers.d.ts.map +1 -0
  72. package/dist/tools/trade/handlers.js +196 -0
  73. package/dist/tools/trade/handlers.js.map +1 -0
  74. package/dist/tools/wallet/definitions.d.ts +158 -0
  75. package/dist/tools/wallet/definitions.d.ts.map +1 -0
  76. package/dist/tools/wallet/definitions.js +73 -0
  77. package/dist/tools/wallet/definitions.js.map +1 -0
  78. package/dist/tools/wallet/handlers.d.ts +28 -0
  79. package/dist/tools/wallet/handlers.d.ts.map +1 -0
  80. package/dist/tools/wallet/handlers.js +214 -0
  81. package/dist/tools/wallet/handlers.js.map +1 -0
  82. package/dist/utils/constants.d.ts +25 -0
  83. package/dist/utils/constants.d.ts.map +1 -0
  84. package/dist/utils/constants.js +19 -0
  85. package/dist/utils/constants.js.map +1 -0
  86. package/dist/utils/formatting.d.ts +14 -0
  87. package/dist/utils/formatting.d.ts.map +1 -0
  88. package/dist/utils/formatting.js +68 -0
  89. package/dist/utils/formatting.js.map +1 -0
  90. package/package.json +43 -0
  91. package/src/clients/daemon.ts +41 -0
  92. package/src/clients/trade.ts +51 -0
  93. package/src/clients/wallet.ts +59 -0
  94. package/src/config.ts +56 -0
  95. package/src/index.ts +20 -0
  96. package/src/logger.ts +33 -0
  97. package/src/prompts/index.ts +80 -0
  98. package/src/resources/index.ts +73 -0
  99. package/src/server.ts +26 -0
  100. package/src/tools/assets/definitions.ts +58 -0
  101. package/src/tools/assets/handlers.ts +140 -0
  102. package/src/tools/daemon/definitions.ts +86 -0
  103. package/src/tools/daemon/handlers.ts +349 -0
  104. package/src/tools/register.ts +194 -0
  105. package/src/tools/swap/definitions.ts +36 -0
  106. package/src/tools/swap/handlers.ts +139 -0
  107. package/src/tools/trade/definitions.ts +67 -0
  108. package/src/tools/trade/handlers.ts +264 -0
  109. package/src/tools/wallet/definitions.ts +92 -0
  110. package/src/tools/wallet/handlers.ts +268 -0
  111. package/src/utils/constants.ts +24 -0
  112. package/src/utils/formatting.ts +78 -0
  113. package/tsconfig.json +19 -0
@@ -0,0 +1,140 @@
1
+ import { WalletClient } from "../../clients/wallet.js";
2
+ import { humanToAtomic, formatAssetAmount } from "../../utils/formatting.js";
3
+ import type {
4
+ DeployAssetInput,
5
+ EmitAssetInput,
6
+ BurnAssetInput,
7
+ UpdateAssetInput,
8
+ TransferAssetOwnershipInput,
9
+ WhitelistAssetInput,
10
+ RemoveAssetFromWhitelistInput,
11
+ } from "./definitions.js";
12
+
13
+ type ToolResult = { content: Array<{ type: "text"; text: string }> };
14
+ function textResult(text: string): ToolResult {
15
+ return { content: [{ type: "text", text }] };
16
+ }
17
+ function errorResult(error: unknown): ToolResult {
18
+ const msg = error instanceof Error ? error.message : String(error);
19
+ return textResult(`Error: ${msg}`);
20
+ }
21
+
22
+ export class AssetHandlers {
23
+ private client: WalletClient;
24
+
25
+ constructor(client: WalletClient) {
26
+ this.client = client;
27
+ }
28
+
29
+ async deployAsset(input: DeployAssetInput): Promise<ToolResult> {
30
+ try {
31
+ const decimals = input.decimal_point;
32
+ const totalMaxSupply = humanToAtomic(input.total_max_supply, decimals);
33
+ const currentSupply = humanToAtomic(input.current_supply, decimals);
34
+
35
+ const res = await this.client.call<Record<string, unknown>>("deploy_asset", {
36
+ asset_descriptor: {
37
+ ticker: input.ticker,
38
+ full_name: input.full_name,
39
+ total_max_supply: Number(totalMaxSupply),
40
+ current_supply: Number(currentSupply),
41
+ decimal_point: decimals,
42
+ meta_info: input.meta_info || "",
43
+ hidden_supply: input.hidden_supply || false,
44
+ },
45
+ });
46
+ return textResult(
47
+ `Asset deployed!\nAsset ID: ${res.new_asset_id || res.asset_id}\nTX hash: ${res.tx_hash || "N/A"}`,
48
+ );
49
+ } catch (e) {
50
+ return errorResult(e);
51
+ }
52
+ }
53
+
54
+ async emitAsset(input: EmitAssetInput): Promise<ToolResult> {
55
+ try {
56
+ // Need to look up decimals from the asset - default to 12 if unknown
57
+ const res = await this.client.call<Record<string, unknown>>("emit_asset", {
58
+ asset_id: input.asset_id,
59
+ amount: input.amount,
60
+ });
61
+ return textResult(
62
+ `Asset emitted: ${input.amount} of ${input.asset_id.slice(0, 16)}...\nTX hash: ${res.tx_hash || "N/A"}`,
63
+ );
64
+ } catch (e) {
65
+ return errorResult(e);
66
+ }
67
+ }
68
+
69
+ async burnAsset(input: BurnAssetInput): Promise<ToolResult> {
70
+ try {
71
+ const res = await this.client.call<Record<string, unknown>>("burn_asset", {
72
+ asset_id: input.asset_id,
73
+ amount: input.amount,
74
+ });
75
+ return textResult(
76
+ `Asset burned: ${input.amount} of ${input.asset_id.slice(0, 16)}...\nTX hash: ${res.tx_hash || "N/A"}`,
77
+ );
78
+ } catch (e) {
79
+ return errorResult(e);
80
+ }
81
+ }
82
+
83
+ async updateAsset(input: UpdateAssetInput): Promise<ToolResult> {
84
+ try {
85
+ const descriptor: Record<string, unknown> = {};
86
+ if (input.ticker) descriptor.ticker = input.ticker;
87
+ if (input.full_name) descriptor.full_name = input.full_name;
88
+ if (input.meta_info) descriptor.meta_info = input.meta_info;
89
+
90
+ const res = await this.client.call<Record<string, unknown>>("update_asset", {
91
+ asset_id: input.asset_id,
92
+ asset_descriptor: descriptor,
93
+ });
94
+ return textResult(
95
+ `Asset updated: ${input.asset_id.slice(0, 16)}...\nTX hash: ${res.tx_hash || "N/A"}`,
96
+ );
97
+ } catch (e) {
98
+ return errorResult(e);
99
+ }
100
+ }
101
+
102
+ async transferAssetOwnership(input: TransferAssetOwnershipInput): Promise<ToolResult> {
103
+ try {
104
+ const res = await this.client.call<Record<string, unknown>>(
105
+ "transfer_asset_ownership",
106
+ {
107
+ asset_id: input.asset_id,
108
+ new_owner: input.new_owner,
109
+ },
110
+ );
111
+ return textResult(
112
+ `Ownership transferred for ${input.asset_id.slice(0, 16)}...\nNew owner: ${input.new_owner}\nTX hash: ${res.tx_hash || "N/A"}`,
113
+ );
114
+ } catch (e) {
115
+ return errorResult(e);
116
+ }
117
+ }
118
+
119
+ async whitelistAsset(input: WhitelistAssetInput): Promise<ToolResult> {
120
+ try {
121
+ await this.client.call("assets_whitelist_add", {
122
+ asset_id: input.asset_id,
123
+ });
124
+ return textResult(`Asset ${input.asset_id.slice(0, 16)}... added to whitelist.`);
125
+ } catch (e) {
126
+ return errorResult(e);
127
+ }
128
+ }
129
+
130
+ async removeAssetFromWhitelist(input: RemoveAssetFromWhitelistInput): Promise<ToolResult> {
131
+ try {
132
+ await this.client.call("assets_whitelist_remove", {
133
+ asset_id: input.asset_id,
134
+ });
135
+ return textResult(`Asset ${input.asset_id.slice(0, 16)}... removed from whitelist.`);
136
+ } catch (e) {
137
+ return errorResult(e);
138
+ }
139
+ }
140
+ }
@@ -0,0 +1,86 @@
1
+ import { z } from "zod";
2
+
3
+ // --- Zod raw shapes (passed to server.tool()) ---
4
+
5
+ export const GetNetworkInfoShape = {};
6
+
7
+ export const GetHeightShape = {};
8
+
9
+ export const GetBlockByHeightShape = {
10
+ height: z.number().int().min(0).describe("Block height"),
11
+ };
12
+
13
+ export const GetBlockByHashShape = {
14
+ hash: z.string().describe("Block hash"),
15
+ };
16
+
17
+ export const GetLastBlockShape = {};
18
+
19
+ export const GetBlockDetailsShape = {
20
+ id: z.string().describe("Block hash"),
21
+ };
22
+
23
+ export const GetTransactionShape = {
24
+ tx_hash: z.string().describe("Transaction hash"),
25
+ };
26
+
27
+ export const GetTransactionsShape = {
28
+ tx_hashes: z.array(z.string()).describe("Array of transaction hashes"),
29
+ };
30
+
31
+ export const GetPoolInfoShape = {};
32
+
33
+ export const GetAssetInfoShape = {
34
+ asset_id: z.string().describe("Asset ID (64-char hex)"),
35
+ };
36
+
37
+ export const GetAssetsListShape = {
38
+ offset: z.number().int().min(0).default(0).describe("Offset for pagination"),
39
+ count: z.number().int().min(1).max(100).default(50).describe("Number of assets to return"),
40
+ };
41
+
42
+ export const ResolveAliasShape = {
43
+ alias: z.string().describe("Zano alias (without @)"),
44
+ };
45
+
46
+ export const GetAliasByAddressShape = {
47
+ address: z.string().describe("Zano address"),
48
+ };
49
+
50
+ export const SearchBlockchainShape = {
51
+ id: z.string().describe("Hash, alias, or address to search for"),
52
+ };
53
+
54
+ export const ValidateSignatureShape = {
55
+ buff: z.string().describe("Message that was signed"),
56
+ address: z.string().describe("Address of the signer"),
57
+ signature: z.string().describe("Signature to validate"),
58
+ };
59
+
60
+ // --- Zod full schemas (for type inference) ---
61
+
62
+ export const GetBlockByHeightSchema = z.object(GetBlockByHeightShape);
63
+ export const GetBlockByHashSchema = z.object(GetBlockByHashShape);
64
+ export const GetBlockDetailsSchema = z.object(GetBlockDetailsShape);
65
+ export const GetTransactionSchema = z.object(GetTransactionShape);
66
+ export const GetTransactionsSchema = z.object(GetTransactionsShape);
67
+ export const GetAssetInfoSchema = z.object(GetAssetInfoShape);
68
+ export const GetAssetsListSchema = z.object(GetAssetsListShape);
69
+ export const ResolveAliasSchema = z.object(ResolveAliasShape);
70
+ export const GetAliasByAddressSchema = z.object(GetAliasByAddressShape);
71
+ export const SearchBlockchainSchema = z.object(SearchBlockchainShape);
72
+ export const ValidateSignatureSchema = z.object(ValidateSignatureShape);
73
+
74
+ // --- TypeScript types ---
75
+
76
+ export type GetBlockByHeightInput = z.infer<typeof GetBlockByHeightSchema>;
77
+ export type GetBlockByHashInput = z.infer<typeof GetBlockByHashSchema>;
78
+ export type GetBlockDetailsInput = z.infer<typeof GetBlockDetailsSchema>;
79
+ export type GetTransactionInput = z.infer<typeof GetTransactionSchema>;
80
+ export type GetTransactionsInput = z.infer<typeof GetTransactionsSchema>;
81
+ export type GetAssetInfoInput = z.infer<typeof GetAssetInfoSchema>;
82
+ export type GetAssetsListInput = z.infer<typeof GetAssetsListSchema>;
83
+ export type ResolveAliasInput = z.infer<typeof ResolveAliasSchema>;
84
+ export type GetAliasByAddressInput = z.infer<typeof GetAliasByAddressSchema>;
85
+ export type SearchBlockchainInput = z.infer<typeof SearchBlockchainSchema>;
86
+ export type ValidateSignatureInput = z.infer<typeof ValidateSignatureSchema>;
@@ -0,0 +1,349 @@
1
+ import { DaemonClient } from "../../clients/daemon.js";
2
+ import {
3
+ formatZano,
4
+ formatTimestamp,
5
+ formatDifficulty,
6
+ formatHashrate,
7
+ formatAssetAmount,
8
+ } from "../../utils/formatting.js";
9
+ import type {
10
+ GetBlockByHeightInput,
11
+ GetBlockByHashInput,
12
+ GetBlockDetailsInput,
13
+ GetTransactionInput,
14
+ GetTransactionsInput,
15
+ GetAssetInfoInput,
16
+ GetAssetsListInput,
17
+ ResolveAliasInput,
18
+ GetAliasByAddressInput,
19
+ SearchBlockchainInput,
20
+ ValidateSignatureInput,
21
+ } from "./definitions.js";
22
+
23
+ export type ToolResult = {
24
+ content: Array<{ type: "text"; text: string }>;
25
+ };
26
+
27
+ function textResult(text: string): ToolResult {
28
+ return { content: [{ type: "text", text }] };
29
+ }
30
+
31
+ function errorResult(error: unknown): ToolResult {
32
+ const msg = error instanceof Error ? error.message : String(error);
33
+ return textResult(`Error: ${msg}`);
34
+ }
35
+
36
+ export class DaemonHandlers {
37
+ private client: DaemonClient;
38
+
39
+ constructor(client: DaemonClient) {
40
+ this.client = client;
41
+ }
42
+
43
+ async getNetworkInfo(): Promise<ToolResult> {
44
+ try {
45
+ const info = await this.client.call<Record<string, unknown>>("getinfo");
46
+ const lines = [
47
+ `Zano Network Status`,
48
+ ` Height: ${info.height}`,
49
+ ` Network height: ${info.max_net_seen_height || "N/A"}`,
50
+ ` Difficulty: ${info.difficulty}`,
51
+ ` PoS difficulty: ${info.pos_difficulty}`,
52
+ ` Hashrate: ${info.current_network_hashrate_350 || info.current_network_hashrate_50 || "N/A"}`,
53
+ ` Connections: ${info.outgoing_connections_count} out / ${info.incoming_connections_count} in`,
54
+ ` TX pool: ${info.tx_pool_size} transactions`,
55
+ ` Alt blocks: ${info.alt_blocks_count}`,
56
+ ` Block reward: ${formatZano(info.block_reward as number)}`,
57
+ ` Grey peerlist: ${info.grey_peerlist_size}`,
58
+ ` White peerlist: ${info.white_peerlist_size}`,
59
+ ` Alias count: ${info.alias_count}`,
60
+ ` Daemon network: ${info.testnet ? "testnet" : "mainnet"}`,
61
+ ` Synchronized: ${Number(info.height) >= Number(info.max_net_seen_height || 0) ? "Yes" : "No"}`,
62
+ ];
63
+ return textResult(lines.join("\n"));
64
+ } catch (e) {
65
+ return errorResult(e);
66
+ }
67
+ }
68
+
69
+ async getHeight(): Promise<ToolResult> {
70
+ try {
71
+ const res = await this.client.call<{ height: number }>("getheight");
72
+ return textResult(`Current blockchain height: ${res.height}`);
73
+ } catch (e) {
74
+ return errorResult(e);
75
+ }
76
+ }
77
+
78
+ async getBlockByHeight(input: GetBlockByHeightInput): Promise<ToolResult> {
79
+ try {
80
+ const res = await this.client.call<{ block_header: Record<string, unknown> }>(
81
+ "getblockheaderbyheight",
82
+ { height: input.height },
83
+ );
84
+ return textResult(this.formatBlockHeader(res.block_header));
85
+ } catch (e) {
86
+ return errorResult(e);
87
+ }
88
+ }
89
+
90
+ async getBlockByHash(input: GetBlockByHashInput): Promise<ToolResult> {
91
+ try {
92
+ const res = await this.client.call<{ block_header: Record<string, unknown> }>(
93
+ "getblockheaderbyhash",
94
+ { hash: input.hash },
95
+ );
96
+ return textResult(this.formatBlockHeader(res.block_header));
97
+ } catch (e) {
98
+ return errorResult(e);
99
+ }
100
+ }
101
+
102
+ async getLastBlock(): Promise<ToolResult> {
103
+ try {
104
+ const res = await this.client.call<{ block_header: Record<string, unknown> }>(
105
+ "getlastblockheader",
106
+ );
107
+ return textResult(this.formatBlockHeader(res.block_header));
108
+ } catch (e) {
109
+ return errorResult(e);
110
+ }
111
+ }
112
+
113
+ async getBlockDetails(input: GetBlockDetailsInput): Promise<ToolResult> {
114
+ try {
115
+ const res = await this.client.call<Record<string, unknown>>(
116
+ "get_main_block_details",
117
+ { id: input.id },
118
+ );
119
+ const block = res.block_details || res;
120
+ const header = (block as Record<string, unknown>);
121
+ const lines = [
122
+ this.formatBlockHeader(header),
123
+ ` Miner address: ${header.miner_text_info || "N/A"}`,
124
+ ` Transactions: ${Array.isArray(header.transactions_details) ? (header.transactions_details as unknown[]).length : 0}`,
125
+ ];
126
+ if (Array.isArray(header.transactions_details)) {
127
+ for (const tx of header.transactions_details as Array<Record<string, unknown>>) {
128
+ lines.push(` TX: ${tx.id} (fee: ${tx.fee ? formatZano(tx.fee as number) : "coinbase"})`);
129
+ }
130
+ }
131
+ return textResult(lines.join("\n"));
132
+ } catch (e) {
133
+ return errorResult(e);
134
+ }
135
+ }
136
+
137
+ async getTransaction(input: GetTransactionInput): Promise<ToolResult> {
138
+ try {
139
+ const res = await this.client.call<{ tx_info: Record<string, unknown> }>(
140
+ "get_tx_details",
141
+ { tx_hash: input.tx_hash },
142
+ );
143
+ const tx = res.tx_info || res;
144
+ return textResult(this.formatTransaction(tx));
145
+ } catch (e) {
146
+ return errorResult(e);
147
+ }
148
+ }
149
+
150
+ async getTransactions(input: GetTransactionsInput): Promise<ToolResult> {
151
+ try {
152
+ const res = await this.client.call<Record<string, unknown>>(
153
+ "gettransactions",
154
+ { txs_hashes: input.tx_hashes },
155
+ );
156
+ const txs = (res.txs_as_json || res.txs || []) as Array<Record<string, unknown>>;
157
+ if (txs.length === 0) {
158
+ return textResult("No transactions found for the provided hashes.");
159
+ }
160
+ const lines = txs.map(
161
+ (tx, i) => `[${i + 1}] ${typeof tx === "string" ? tx : JSON.stringify(tx, null, 2)}`,
162
+ );
163
+ return textResult(lines.join("\n\n"));
164
+ } catch (e) {
165
+ return errorResult(e);
166
+ }
167
+ }
168
+
169
+ async getPoolInfo(): Promise<ToolResult> {
170
+ try {
171
+ const res = await this.client.call<Record<string, unknown>>("get_pool_info");
172
+ const lines = [
173
+ `Transaction Pool`,
174
+ ` Pool size: ${res.tx_count ?? "N/A"}`,
175
+ ];
176
+ if (Array.isArray(res.transactions)) {
177
+ for (const tx of res.transactions as Array<Record<string, unknown>>) {
178
+ lines.push(` TX: ${tx.id} (size: ${tx.blob_size} bytes, fee: ${tx.fee ? formatZano(tx.fee as number) : "N/A"})`);
179
+ }
180
+ }
181
+ if (res.tx_count === 0 || (!res.transactions && !res.tx_count)) {
182
+ lines.push(" (empty pool)");
183
+ }
184
+ return textResult(lines.join("\n"));
185
+ } catch (e) {
186
+ return errorResult(e);
187
+ }
188
+ }
189
+
190
+ async getAssetInfo(input: GetAssetInfoInput): Promise<ToolResult> {
191
+ try {
192
+ const res = await this.client.call<{ asset_descriptor: Record<string, unknown> }>(
193
+ "get_asset_info",
194
+ { asset_id: input.asset_id },
195
+ );
196
+ const asset = res.asset_descriptor || res;
197
+ const lines = [
198
+ `Asset: ${asset.full_name || asset.ticker || "Unknown"}`,
199
+ ` Ticker: ${asset.ticker || "N/A"}`,
200
+ ` Asset ID: ${input.asset_id}`,
201
+ ` Decimals: ${asset.decimal_point ?? 12}`,
202
+ ` Total supply: ${asset.total_max_supply || asset.current_supply || "N/A"}`,
203
+ ` Current supply: ${asset.current_supply || "N/A"}`,
204
+ ` Owner: ${asset.owner || "N/A"}`,
205
+ ` Meta info: ${asset.meta_info || "N/A"}`,
206
+ ` Hidden supply: ${asset.hidden_supply ? "Yes" : "No"}`,
207
+ ];
208
+ return textResult(lines.join("\n"));
209
+ } catch (e) {
210
+ return errorResult(e);
211
+ }
212
+ }
213
+
214
+ async getAssetsList(input: GetAssetsListInput): Promise<ToolResult> {
215
+ try {
216
+ const res = await this.client.call<{ assets: Array<Record<string, unknown>> }>(
217
+ "get_assets_list",
218
+ { offset: input.offset, count: input.count },
219
+ );
220
+ const assets = res.assets || [];
221
+ if (assets.length === 0) {
222
+ return textResult("No assets found.");
223
+ }
224
+ const lines = [`Registered Assets (offset: ${input.offset}, count: ${assets.length}):`];
225
+ for (const a of assets) {
226
+ const desc = (a.asset_descriptor || a) as Record<string, unknown>;
227
+ lines.push(
228
+ ` ${desc.ticker || "?"} - ${desc.full_name || "Unknown"} (ID: ${(a.asset_id as string)?.slice(0, 16)}..., decimals: ${desc.decimal_point ?? 12})`,
229
+ );
230
+ }
231
+ return textResult(lines.join("\n"));
232
+ } catch (e) {
233
+ return errorResult(e);
234
+ }
235
+ }
236
+
237
+ async resolveAlias(input: ResolveAliasInput): Promise<ToolResult> {
238
+ try {
239
+ const res = await this.client.call<{ alias_details: Record<string, unknown> }>(
240
+ "get_alias_details",
241
+ { alias: input.alias },
242
+ );
243
+ const details = res.alias_details || res;
244
+ const lines = [
245
+ `Alias: @${input.alias}`,
246
+ ` Address: ${details.address || "N/A"}`,
247
+ ` Comment: ${details.comment || ""}`,
248
+ ` Tracking key: ${details.tracking_key || "N/A"}`,
249
+ ];
250
+ return textResult(lines.join("\n"));
251
+ } catch (e) {
252
+ return errorResult(e);
253
+ }
254
+ }
255
+
256
+ async getAliasByAddress(input: GetAliasByAddressInput): Promise<ToolResult> {
257
+ try {
258
+ const res = await this.client.call<{ alias_info_list?: Array<Record<string, unknown>> }>(
259
+ "get_alias_by_address",
260
+ { address: input.address },
261
+ );
262
+ const list = res.alias_info_list;
263
+ if (!list || list.length === 0) {
264
+ return textResult(`No alias found for address ${input.address}`);
265
+ }
266
+ const alias = list[0];
267
+ return textResult(
268
+ `Address: ${input.address}\nAlias: @${alias.alias}\nComment: ${alias.comment || ""}`,
269
+ );
270
+ } catch (e) {
271
+ return errorResult(e);
272
+ }
273
+ }
274
+
275
+ async searchBlockchain(input: SearchBlockchainInput): Promise<ToolResult> {
276
+ try {
277
+ const res = await this.client.call<Record<string, unknown>>(
278
+ "search_by_id",
279
+ { id: input.id },
280
+ );
281
+ return textResult(
282
+ `Search results for "${input.id}":\n${JSON.stringify(res, null, 2)}`,
283
+ );
284
+ } catch (e) {
285
+ return errorResult(e);
286
+ }
287
+ }
288
+
289
+ async validateSignature(input: ValidateSignatureInput): Promise<ToolResult> {
290
+ try {
291
+ const res = await this.client.call<{ valid: boolean }>(
292
+ "validate_signature",
293
+ {
294
+ buff: input.buff,
295
+ address: input.address,
296
+ signature: input.signature,
297
+ },
298
+ );
299
+ return textResult(
300
+ `Signature validation: ${res.valid ? "VALID" : "INVALID"}`,
301
+ );
302
+ } catch (e) {
303
+ return errorResult(e);
304
+ }
305
+ }
306
+
307
+ // --- Private helpers ---
308
+
309
+ private formatBlockHeader(h: Record<string, unknown>): string {
310
+ const lines = [
311
+ `Block ${h.height}`,
312
+ ` Hash: ${h.hash || h.id}`,
313
+ ` Previous hash: ${h.prev_hash || "N/A"}`,
314
+ ` Timestamp: ${formatTimestamp(Number(h.timestamp || 0))}`,
315
+ ` Difficulty: ${formatDifficulty(Number(h.difficulty || 0))}`,
316
+ ` Reward: ${h.reward ? formatZano(h.reward as number) : "N/A"}`,
317
+ ` Type: ${h.is_pos ? "PoS" : "PoW"}`,
318
+ ` Depth: ${h.depth ?? "N/A"}`,
319
+ ` Orphan: ${h.orphan_status ? "Yes" : "No"}`,
320
+ ` TX count: ${h.num_txes ?? h.tx_count ?? "N/A"}`,
321
+ ];
322
+ return lines.join("\n");
323
+ }
324
+
325
+ private formatTransaction(tx: Record<string, unknown>): string {
326
+ const lines = [
327
+ `Transaction ${tx.id || tx.tx_hash}`,
328
+ ` Block: ${tx.keeper_block ?? "N/A"}`,
329
+ ` Timestamp: ${formatTimestamp(Number(tx.timestamp || 0))}`,
330
+ ` Fee: ${tx.fee ? formatZano(tx.fee as number) : "coinbase"}`,
331
+ ` Size: ${tx.blob_size || tx.size || "N/A"} bytes`,
332
+ ` Inputs: ${tx.ins_count ?? (Array.isArray(tx.ins) ? (tx.ins as unknown[]).length : "N/A")}`,
333
+ ` Outputs: ${tx.outs_count ?? (Array.isArray(tx.outs) ? (tx.outs as unknown[]).length : "N/A")}`,
334
+ ` Amount: ${tx.amount ? formatZano(tx.amount as number) : "N/A"}`,
335
+ ` Confirmations: ${tx.confirmations ?? "N/A"}`,
336
+ ];
337
+ if (tx.extra) {
338
+ const extra = tx.extra as Array<Record<string, unknown>>;
339
+ if (Array.isArray(extra)) {
340
+ for (const e of extra) {
341
+ if (e.type === "asset_descriptor_operation") {
342
+ lines.push(` Asset operation: ${JSON.stringify(e.asset_descriptor_operation || e)}`);
343
+ }
344
+ }
345
+ }
346
+ }
347
+ return lines.join("\n");
348
+ }
349
+ }