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.
- package/dist/index.mjs +249 -45
- 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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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:
|
|
2433
|
+
mcp: mcpCommand,
|
|
2434
|
+
preflight: defineCommand({
|
|
2271
2435
|
meta: {
|
|
2272
|
-
name: "
|
|
2273
|
-
description: "
|
|
2436
|
+
name: "preflight",
|
|
2437
|
+
description: "Validate a payment before sending: check balances, fees, and destination"
|
|
2274
2438
|
},
|
|
2275
|
-
args: {
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
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
|
}
|