@ironwallet/mcp-server 1.0.0-preprod.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 (110) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +161 -0
  3. package/assets/knight.png +0 -0
  4. package/assets/wordmark.svg +20 -0
  5. package/dist/api/auth.d.ts +20 -0
  6. package/dist/api/auth.js +92 -0
  7. package/dist/api/auth.js.map +1 -0
  8. package/dist/api/balances.d.ts +25 -0
  9. package/dist/api/balances.js +565 -0
  10. package/dist/api/balances.js.map +1 -0
  11. package/dist/api/http.d.ts +34 -0
  12. package/dist/api/http.js +277 -0
  13. package/dist/api/http.js.map +1 -0
  14. package/dist/api/relay.d.ts +49 -0
  15. package/dist/api/relay.js +194 -0
  16. package/dist/api/relay.js.map +1 -0
  17. package/dist/api/rpc.d.ts +10 -0
  18. package/dist/api/rpc.js +26 -0
  19. package/dist/api/rpc.js.map +1 -0
  20. package/dist/api/swap.d.ts +134 -0
  21. package/dist/api/swap.js +554 -0
  22. package/dist/api/swap.js.map +1 -0
  23. package/dist/cli.d.ts +6 -0
  24. package/dist/cli.js +20 -0
  25. package/dist/cli.js.map +1 -0
  26. package/dist/config.d.ts +91 -0
  27. package/dist/config.js +175 -0
  28. package/dist/config.js.map +1 -0
  29. package/dist/generated/env-config.d.ts +5 -0
  30. package/dist/generated/env-config.js +23 -0
  31. package/dist/generated/env-config.js.map +1 -0
  32. package/dist/generated/env-profile.d.ts +15 -0
  33. package/dist/generated/env-profile.js +2 -0
  34. package/dist/generated/env-profile.js.map +1 -0
  35. package/dist/keystore/crypto.d.ts +17 -0
  36. package/dist/keystore/crypto.js +57 -0
  37. package/dist/keystore/crypto.js.map +1 -0
  38. package/dist/keystore/store.d.ts +45 -0
  39. package/dist/keystore/store.js +224 -0
  40. package/dist/keystore/store.js.map +1 -0
  41. package/dist/keystore/types.d.ts +31 -0
  42. package/dist/keystore/types.js +2 -0
  43. package/dist/keystore/types.js.map +1 -0
  44. package/dist/local-secrets.d.ts +16 -0
  45. package/dist/local-secrets.js +73 -0
  46. package/dist/local-secrets.js.map +1 -0
  47. package/dist/log.d.ts +30 -0
  48. package/dist/log.js +323 -0
  49. package/dist/log.js.map +1 -0
  50. package/dist/passphrase.d.ts +6 -0
  51. package/dist/passphrase.js +10 -0
  52. package/dist/passphrase.js.map +1 -0
  53. package/dist/policy.d.ts +22 -0
  54. package/dist/policy.js +108 -0
  55. package/dist/policy.js.map +1 -0
  56. package/dist/server.d.ts +6 -0
  57. package/dist/server.js +38 -0
  58. package/dist/server.js.map +1 -0
  59. package/dist/signing/bitcoin.d.ts +18 -0
  60. package/dist/signing/bitcoin.js +84 -0
  61. package/dist/signing/bitcoin.js.map +1 -0
  62. package/dist/signing/dispatch.d.ts +7 -0
  63. package/dist/signing/dispatch.js +35 -0
  64. package/dist/signing/dispatch.js.map +1 -0
  65. package/dist/signing/evm.d.ts +16 -0
  66. package/dist/signing/evm.js +59 -0
  67. package/dist/signing/evm.js.map +1 -0
  68. package/dist/signing/index.d.ts +10 -0
  69. package/dist/signing/index.js +57 -0
  70. package/dist/signing/index.js.map +1 -0
  71. package/dist/signing/solana.d.ts +18 -0
  72. package/dist/signing/solana.js +106 -0
  73. package/dist/signing/solana.js.map +1 -0
  74. package/dist/signing/swap.d.ts +10 -0
  75. package/dist/signing/swap.js +114 -0
  76. package/dist/signing/swap.js.map +1 -0
  77. package/dist/signing/ton.d.ts +33 -0
  78. package/dist/signing/ton.js +166 -0
  79. package/dist/signing/ton.js.map +1 -0
  80. package/dist/signing/tron.d.ts +11 -0
  81. package/dist/signing/tron.js +39 -0
  82. package/dist/signing/tron.js.map +1 -0
  83. package/dist/signing/types.d.ts +23 -0
  84. package/dist/signing/types.js +2 -0
  85. package/dist/signing/types.js.map +1 -0
  86. package/dist/signing/xrp.d.ts +11 -0
  87. package/dist/signing/xrp.js +122 -0
  88. package/dist/signing/xrp.js.map +1 -0
  89. package/dist/tools/helpers.d.ts +42 -0
  90. package/dist/tools/helpers.js +80 -0
  91. package/dist/tools/helpers.js.map +1 -0
  92. package/dist/tools/index.d.ts +7 -0
  93. package/dist/tools/index.js +16 -0
  94. package/dist/tools/index.js.map +1 -0
  95. package/dist/tools/swap.d.ts +6 -0
  96. package/dist/tools/swap.js +343 -0
  97. package/dist/tools/swap.js.map +1 -0
  98. package/dist/tools/transfer.d.ts +6 -0
  99. package/dist/tools/transfer.js +284 -0
  100. package/dist/tools/transfer.js.map +1 -0
  101. package/dist/tools/wallets.d.ts +6 -0
  102. package/dist/tools/wallets.js +79 -0
  103. package/dist/tools/wallets.js.map +1 -0
  104. package/dist/wallet/derive.d.ts +53 -0
  105. package/dist/wallet/derive.js +233 -0
  106. package/dist/wallet/derive.js.map +1 -0
  107. package/dist/web/manager.d.ts +15 -0
  108. package/dist/web/manager.js +444 -0
  109. package/dist/web/manager.js.map +1 -0
  110. package/package.json +54 -0
@@ -0,0 +1,343 @@
1
+ /**
2
+ * MCP tools for Swap Proxy (SWP): discovery, estimate, execute, status.
3
+ */
4
+ import { z } from "zod";
5
+ import { canSwap, parseNetworkId } from "../config.js";
6
+ import { getBalance } from "../api/balances.js";
7
+ import { assetKey, createSwap, estimateSwap, executeSwap, getSwapStatus, listSwapAssetsFrom, listSwapAssetsTo, listSwapNetworks, resolveSwapAsset, } from "../api/swap.js";
8
+ import { signSwapTransactions } from "../signing/swap.js";
9
+ import { enforcePolicy } from "../policy.js";
10
+ import { logInfo } from "../log.js";
11
+ const assetInputSchema = z.object({
12
+ network: z.string().describe("Network id, e.g. ethereum, bsc, bitcoin."),
13
+ symbol: z.string().describe("Asset symbol, e.g. ETH, USDT."),
14
+ address: z
15
+ .string()
16
+ .optional()
17
+ .nullable()
18
+ .describe("Token contract address. Omit/null for the native coin."),
19
+ decimals: z
20
+ .number()
21
+ .int()
22
+ .optional()
23
+ .describe("Token decimals. If omitted, resolved via SWP catalog (paged). Prefer passing decimals (+ address for tokens) from list_swap_assets."),
24
+ });
25
+ async function enrichAssets(items, addresses) {
26
+ return Promise.all(items.map(async (asset) => {
27
+ const net = parseNetworkId(asset.network);
28
+ const executable = Boolean(net && canSwap(net));
29
+ const walletAddress = net ? addresses[net] : undefined;
30
+ const receivable = Boolean(walletAddress);
31
+ let balance;
32
+ let balanceRaw;
33
+ if (walletAddress && net) {
34
+ try {
35
+ const bal = await getBalance(net, walletAddress, asset.address ?? undefined);
36
+ balance = bal.formatted;
37
+ balanceRaw = bal.raw;
38
+ }
39
+ catch {
40
+ // best-effort enrichment
41
+ }
42
+ }
43
+ return {
44
+ ...asset,
45
+ walletAddress,
46
+ balance,
47
+ balanceRaw,
48
+ executable,
49
+ receivable,
50
+ };
51
+ }));
52
+ }
53
+ async function buildSide(token, assetIn, walletAddress, amount, correlationId, fromForToLookup) {
54
+ const asset = await resolveSwapAsset(token, assetIn, {
55
+ correlationId,
56
+ fromForToLookup,
57
+ });
58
+ return {
59
+ asset,
60
+ address: walletAddress,
61
+ ...(amount !== undefined ? { amount } : {}),
62
+ };
63
+ }
64
+ function requireSellAddress(addresses, network) {
65
+ const networkId = parseNetworkId(network);
66
+ if (!networkId) {
67
+ throw new Error(`Unknown or unsupported sell network "${network}".`);
68
+ }
69
+ if (!canSwap(networkId)) {
70
+ throw new Error(`Swap sell/signing is not supported on "${networkId}" in this MCP build.`);
71
+ }
72
+ const address = addresses[networkId];
73
+ if (!address) {
74
+ throw new Error(`No ${networkId} address in the keystore for sell side. Re-open the wallet session to sync addresses.`);
75
+ }
76
+ return { networkId, address };
77
+ }
78
+ /** Buy side only needs a receive address — not local swap signing. */
79
+ function requireBuyAddress(addresses, network) {
80
+ const networkId = parseNetworkId(network);
81
+ if (!networkId) {
82
+ throw new Error(`Unknown or unsupported buy network "${network}". MCP has no keystore address for it.`);
83
+ }
84
+ const address = addresses[networkId];
85
+ if (!address) {
86
+ throw new Error(`No ${networkId} address in the keystore for buy/receive side. Re-open the wallet session to sync addresses.`);
87
+ }
88
+ return { networkId, address };
89
+ }
90
+ /** Shared prep for estimate_swap / execute_swap (session, maxMode, sides). */
91
+ async function prepareSwapSides(helpers, opts) {
92
+ const { entry, mnemonic, token } = await helpers.session(opts.wallet, opts.correlationId);
93
+ const sell = requireSellAddress(entry.addresses, opts.from.network);
94
+ const buy = requireBuyAddress(entry.addresses, opts.to.network);
95
+ const useMax = Boolean(opts.maxMode);
96
+ let sellAmount = opts.amount;
97
+ if (useMax && !sellAmount) {
98
+ const bal = await getBalance(sell.networkId, sell.address, opts.from.address ?? undefined);
99
+ sellAmount = bal.formatted;
100
+ if (Number(bal.raw) <= 0) {
101
+ throw new Error(`maxMode requested but ${opts.from.symbol} balance on ${sell.networkId} is zero.`);
102
+ }
103
+ }
104
+ if (!sellAmount) {
105
+ throw new Error("amount is required unless maxMode is true.");
106
+ }
107
+ const fromSide = await buildSide(token, opts.from, sell.address, sellAmount, opts.correlationId);
108
+ const toSide = await buildSide(token, opts.to, buy.address, undefined, opts.correlationId, fromSide.asset);
109
+ return {
110
+ entry,
111
+ mnemonic,
112
+ token,
113
+ sell,
114
+ buy,
115
+ useMax,
116
+ sellAmount,
117
+ fromSide,
118
+ toSide,
119
+ };
120
+ }
121
+ export function registerSwapTools(server, helpers) {
122
+ const { ok, withToolLog, session } = helpers;
123
+ server.registerTool("list_swap_networks", {
124
+ title: "List swap networks",
125
+ description: "List networks available for Swap Proxy (SWP). Use before listing swap assets.",
126
+ inputSchema: {
127
+ wallet: z.string().optional(),
128
+ },
129
+ }, async ({ wallet }) => withToolLog("list_swap_networks", { wallet }, async ({ correlationId }) => {
130
+ const { token } = await session(wallet, correlationId);
131
+ const networks = await listSwapNetworks(token, { correlationId });
132
+ return ok({
133
+ correlationId,
134
+ networks: networks.map((n) => {
135
+ const id = parseNetworkId(n.name);
136
+ return {
137
+ ...n,
138
+ networkId: id,
139
+ executable: Boolean(id && canSwap(id)),
140
+ };
141
+ }),
142
+ });
143
+ }));
144
+ server.registerTool("list_swap_assets", {
145
+ title: "List swap assets",
146
+ description: "List tradable assets from Swap Proxy. direction=from lists sell assets; direction=to requires fromNetwork/fromSymbol (and fromAddress for tokens) and lists buy assets. Enriches with wallet address and balance when possible.",
147
+ inputSchema: {
148
+ wallet: z.string().optional(),
149
+ direction: z.enum(["from", "to"]),
150
+ fromNetwork: z.string().optional(),
151
+ fromSymbol: z.string().optional(),
152
+ fromAddress: z.string().optional().nullable(),
153
+ search: z.string().optional(),
154
+ networks: z.array(z.string()).optional(),
155
+ page: z.number().int().positive().optional(),
156
+ pageSize: z.number().int().positive().max(100).optional(),
157
+ },
158
+ }, async (args) => withToolLog("list_swap_assets", args, async ({ correlationId }) => {
159
+ const { entry, token } = await session(args.wallet, correlationId);
160
+ let page;
161
+ if (args.direction === "from") {
162
+ page = await listSwapAssetsFrom(token, {
163
+ search: args.search,
164
+ networks: args.networks,
165
+ page: args.page,
166
+ pageSize: args.pageSize,
167
+ }, { correlationId });
168
+ }
169
+ else {
170
+ if (!args.fromNetwork || !args.fromSymbol) {
171
+ throw new Error("direction=to requires fromNetwork and fromSymbol (from a prior list_swap_assets from call).");
172
+ }
173
+ page = await listSwapAssetsTo(token, {
174
+ from: {
175
+ network: args.fromNetwork,
176
+ symbol: args.fromSymbol,
177
+ address: args.fromAddress ?? null,
178
+ },
179
+ search: args.search,
180
+ networks: args.networks,
181
+ page: args.page,
182
+ pageSize: args.pageSize,
183
+ }, { correlationId });
184
+ }
185
+ const items = await enrichAssets(page.items, entry.addresses);
186
+ return ok({
187
+ correlationId,
188
+ direction: args.direction,
189
+ page: page.page,
190
+ pageSize: page.pageSize,
191
+ total: page.total,
192
+ items,
193
+ });
194
+ }));
195
+ server.registerTool("estimate_swap", {
196
+ title: "Estimate swap",
197
+ description: "Get a Swap Proxy quote (amounts, fees, operationId). Quote can expire — prefer execute_swap for sending (it re-estimates). Amount is a decimal string; set maxMode=true to sell the full balance (backend corrects amount).",
198
+ inputSchema: {
199
+ wallet: z.string().optional(),
200
+ from: assetInputSchema,
201
+ to: assetInputSchema,
202
+ amount: z
203
+ .string()
204
+ .optional()
205
+ .describe("Sell amount as decimal string. Required unless maxMode."),
206
+ maxMode: z.boolean().optional(),
207
+ },
208
+ }, async ({ wallet, from, to, amount, maxMode }) => withToolLog("estimate_swap", { wallet, from, to, amount, maxMode }, async ({ correlationId }) => {
209
+ const prep = await prepareSwapSides(helpers, {
210
+ wallet,
211
+ from,
212
+ to,
213
+ amount,
214
+ maxMode,
215
+ correlationId,
216
+ });
217
+ const est = await estimateSwap(prep.token, {
218
+ from: prep.fromSide,
219
+ to: prep.toSide,
220
+ maxMode: prep.useMax,
221
+ }, { correlationId });
222
+ return ok({
223
+ correlationId,
224
+ operationId: est.operationId,
225
+ provider: est.provider,
226
+ amountFrom: est.from.amount ?? prep.sellAmount,
227
+ amountTo: est.to.amount,
228
+ correctedAmountFrom: est.correctedAmountFrom,
229
+ correctedAmountTo: est.correctedAmountTo,
230
+ fees: est.details?.fees,
231
+ details: est.details,
232
+ from: est.from,
233
+ to: est.to,
234
+ note: "Quote may expire. Call execute_swap to create+sign+broadcast (it performs a fresh estimate).",
235
+ });
236
+ }));
237
+ server.registerTool("execute_swap", {
238
+ title: "Execute swap",
239
+ description: "Execute a swap via Swap Proxy: fresh estimate → create → local sign → execute. Does NOT use the transfer relay. Returns operationId and txHash; poll with get_swap_status.",
240
+ inputSchema: {
241
+ wallet: z.string().optional(),
242
+ from: assetInputSchema,
243
+ to: assetInputSchema,
244
+ amount: z
245
+ .string()
246
+ .optional()
247
+ .describe("Sell amount as decimal string. Required unless maxMode."),
248
+ maxMode: z.boolean().optional(),
249
+ },
250
+ }, async ({ wallet, from, to, amount, maxMode }) => withToolLog("execute_swap", { wallet, from, to, amount, maxMode }, async ({ correlationId, started }) => {
251
+ const prep = await prepareSwapSides(helpers, {
252
+ wallet,
253
+ from,
254
+ to,
255
+ amount,
256
+ maxMode,
257
+ correlationId,
258
+ });
259
+ enforcePolicy(prep.entry, {
260
+ kind: "swap",
261
+ network: prep.sell.networkId,
262
+ });
263
+ logInfo("tool.execute_swap.estimate", {
264
+ correlationId,
265
+ from: assetKey(prep.fromSide.asset),
266
+ to: assetKey(prep.toSide.asset),
267
+ amount: prep.sellAmount,
268
+ maxMode: prep.useMax,
269
+ });
270
+ const est = await estimateSwap(prep.token, {
271
+ from: prep.fromSide,
272
+ to: prep.toSide,
273
+ maxMode: prep.useMax,
274
+ }, { correlationId });
275
+ if (!est.operationId) {
276
+ throw new Error("Swap estimate returned no operationId.");
277
+ }
278
+ // Mobile: from.amount = requested sellAmount; correctedAmountFrom separate.
279
+ const corrected = est.correctedAmountFrom ?? est.from.amount ?? prep.sellAmount;
280
+ enforcePolicy(prep.entry, {
281
+ kind: "swap",
282
+ network: prep.sell.networkId,
283
+ amount: corrected,
284
+ });
285
+ const createFrom = {
286
+ ...prep.fromSide,
287
+ amount: prep.sellAmount,
288
+ };
289
+ const created = await createSwap(prep.token, {
290
+ operationId: est.operationId,
291
+ correctedAmountFrom: corrected,
292
+ from: createFrom,
293
+ to: prep.toSide,
294
+ maxMode: prep.useMax,
295
+ }, { correlationId });
296
+ if (created.txsToSign.length === 0) {
297
+ throw new Error("Swap create returned no transactions to sign.");
298
+ }
299
+ if (!created.swapOrderId) {
300
+ throw new Error("Swap create returned no swapOrderId (order.uid). Cannot execute.");
301
+ }
302
+ const signed = await signSwapTransactions(prep.sell.networkId, prep.mnemonic, created.txsToSign);
303
+ if (signed.length === 0) {
304
+ throw new Error("No transactions were signed for this swap.");
305
+ }
306
+ const executed = await executeSwap(prep.token, {
307
+ operationId: created.operationId,
308
+ swapOrderId: created.swapOrderId,
309
+ from: createFrom,
310
+ to: prep.toSide,
311
+ signedTransactions: signed,
312
+ }, { correlationId });
313
+ const preferred = signed.find((t) => t.type === "MainTransfer" || t.type === "ExternalContractCall") ?? signed[0];
314
+ return ok({
315
+ correlationId,
316
+ operationId: executed.operationId,
317
+ swapOrderId: created.swapOrderId,
318
+ txHash: executed.txHash ?? preferred?.txHash,
319
+ amountFrom: corrected,
320
+ amountTo: est.to.amount,
321
+ provider: created.provider ?? est.provider,
322
+ fromNetwork: prep.sell.networkId,
323
+ toNetwork: prep.buy.networkId,
324
+ elapsedMs: Date.now() - started,
325
+ note: "Poll get_swap_status with operationId until complete. Do not re-execute blindly after a timeout.",
326
+ });
327
+ }));
328
+ server.registerTool("get_swap_status", {
329
+ title: "Get swap status",
330
+ description: "Poll Swap Proxy order/operation status by operationId.",
331
+ inputSchema: {
332
+ wallet: z.string().optional(),
333
+ operationId: z.string(),
334
+ },
335
+ }, async ({ wallet, operationId }) => withToolLog("get_swap_status", { wallet, operationId }, async ({ correlationId }) => {
336
+ const { token } = await session(wallet, correlationId);
337
+ const status = await getSwapStatus(token, operationId, {
338
+ correlationId,
339
+ });
340
+ return ok({ correlationId, ...status });
341
+ }));
342
+ }
343
+ //# sourceMappingURL=swap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"swap.js","sourceRoot":"","sources":["../../src/tools/swap.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * MCP tools for balance reads and transfer relay (estimate → sign → forward).
3
+ */
4
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ import { type ToolHelpers } from "./helpers.js";
6
+ export declare function registerTransferTools(server: McpServer, helpers: ToolHelpers): void;
@@ -0,0 +1,284 @@
1
+ /**
2
+ * MCP tools for balance reads and transfer relay (estimate → sign → forward).
3
+ */
4
+ import { z } from "zod";
5
+ import { canSend } from "../config.js";
6
+ import { resolveEntry } from "../keystore/store.js";
7
+ import { estimate, forward, operationState } from "../api/relay.js";
8
+ import { getBalance } from "../api/balances.js";
9
+ import { parseUnits, formatUnits } from "ethers";
10
+ import { signForward } from "../signing/index.js";
11
+ import { tonPublicKeyHex } from "../signing/ton.js";
12
+ import { enforcePolicy } from "../policy.js";
13
+ import { logInfo, logWarn } from "../log.js";
14
+ import { networkEnum } from "./helpers.js";
15
+ /**
16
+ * Estimate a transfer and, if the amount + fee would exceed the balance, reduce
17
+ * the amount to `balance - fee` and re-estimate. Fees for native coins are paid
18
+ * in the coin (`coinFee`); for tokens the gasless fee is paid in the token
19
+ * (`tokenFee`). The returned `est` always matches the returned `amount`.
20
+ */
21
+ async function resolveAmountForFee(opts) {
22
+ const { network, token, address, to, tokenAddress, memo, fromPublicKey, correlationId, } = opts;
23
+ const bal = await getBalance(network, address, tokenAddress);
24
+ const decimals = bal.decimals;
25
+ const balanceRaw = BigInt(bal.raw);
26
+ logInfo("send.resolve_amount.start", {
27
+ correlationId,
28
+ network,
29
+ address,
30
+ to,
31
+ requestedAmount: opts.amount,
32
+ balance: bal.formatted,
33
+ balanceRaw: bal.raw,
34
+ symbol: bal.symbol,
35
+ decimals,
36
+ tokenAddress,
37
+ hasFromPublicKey: Boolean(fromPublicKey),
38
+ });
39
+ let amount = opts.amount;
40
+ let adjusted = false;
41
+ let est = await estimate(network, token, {
42
+ fromAddress: address,
43
+ toAddress: to,
44
+ tokenAddress,
45
+ amount,
46
+ memo,
47
+ fromPublicKey,
48
+ }, { correlationId });
49
+ const feeOf = (e) => BigInt((tokenAddress ? e.estimateResult?.tokenFee : e.estimateResult?.coinFee) ??
50
+ "0");
51
+ const amountRawOf = (e, amt) => {
52
+ const fromEst = e.estimateResult?.amount;
53
+ if (fromEst != null && /^\d+$/.test(fromEst))
54
+ return BigInt(fromEst);
55
+ return parseUnits(amt, decimals);
56
+ };
57
+ // A few passes in case the fee shifts slightly when the amount changes.
58
+ for (let i = 0; i < 4; i++) {
59
+ const feeRaw = feeOf(est);
60
+ const requestedRaw = amountRawOf(est, amount);
61
+ logInfo("send.resolve_amount.pass", {
62
+ correlationId,
63
+ pass: i,
64
+ amount,
65
+ amountRaw: requestedRaw.toString(),
66
+ feeRaw: feeRaw.toString(),
67
+ fee: formatUnits(feeRaw, decimals),
68
+ balanceRaw: balanceRaw.toString(),
69
+ fits: requestedRaw + feeRaw <= balanceRaw,
70
+ adjusted,
71
+ estimateTraceId: est.traceId,
72
+ });
73
+ if (requestedRaw + feeRaw <= balanceRaw) {
74
+ logInfo("send.resolve_amount.ok", {
75
+ correlationId,
76
+ amount,
77
+ adjusted,
78
+ requestedAmount: opts.amount,
79
+ fee: formatUnits(feeRaw, decimals),
80
+ symbol: bal.symbol,
81
+ });
82
+ return {
83
+ est,
84
+ amount,
85
+ adjusted,
86
+ requestedAmount: opts.amount,
87
+ balance: bal.formatted,
88
+ fee: formatUnits(feeRaw, decimals),
89
+ symbol: bal.symbol,
90
+ };
91
+ }
92
+ const correctedRaw = balanceRaw - feeRaw;
93
+ if (correctedRaw <= 0n) {
94
+ throw new Error(`Balance ${bal.formatted} ${bal.symbol} is too low to cover the fee ` +
95
+ `(${formatUnits(feeRaw, decimals)} ${bal.symbol}).`);
96
+ }
97
+ const prev = amount;
98
+ amount = formatUnits(correctedRaw, decimals);
99
+ adjusted = true;
100
+ logWarn("send.resolve_amount.adjust", {
101
+ correlationId,
102
+ pass: i,
103
+ fromAmount: prev,
104
+ toAmount: amount,
105
+ fee: formatUnits(feeRaw, decimals),
106
+ balance: bal.formatted,
107
+ symbol: bal.symbol,
108
+ });
109
+ est = await estimate(network, token, {
110
+ fromAddress: address,
111
+ toAddress: to,
112
+ tokenAddress,
113
+ amount,
114
+ memo,
115
+ fromPublicKey,
116
+ }, { correlationId });
117
+ }
118
+ throw new Error("Could not fit the amount under balance minus fee (fee kept changing). Try a smaller amount.");
119
+ }
120
+ export function registerTransferTools(server, helpers) {
121
+ const { ok, withToolLog, session } = helpers;
122
+ server.registerTool("get_balance", {
123
+ title: "Get balance",
124
+ description: "Get the balance for a wallet on a network. Optionally pass a token contract address; omit for the native coin.",
125
+ inputSchema: {
126
+ wallet: z.string().optional().describe("Wallet name. Optional if only one exists."),
127
+ network: networkEnum,
128
+ tokenAddress: z
129
+ .string()
130
+ .optional()
131
+ .describe("Token contract address. Omit for the native coin."),
132
+ },
133
+ }, async ({ wallet, network, tokenAddress }) => withToolLog("get_balance", { wallet, network, tokenAddress }, async ({ correlationId }) => {
134
+ const entry = resolveEntry(wallet);
135
+ const address = entry.addresses[network];
136
+ if (!address) {
137
+ throw new Error(`Wallet "${entry.name}" has no ${network} address.`);
138
+ }
139
+ const result = await getBalance(network, address, tokenAddress);
140
+ return ok({ ...result, correlationId });
141
+ }));
142
+ server.registerTool("estimate_transfer", {
143
+ title: "Estimate transfer",
144
+ description: "Estimate fees for a transfer without sending. Returns fees and the number of transactions that would be signed.",
145
+ inputSchema: {
146
+ wallet: z.string().optional(),
147
+ network: networkEnum,
148
+ to: z.string().describe("Recipient address."),
149
+ amount: z.string().describe("Amount in asset units (decimal string)."),
150
+ tokenAddress: z
151
+ .string()
152
+ .optional()
153
+ .describe("Token contract address. Omit for the native coin."),
154
+ memo: z.string().optional(),
155
+ },
156
+ }, async ({ wallet, network, to, amount, tokenAddress, memo }) => withToolLog("estimate_transfer", { wallet, network, to, amount, tokenAddress, hasMemo: Boolean(memo) }, async ({ correlationId }) => {
157
+ if (!canSend(network)) {
158
+ throw new Error(`Sending on "${network}" is not supported.`);
159
+ }
160
+ const { entry, mnemonic, token } = await session(wallet, correlationId);
161
+ const address = entry.addresses[network];
162
+ const fromPublicKey = network === "ton" ? tonPublicKeyHex(mnemonic) : undefined;
163
+ const est = await estimate(network, token, {
164
+ fromAddress: address,
165
+ toAddress: to,
166
+ tokenAddress,
167
+ amount,
168
+ memo,
169
+ fromPublicKey,
170
+ }, { correlationId });
171
+ return ok({
172
+ amount: est.estimateResult?.amount,
173
+ coinFee: est.estimateResult?.coinFee,
174
+ tokenFee: est.estimateResult?.tokenFee,
175
+ transactionsToSign: est.estimateResult?.transactionsToSign?.length ?? 0,
176
+ traceId: est.traceId,
177
+ correlationId,
178
+ });
179
+ }));
180
+ server.registerTool("send_transfer", {
181
+ title: "Send transfer",
182
+ description: "Send a transfer: sign locally with the wallet's key and broadcast via the Ironwallet forward relay (estimate -> sign -> forward) for EVM, Tron, Bitcoin, Litecoin, Doge, Solana, XRP and TON. Returns the transaction hash (and operation id where applicable).",
183
+ inputSchema: {
184
+ wallet: z.string().optional(),
185
+ network: networkEnum,
186
+ to: z.string().describe("Recipient address."),
187
+ amount: z.string().describe("Amount in asset units (decimal string)."),
188
+ tokenAddress: z
189
+ .string()
190
+ .optional()
191
+ .describe("Token contract address. Omit for the native coin."),
192
+ memo: z.string().optional(),
193
+ },
194
+ }, async ({ wallet, network, to, amount, tokenAddress, memo }) => withToolLog("send_transfer", { wallet, network, to, amount, tokenAddress, hasMemo: Boolean(memo) }, async ({ correlationId, started }) => {
195
+ if (!canSend(network)) {
196
+ throw new Error(`Sending on "${network}" is not supported.`);
197
+ }
198
+ const { entry, mnemonic, token } = await session(wallet, correlationId);
199
+ const address = entry.addresses[network];
200
+ logInfo("tool.send_transfer.session", {
201
+ correlationId,
202
+ wallet: entry.name,
203
+ fromAddress: address,
204
+ network,
205
+ });
206
+ const fromPublicKey = network === "ton" ? tonPublicKeyHex(mnemonic) : undefined;
207
+ const resolved = await resolveAmountForFee({
208
+ network,
209
+ token,
210
+ address,
211
+ to,
212
+ tokenAddress,
213
+ amount,
214
+ memo,
215
+ fromPublicKey,
216
+ correlationId,
217
+ });
218
+ enforcePolicy(entry, {
219
+ kind: "transfer",
220
+ network,
221
+ toAddress: to,
222
+ amount: resolved.amount,
223
+ });
224
+ const params = {
225
+ fromAddress: address,
226
+ toAddress: to,
227
+ tokenAddress,
228
+ amount: resolved.amount,
229
+ memo,
230
+ fromPublicKey,
231
+ };
232
+ const txs = resolved.est.estimateResult?.transactionsToSign ?? [];
233
+ if (txs.length === 0) {
234
+ throw new Error("Estimate returned no transactions to sign.");
235
+ }
236
+ logInfo("tool.send_transfer.sign.start", {
237
+ correlationId,
238
+ network,
239
+ txCount: txs.length,
240
+ amount: resolved.amount,
241
+ adjustedForFee: resolved.adjusted,
242
+ });
243
+ const signStarted = Date.now();
244
+ const signed = await signForward(network, mnemonic, txs);
245
+ logInfo("tool.send_transfer.sign.ok", {
246
+ correlationId,
247
+ network,
248
+ elapsedMs: Date.now() - signStarted,
249
+ signedCount: signed.length,
250
+ });
251
+ const result = await forward(network, token, params, resolved.est, signed, { correlationId });
252
+ return ok({
253
+ txHash: result.txHash,
254
+ operationId: result.operationId,
255
+ chainId: result.chainId,
256
+ amountSent: resolved.amount,
257
+ adjustedForFee: resolved.adjusted,
258
+ requestedAmount: resolved.requestedAmount,
259
+ correlationId,
260
+ elapsedMs: Date.now() - started,
261
+ ...(resolved.adjusted
262
+ ? {
263
+ note: `Requested ${resolved.requestedAmount} exceeded balance minus fee; reduced to ${resolved.amount} ${resolved.symbol} (fee ${resolved.fee} ${resolved.symbol}).`,
264
+ }
265
+ : {}),
266
+ });
267
+ }));
268
+ server.registerTool("get_operation_status", {
269
+ title: "Get operation status",
270
+ description: "Poll the state of a forward operation by its operation id.",
271
+ inputSchema: {
272
+ wallet: z.string().optional(),
273
+ network: networkEnum,
274
+ operationId: z.string(),
275
+ },
276
+ }, async ({ wallet, network, operationId }) => withToolLog("get_operation_status", { wallet, network, operationId }, async ({ correlationId }) => {
277
+ const { token } = await session(wallet, correlationId);
278
+ const state = await operationState(network, token, operationId, {
279
+ correlationId,
280
+ });
281
+ return ok({ ...state, correlationId });
282
+ }));
283
+ }
284
+ //# sourceMappingURL=transfer.js.map