stelagent 0.0.3 → 0.0.5

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 (2) hide show
  1. package/dist/index.mjs +249 -45
  2. package/package.json +1 -1
package/dist/index.mjs CHANGED
@@ -201,7 +201,7 @@ function formatError(e) {
201
201
  if (e instanceof Error) return e.message;
202
202
  return String(e);
203
203
  }
204
- async function runApp(command, fn, format = "json") {
204
+ async function runApp(command, fn, format = "json", options) {
205
205
  const start = Date.now();
206
206
  try {
207
207
  const result = await fn();
@@ -210,6 +210,7 @@ async function runApp(command, fn, format = "json") {
210
210
  ok: true,
211
211
  durationMs: Date.now() - start
212
212
  });
213
+ if (!options?.silent) printResult(Result.ok(result), format);
213
214
  return result;
214
215
  } catch (e) {
215
216
  writeAuditEntry({
@@ -323,7 +324,7 @@ z.enum(["asc", "desc"]).default("desc");
323
324
  z.coerce.number().int().min(6e4);
324
325
  //#endregion
325
326
  //#region src/commands/wallet-login.ts
326
- function formatZodError$11(e) {
327
+ function formatZodError$12(e) {
327
328
  return e.issues.map((issue) => issue.message).join(", ");
328
329
  }
329
330
  const walletLogin = defineCommand({
@@ -354,7 +355,7 @@ const walletLogin = defineCommand({
354
355
  catch: (e) => e
355
356
  });
356
357
  if (Result.isError(validation)) {
357
- const msg = validation.error instanceof z.ZodError ? formatZodError$11(validation.error) : validation.error instanceof Error ? validation.error.message : String(validation.error);
358
+ const msg = validation.error instanceof z.ZodError ? formatZodError$12(validation.error) : validation.error instanceof Error ? validation.error.message : String(validation.error);
358
359
  printResult(Result.err(msg), format);
359
360
  return;
360
361
  }
@@ -533,7 +534,7 @@ const walletAddress = defineCommand({
533
534
  });
534
535
  //#endregion
535
536
  //#region src/services/stellar.ts
536
- const HORIZON_URLS$1 = {
537
+ const HORIZON_URLS$2 = {
537
538
  testnet: process.env.STELAGENT_HORIZON_TESTNET_URL ?? "https://horizon-testnet.stellar.org",
538
539
  pubnet: process.env.STELAGENT_HORIZON_PUBNET_URL ?? "https://horizon-mainnet.stellar.org"
539
540
  };
@@ -573,7 +574,7 @@ async function getBalances(publicKey, network) {
573
574
  return Result.tryPromise({
574
575
  try: async () => {
575
576
  const { Horizon } = await import("@stellar/stellar-sdk");
576
- return (await new Horizon.Server(HORIZON_URLS$1[network]).loadAccount(publicKey)).balances.map((b) => ({
577
+ return (await new Horizon.Server(HORIZON_URLS$2[network]).loadAccount(publicKey)).balances.map((b) => ({
577
578
  assetType: b.asset_type,
578
579
  assetCode: b.asset_type === "native" ? "XLM" : "asset_code" in b && typeof b.asset_code === "string" ? b.asset_code : "",
579
580
  balance: b.balance
@@ -586,7 +587,7 @@ async function transferXlm(sourceSecret, destination, amount, network) {
586
587
  return Result.tryPromise({
587
588
  try: async () => {
588
589
  const { Keypair, Asset, Operation, TransactionBuilder, Horizon } = await import("@stellar/stellar-sdk");
589
- const server = new Horizon.Server(HORIZON_URLS$1[network]);
590
+ const server = new Horizon.Server(HORIZON_URLS$2[network]);
590
591
  const sourceKp = Keypair.fromSecret(sourceSecret);
591
592
  let sourceAccount;
592
593
  try {
@@ -623,7 +624,7 @@ async function sendPayment(sourceSecret, destination, amount, assetStr, network,
623
624
  return Result.tryPromise({
624
625
  try: async () => {
625
626
  const { Keypair, Operation, TransactionBuilder, Horizon, Memo } = await import("@stellar/stellar-sdk");
626
- const server = new Horizon.Server(HORIZON_URLS$1[network]);
627
+ const server = new Horizon.Server(HORIZON_URLS$2[network]);
627
628
  const sourceKp = Keypair.fromSecret(sourceSecret);
628
629
  let sourceAccount;
629
630
  try {
@@ -685,7 +686,7 @@ const walletBalance = defineCommand({
685
686
  });
686
687
  //#endregion
687
688
  //#region src/commands/wallet-transfer.ts
688
- function formatZodError$10(e) {
689
+ function formatZodError$11(e) {
689
690
  return e.issues.map((issue) => issue.message).join(", ");
690
691
  }
691
692
  //#endregion
@@ -732,7 +733,7 @@ const walletCommand = defineCommand({
732
733
  catch: (e) => e
733
734
  });
734
735
  if (Result.isError(validation)) {
735
- const msg = validation.error instanceof z.ZodError ? formatZodError$10(validation.error) : validation.error instanceof Error ? validation.error.message : String(validation.error);
736
+ const msg = validation.error instanceof z.ZodError ? formatZodError$11(validation.error) : validation.error instanceof Error ? validation.error.message : String(validation.error);
736
737
  printResult(Result.err(msg), format);
737
738
  return;
738
739
  }
@@ -842,7 +843,7 @@ async function pay(url) {
842
843
  }
843
844
  //#endregion
844
845
  //#region src/commands/pay.ts
845
- function formatZodError$9(e) {
846
+ function formatZodError$10(e) {
846
847
  return e.issues.map((issue) => issue.message).join(", ");
847
848
  }
848
849
  const payCommand = defineCommand({
@@ -866,7 +867,7 @@ const payCommand = defineCommand({
866
867
  catch: (e) => e
867
868
  });
868
869
  if (Result.isError(validation)) {
869
- const msg = validation.error instanceof z.ZodError ? formatZodError$9(validation.error) : validation.error instanceof Error ? validation.error.message : String(validation.error);
870
+ const msg = validation.error instanceof z.ZodError ? formatZodError$10(validation.error) : validation.error instanceof Error ? validation.error.message : String(validation.error);
870
871
  printResult(Result.err(msg), format);
871
872
  return;
872
873
  }
@@ -879,12 +880,12 @@ const payCommand = defineCommand({
879
880
  });
880
881
  //#endregion
881
882
  //#region src/services/horizon.ts
882
- const HORIZON_URLS = {
883
+ const HORIZON_URLS$1 = {
883
884
  testnet: process.env.STELAGENT_HORIZON_TESTNET_URL ?? "https://horizon-testnet.stellar.org",
884
885
  pubnet: process.env.STELAGENT_HORIZON_PUBNET_URL ?? "https://horizon-mainnet.stellar.org"
885
886
  };
886
887
  function horizonServer(network) {
887
- return new Horizon.Server(HORIZON_URLS[network]);
888
+ return new Horizon.Server(HORIZON_URLS$1[network]);
888
889
  }
889
890
  function classifyHorizonError(e, address) {
890
891
  const message = e instanceof Error ? e.message : String(e);
@@ -1113,7 +1114,7 @@ function streamEffects(publicKey, network, opts, onMessage, onError) {
1113
1114
  }
1114
1115
  //#endregion
1115
1116
  //#region src/commands/account-details.ts
1116
- function formatZodError$8(e) {
1117
+ function formatZodError$9(e) {
1117
1118
  return e.issues.map((issue) => issue.message).join(", ");
1118
1119
  }
1119
1120
  const accountDetails = defineCommand({
@@ -1144,7 +1145,7 @@ const accountDetails = defineCommand({
1144
1145
  catch: (e) => e
1145
1146
  });
1146
1147
  if (Result.isError(validation)) {
1147
- const msg = validation.error instanceof z.ZodError ? formatZodError$8(validation.error) : validation.error instanceof Error ? validation.error.message : String(validation.error);
1148
+ const msg = validation.error instanceof z.ZodError ? formatZodError$9(validation.error) : validation.error instanceof Error ? validation.error.message : String(validation.error);
1148
1149
  printResult(Result.err(msg), format);
1149
1150
  return;
1150
1151
  }
@@ -1157,7 +1158,7 @@ const accountDetails = defineCommand({
1157
1158
  });
1158
1159
  //#endregion
1159
1160
  //#region src/commands/account-transactions.ts
1160
- function formatZodError$7(e) {
1161
+ function formatZodError$8(e) {
1161
1162
  return e.issues.map((issue) => issue.message).join(", ");
1162
1163
  }
1163
1164
  const accountTransactions = defineCommand({
@@ -1205,7 +1206,7 @@ const accountTransactions = defineCommand({
1205
1206
  catch: (e) => e
1206
1207
  });
1207
1208
  if (Result.isError(validation)) {
1208
- const msg = validation.error instanceof z.ZodError ? formatZodError$7(validation.error) : validation.error instanceof Error ? validation.error.message : String(validation.error);
1209
+ const msg = validation.error instanceof z.ZodError ? formatZodError$8(validation.error) : validation.error instanceof Error ? validation.error.message : String(validation.error);
1209
1210
  printResult(Result.err(msg), format);
1210
1211
  return;
1211
1212
  }
@@ -1226,7 +1227,7 @@ const accountTransactions = defineCommand({
1226
1227
  });
1227
1228
  //#endregion
1228
1229
  //#region src/commands/account-payments.ts
1229
- function formatZodError$6(e) {
1230
+ function formatZodError$7(e) {
1230
1231
  return e.issues.map((issue) => issue.message).join(", ");
1231
1232
  }
1232
1233
  const accountPayments = defineCommand({
@@ -1274,7 +1275,7 @@ const accountPayments = defineCommand({
1274
1275
  catch: (e) => e
1275
1276
  });
1276
1277
  if (Result.isError(validation)) {
1277
- const msg = validation.error instanceof z.ZodError ? formatZodError$6(validation.error) : validation.error instanceof Error ? validation.error.message : String(validation.error);
1278
+ const msg = validation.error instanceof z.ZodError ? formatZodError$7(validation.error) : validation.error instanceof Error ? validation.error.message : String(validation.error);
1278
1279
  printResult(Result.err(msg), format);
1279
1280
  return;
1280
1281
  }
@@ -1295,7 +1296,7 @@ const accountPayments = defineCommand({
1295
1296
  });
1296
1297
  //#endregion
1297
1298
  //#region src/commands/account-effects.ts
1298
- function formatZodError$5(e) {
1299
+ function formatZodError$6(e) {
1299
1300
  return e.issues.map((issue) => issue.message).join(", ");
1300
1301
  }
1301
1302
  //#endregion
@@ -1348,7 +1349,7 @@ const accountCommand = defineCommand({
1348
1349
  catch: (e) => e
1349
1350
  });
1350
1351
  if (Result.isError(validation)) {
1351
- const msg = validation.error instanceof z.ZodError ? formatZodError$5(validation.error) : validation.error instanceof Error ? validation.error.message : String(validation.error);
1352
+ const msg = validation.error instanceof z.ZodError ? formatZodError$6(validation.error) : validation.error instanceof Error ? validation.error.message : String(validation.error);
1352
1353
  printResult(Result.err(msg), format);
1353
1354
  return;
1354
1355
  }
@@ -1370,7 +1371,7 @@ const accountCommand = defineCommand({
1370
1371
  });
1371
1372
  //#endregion
1372
1373
  //#region src/commands/assets-search.ts
1373
- function formatZodError$4(e) {
1374
+ function formatZodError$5(e) {
1374
1375
  return e.issues.map((issue) => issue.message).join(", ");
1375
1376
  }
1376
1377
  //#endregion
@@ -1419,7 +1420,7 @@ const assetsCommand = defineCommand({
1419
1420
  catch: (e) => e
1420
1421
  });
1421
1422
  if (Result.isError(validation)) {
1422
- const msg = validation.error instanceof z.ZodError ? formatZodError$4(validation.error) : validation.error instanceof Error ? validation.error.message : String(validation.error);
1423
+ const msg = validation.error instanceof z.ZodError ? formatZodError$5(validation.error) : validation.error instanceof Error ? validation.error.message : String(validation.error);
1423
1424
  printResult(Result.err(msg), format);
1424
1425
  return;
1425
1426
  }
@@ -1506,7 +1507,7 @@ const assetsCommand = defineCommand({
1506
1507
  });
1507
1508
  //#endregion
1508
1509
  //#region src/commands/send.ts
1509
- function formatZodError$3(e) {
1510
+ function formatZodError$4(e) {
1510
1511
  return e.issues.map((issue) => issue.message).join(", ");
1511
1512
  }
1512
1513
  const sendCommand = defineCommand({
@@ -1561,7 +1562,7 @@ const sendCommand = defineCommand({
1561
1562
  catch: (e) => e
1562
1563
  });
1563
1564
  if (Result.isError(validation)) {
1564
- const msg = validation.error instanceof z.ZodError ? formatZodError$3(validation.error) : validation.error instanceof Error ? validation.error.message : String(validation.error);
1565
+ const msg = validation.error instanceof z.ZodError ? formatZodError$4(validation.error) : validation.error instanceof Error ? validation.error.message : String(validation.error);
1565
1566
  printResult(Result.err(msg), format);
1566
1567
  return;
1567
1568
  }
@@ -1609,7 +1610,7 @@ const feeCommand = defineCommand({
1609
1610
  });
1610
1611
  //#endregion
1611
1612
  //#region src/commands/monitor-transactions.ts
1612
- function formatZodError$2(e) {
1613
+ function formatZodError$3(e) {
1613
1614
  return e.issues.map((issue) => issue.message).join(", ");
1614
1615
  }
1615
1616
  const monitorTransactions = defineCommand({
@@ -1646,7 +1647,7 @@ const monitorTransactions = defineCommand({
1646
1647
  catch: (e) => e
1647
1648
  });
1648
1649
  if (Result.isError(validation)) {
1649
- const msg = validation.error instanceof z.ZodError ? formatZodError$2(validation.error) : validation.error instanceof Error ? validation.error.message : String(validation.error);
1650
+ const msg = validation.error instanceof z.ZodError ? formatZodError$3(validation.error) : validation.error instanceof Error ? validation.error.message : String(validation.error);
1650
1651
  printResult(Result.err(msg), format);
1651
1652
  return;
1652
1653
  }
@@ -1666,7 +1667,7 @@ const monitorTransactions = defineCommand({
1666
1667
  error: `Stream error: ${message}`
1667
1668
  }));
1668
1669
  });
1669
- }, format);
1670
+ }, format, { silent: true });
1670
1671
  } catch (e) {
1671
1672
  const message = e instanceof Error ? e.message : String(e);
1672
1673
  printResult(Result.err(`Failed to stream transactions: ${message}`), "json");
@@ -1686,7 +1687,7 @@ const monitorTransactions = defineCommand({
1686
1687
  });
1687
1688
  //#endregion
1688
1689
  //#region src/commands/monitor-payments.ts
1689
- function formatZodError$1(e) {
1690
+ function formatZodError$2(e) {
1690
1691
  return e.issues.map((issue) => issue.message).join(", ");
1691
1692
  }
1692
1693
  const monitorPayments = defineCommand({
@@ -1723,7 +1724,7 @@ const monitorPayments = defineCommand({
1723
1724
  catch: (e) => e
1724
1725
  });
1725
1726
  if (Result.isError(validation)) {
1726
- const msg = validation.error instanceof z.ZodError ? formatZodError$1(validation.error) : validation.error instanceof Error ? validation.error.message : String(validation.error);
1727
+ const msg = validation.error instanceof z.ZodError ? formatZodError$2(validation.error) : validation.error instanceof Error ? validation.error.message : String(validation.error);
1727
1728
  printResult(Result.err(msg), format);
1728
1729
  return;
1729
1730
  }
@@ -1743,7 +1744,7 @@ const monitorPayments = defineCommand({
1743
1744
  error: `Stream error: ${message}`
1744
1745
  }));
1745
1746
  });
1746
- }, format);
1747
+ }, format, { silent: true });
1747
1748
  } catch (e) {
1748
1749
  const message = e instanceof Error ? e.message : String(e);
1749
1750
  printResult(Result.err(`Failed to stream payments: ${message}`), "json");
@@ -1763,7 +1764,7 @@ const monitorPayments = defineCommand({
1763
1764
  });
1764
1765
  //#endregion
1765
1766
  //#region src/commands/monitor-effects.ts
1766
- function formatZodError(e) {
1767
+ function formatZodError$1(e) {
1767
1768
  return e.issues.map((issue) => issue.message).join(", ");
1768
1769
  }
1769
1770
  //#endregion
@@ -1810,7 +1811,7 @@ const monitorCommand = defineCommand({
1810
1811
  catch: (e) => e
1811
1812
  });
1812
1813
  if (Result.isError(validation)) {
1813
- const msg = validation.error instanceof z.ZodError ? formatZodError(validation.error) : validation.error instanceof Error ? validation.error.message : String(validation.error);
1814
+ const msg = validation.error instanceof z.ZodError ? formatZodError$1(validation.error) : validation.error instanceof Error ? validation.error.message : String(validation.error);
1814
1815
  printResult(Result.err(msg), format);
1815
1816
  return;
1816
1817
  }
@@ -1830,7 +1831,7 @@ const monitorCommand = defineCommand({
1830
1831
  error: `Stream error: ${message}`
1831
1832
  }));
1832
1833
  });
1833
- }, format);
1834
+ }, format, { silent: true });
1834
1835
  } catch (e) {
1835
1836
  const message = e instanceof Error ? e.message : String(e);
1836
1837
  printResult(Result.err(`Failed to stream effects: ${message}`), "json");
@@ -2173,6 +2174,126 @@ function registerAssetTools(server) {
2173
2174
  });
2174
2175
  }
2175
2176
  //#endregion
2177
+ //#region src/services/preflight.ts
2178
+ const HORIZON_URLS = {
2179
+ testnet: process.env.STELAGENT_HORIZON_TESTNET_URL ?? "https://horizon-testnet.stellar.org",
2180
+ pubnet: process.env.STELAGENT_HORIZON_PUBNET_URL ?? "https://horizon-mainnet.stellar.org"
2181
+ };
2182
+ const BASE_RESERVE = "1";
2183
+ const LINE_RESERVE = "0.5";
2184
+ async function preflightSend(destination, amount, assetStr, network) {
2185
+ const walletResult = await fetchWallet();
2186
+ if (Result.isError(walletResult)) return Result.err(walletResult.error);
2187
+ const wallet = walletResult.value;
2188
+ const sender = wallet.publicKey;
2189
+ const server = new Horizon.Server(HORIZON_URLS[network]);
2190
+ let senderAccount;
2191
+ let senderFunded = true;
2192
+ let destinationFunded = true;
2193
+ let destinationFundingRequired = false;
2194
+ const warnings = [];
2195
+ try {
2196
+ senderAccount = await server.loadAccount(sender);
2197
+ } catch {
2198
+ senderFunded = false;
2199
+ const msg = wallet.network === "testnet" ? `Sender account ${sender.slice(0, 8)}... is not funded. Use wallet login to auto-fund on testnet.` : `Sender account ${sender.slice(0, 8)}... is not funded. Send at least 1 XLM to activate it.`;
2200
+ return Result.ok({
2201
+ canProceed: false,
2202
+ sender,
2203
+ destination,
2204
+ amount,
2205
+ asset: assetStr === "native" ? "XLM" : assetStr,
2206
+ network,
2207
+ senderFunded,
2208
+ destinationFunded: true,
2209
+ destinationFundingRequired: false,
2210
+ balances: [],
2211
+ estimatedFee: "0",
2212
+ baseReserve: BASE_RESERVE,
2213
+ warnings: [msg]
2214
+ });
2215
+ }
2216
+ let feeStatsResult;
2217
+ try {
2218
+ const stats = await server.feeStats();
2219
+ feeStatsResult = {
2220
+ feeCharged: { mode: stats.fee_charged.mode },
2221
+ maxFee: { mode: stats.max_fee.mode }
2222
+ };
2223
+ } catch {
2224
+ feeStatsResult = {
2225
+ feeCharged: { mode: "100" },
2226
+ maxFee: { mode: "100" }
2227
+ };
2228
+ }
2229
+ const estimatedFee = feeStatsResult.feeCharged.mode;
2230
+ const balances = [];
2231
+ let sufficientOverall = true;
2232
+ for (const b of senderAccount.balances) {
2233
+ const code = b.asset_type === "native" ? "XLM" : "asset_code" in b ? String(b.asset_code) : "";
2234
+ const available = Number(b.balance);
2235
+ if (b.asset_type === "native") {
2236
+ const subentryCount = senderAccount.subentry_count;
2237
+ const effectiveAvailable = available - (Number(BASE_RESERVE) + Number(LINE_RESERVE) * subentryCount);
2238
+ const needed = Number(amount) + Number(estimatedFee) / 1e7;
2239
+ const sufficient = effectiveAvailable >= needed;
2240
+ if (!sufficient) sufficientOverall = false;
2241
+ balances.push({
2242
+ asset: "XLM",
2243
+ available: effectiveAvailable.toFixed(7),
2244
+ needed: needed.toFixed(7),
2245
+ sufficient
2246
+ });
2247
+ } else if (assetStr !== "native" && code === assetStr.split(":")[0] || assetStr === code) {
2248
+ const needed = Number(amount);
2249
+ const sufficient = available >= needed;
2250
+ if (!sufficient) sufficientOverall = false;
2251
+ balances.push({
2252
+ asset: code,
2253
+ available: available.toFixed(7),
2254
+ needed: needed.toFixed(7),
2255
+ sufficient
2256
+ });
2257
+ }
2258
+ }
2259
+ try {
2260
+ await server.loadAccount(destination);
2261
+ } catch {
2262
+ destinationFunded = false;
2263
+ destinationFundingRequired = true;
2264
+ if (assetStr === "native") warnings.push(`Destination ${destination.slice(0, 8)}... is not funded. The transaction will create the account if amount >= 1 XLM.`);
2265
+ else {
2266
+ warnings.push(`Destination ${destination.slice(0, 8)}... is not funded. Cannot send custom assets to an unfunded account. The destination must first receive XLM.`);
2267
+ sufficientOverall = false;
2268
+ }
2269
+ }
2270
+ if (assetStr === "native") {
2271
+ const totalNeeded = Number(amount) + Number(estimatedFee) / 1e7;
2272
+ const xlmBalance = senderAccount.balances.find((b) => b.asset_type === "native");
2273
+ if (xlmBalance) {
2274
+ const subentryCount = senderAccount.subentry_count;
2275
+ const minReserve = Number(BASE_RESERVE) + Number(LINE_RESERVE) * subentryCount;
2276
+ if (Number(xlmBalance.balance) - totalNeeded - minReserve < 0) warnings.push(`After sending ${amount} XLM + fees, remaining balance would fall below minimum reserve (${minReserve} XLM).`);
2277
+ }
2278
+ }
2279
+ const canProceed = senderFunded && sufficientOverall;
2280
+ return Result.ok({
2281
+ canProceed,
2282
+ sender,
2283
+ destination,
2284
+ amount,
2285
+ asset: assetStr === "native" ? "XLM" : assetStr,
2286
+ network,
2287
+ senderFunded,
2288
+ destinationFunded,
2289
+ destinationFundingRequired,
2290
+ balances,
2291
+ estimatedFee,
2292
+ baseReserve: BASE_RESERVE,
2293
+ warnings
2294
+ });
2295
+ }
2296
+ //#endregion
2176
2297
  //#region src/mcp/payment-tools.ts
2177
2298
  function ok(data) {
2178
2299
  return { content: [{
@@ -2230,6 +2351,25 @@ function registerPaymentTools(server) {
2230
2351
  if (Result.isError(result)) return err(formatPaymentError(result.error));
2231
2352
  return ok(result.value);
2232
2353
  });
2354
+ server.registerTool("preflight_check", {
2355
+ description: "Validate a payment before sending. Checks if the sender has sufficient balance, estimates fees, and verifies the destination account. Returns whether the transaction can proceed, along with detailed balance info and warnings.",
2356
+ inputSchema: {
2357
+ destination: z.string().describe("Destination public key (G...)"),
2358
+ amount: z.string().describe("Amount to validate (up to 7 decimal places)"),
2359
+ asset: z.string().default("native").describe("Asset: 'native' for XLM, or 'CODE:ISSUER' for custom assets"),
2360
+ network: z.enum(["testnet", "pubnet"]).default("testnet").describe("Stellar network")
2361
+ }
2362
+ }, async ({ destination, amount, asset, network }) => {
2363
+ const destValidation = stellarPublicKey.safeParse(destination);
2364
+ if (!destValidation.success) return err(`Invalid destination: ${destValidation.error.issues.map((i) => i.message).join(", ")}`);
2365
+ const amountValidation = amountSchema.safeParse(amount);
2366
+ if (!amountValidation.success) return err(`Invalid amount: ${amountValidation.error.issues.map((i) => i.message).join(", ")}`);
2367
+ const assetValidation = assetSchema.safeParse(asset);
2368
+ if (!assetValidation.success) return err(`Invalid asset: ${assetValidation.error.issues.map((i) => i.message).join(", ")}`);
2369
+ const result = await preflightSend(destination, amount, asset, network === "pubnet" ? "pubnet" : "testnet");
2370
+ if (Result.isError(result)) return err(formatWalletError(result.error));
2371
+ return ok(result.value);
2372
+ });
2233
2373
  }
2234
2374
  //#endregion
2235
2375
  //#region src/mcp/mod.ts
@@ -2252,6 +2392,29 @@ async function startMcpServer() {
2252
2392
  await server.connect(transport);
2253
2393
  }
2254
2394
  //#endregion
2395
+ //#region src/commands/mcp.ts
2396
+ const mcpCommand = defineCommand({
2397
+ meta: {
2398
+ name: "mcp",
2399
+ description: "Start the MCP server on stdio"
2400
+ },
2401
+ args: {},
2402
+ async run() {
2403
+ try {
2404
+ await startMcpServer();
2405
+ } catch (e) {
2406
+ const message = e instanceof Error ? e.message : String(e);
2407
+ console.error(`Failed to start MCP server: ${message}`);
2408
+ process.exit(1);
2409
+ }
2410
+ }
2411
+ });
2412
+ //#endregion
2413
+ //#region src/commands/preflight.ts
2414
+ function formatZodError(e) {
2415
+ return e.issues.map((issue) => issue.message).join(", ");
2416
+ }
2417
+ //#endregion
2255
2418
  //#region src/index.ts
2256
2419
  runMain(defineCommand({
2257
2420
  meta: {
@@ -2267,20 +2430,61 @@ runMain(defineCommand({
2267
2430
  send: sendCommand,
2268
2431
  fee: feeCommand,
2269
2432
  monitor: monitorCommand,
2270
- mcp: defineCommand({
2433
+ mcp: mcpCommand,
2434
+ preflight: defineCommand({
2271
2435
  meta: {
2272
- name: "mcp",
2273
- description: "Start the MCP server on stdio"
2436
+ name: "preflight",
2437
+ description: "Validate a payment before sending: check balances, fees, and destination"
2274
2438
  },
2275
- args: {},
2276
- async run() {
2277
- try {
2278
- await startMcpServer();
2279
- } catch (e) {
2280
- const message = e instanceof Error ? e.message : String(e);
2281
- console.error(`Failed to start MCP server: ${message}`);
2282
- process.exit(1);
2439
+ args: {
2440
+ destination: {
2441
+ type: "positional",
2442
+ description: "Destination public key (G...)",
2443
+ required: true
2444
+ },
2445
+ amount: {
2446
+ type: "string",
2447
+ alias: ["a"],
2448
+ description: "Amount to validate",
2449
+ required: true
2450
+ },
2451
+ asset: {
2452
+ type: "string",
2453
+ alias: ["s"],
2454
+ description: "Asset: 'native' (default) or 'CODE:ISSUER'",
2455
+ default: "native"
2456
+ },
2457
+ network: networkArg,
2458
+ format: formatArg
2459
+ },
2460
+ async run({ args }) {
2461
+ const networkResult = parseNetwork$4(String(args.network ?? "testnet"));
2462
+ const format = parseFormat(String(args.format ?? "json"));
2463
+ if (Result.isError(networkResult)) {
2464
+ printResult(Result.err(networkResult.error._tag), "json");
2465
+ return;
2466
+ }
2467
+ const destination = String(args.destination ?? "");
2468
+ const amount = String(args.amount ?? "");
2469
+ const asset = String(args.asset ?? "native");
2470
+ const validation = Result.try({
2471
+ try: () => {
2472
+ stellarPublicKey.parse(destination);
2473
+ amountSchema.parse(amount);
2474
+ assetSchema.parse(asset);
2475
+ },
2476
+ catch: (e) => e
2477
+ });
2478
+ if (Result.isError(validation)) {
2479
+ const msg = validation.error instanceof z.ZodError ? formatZodError(validation.error) : validation.error instanceof Error ? validation.error.message : String(validation.error);
2480
+ printResult(Result.err(msg), format);
2481
+ return;
2283
2482
  }
2483
+ await runCommand(async () => {
2484
+ const result = await preflightSend(destination, amount, asset, networkResult.value);
2485
+ if (Result.isError(result)) return Result.err(formatWalletError(result.error));
2486
+ return Result.ok(result.value);
2487
+ }, format);
2284
2488
  }
2285
2489
  })
2286
2490
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stelagent",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "description": "Modular, agent-first CLI for Stellar — wallet, payments, markets, and DeFi",
5
5
  "license": "MIT",
6
6
  "bin": {