flash-trade-mcp 0.3.5 → 0.4.1

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.js +263 -29
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -49236,7 +49236,7 @@ class FlashApiClient {
49236
49236
  // src/tools/health.ts
49237
49237
  function registerHealthTools(server, client) {
49238
49238
  server.registerTool("health_check", {
49239
- description: "Check if the Flash Trade API is running and responsive. Returns account counts and service status. Call this first to verify connectivity before using other tools."
49239
+ description: "Verify Flash Trade API connectivity and status. Call this first before any other tool. Returns service status and account counts."
49240
49240
  }, async () => {
49241
49241
  const health = await client.getHealth();
49242
49242
  const lines = [`Status: ${health.status}`];
@@ -49250,7 +49250,7 @@ function registerHealthTools(server, client) {
49250
49250
  });
49251
49251
  }
49252
49252
 
49253
- // src/tools/markets.ts
49253
+ // src/tools/shared/custody-map.ts
49254
49254
  var VIRTUAL_CUSTODY_MAP = {
49255
49255
  "6bthDsp8pcGBGKVKCKZjV5JfuSUNRo62RG4hQHj1u4CK": { symbol: "BNB", pool: "Crypto.1", maxLeverage: "60.00" },
49256
49256
  A8SKWb3pwbFUtxLQhnpUTfy7CkxBpWGvTLYyJyWHCMWv: { symbol: "PYTH", pool: "Governance.1", maxLeverage: "60.00" },
@@ -49270,6 +49270,30 @@ var VIRTUAL_CUSTODY_MAP = {
49270
49270
  RQNURQjDbq2Yah2udtFTNT7TjR15vsPV3oJNnwYher8: { symbol: "TSLA", pool: "Equity.1", maxLeverage: "12.00" },
49271
49271
  ArnD1faZVVkkewX4HUSoDuht46egAtVvhDTFMJn3DkFo: { symbol: "SAMO", pool: "Community.3", maxLeverage: "50.00" }
49272
49272
  };
49273
+ function formatPriceUsd(data) {
49274
+ const price = parseFloat(data.price);
49275
+ const exp = parseFloat(data.exponent);
49276
+ if (isNaN(price) || isNaN(exp))
49277
+ return "?";
49278
+ const usd = price * Math.pow(10, exp);
49279
+ if (usd === 0)
49280
+ return "0.00";
49281
+ if (usd < 0.01)
49282
+ return usd.toPrecision(4);
49283
+ return usd.toFixed(2);
49284
+ }
49285
+ function formatCompactUsd(valueStr) {
49286
+ if (!valueStr)
49287
+ return "$?";
49288
+ const num = parseFloat(valueStr);
49289
+ if (isNaN(num))
49290
+ return `$${valueStr}`;
49291
+ if (num >= 1e6)
49292
+ return `$${(num / 1e6).toFixed(2)}M`;
49293
+ if (num >= 1000)
49294
+ return `$${(num / 1000).toFixed(1)}K`;
49295
+ return `$${num.toFixed(2)}`;
49296
+ }
49273
49297
  function buildCustodySymbolMap(poolData) {
49274
49298
  const map3 = new Map;
49275
49299
  for (const pool of poolData.pools) {
@@ -49288,6 +49312,8 @@ function buildCustodySymbolMap(poolData) {
49288
49312
  }
49289
49313
  return map3;
49290
49314
  }
49315
+
49316
+ // src/tools/markets.ts
49291
49317
  function formatMarketsSummary(markets, custodyInfo) {
49292
49318
  const lines = [
49293
49319
  `${markets.length} markets available:
@@ -49317,7 +49343,7 @@ Use get_market with a pubkey for full details. Use get_prices for current oracle
49317
49343
  }
49318
49344
  function registerMarketTools(server, client) {
49319
49345
  server.registerTool("get_markets", {
49320
- description: "List all available perpetual futures markets on Flash Trade. Returns a compact summary table with symbol, side (Long/Short), pool, max leverage, and market pubkey. For full details on a specific market, use get_market with the pubkey."
49346
+ description: "List all available perpetual futures markets. Returns a summary table with symbol, side, pool, max leverage, and pubkey. For a trading-ready view with prices, use get_trading_overview instead."
49321
49347
  }, async () => {
49322
49348
  const [markets, poolData] = await Promise.all([
49323
49349
  client.getMarkets(),
@@ -49367,13 +49393,48 @@ Use get_pool with a pubkey for full details. Use get_pool_data for AUM and utili
49367
49393
  });
49368
49394
  }
49369
49395
 
49396
+ // src/sanitize.ts
49397
+ var zBool = exports_external.preprocess((v) => {
49398
+ if (typeof v === "string")
49399
+ return v === "true";
49400
+ return v;
49401
+ }, exports_external.boolean());
49402
+ function sanitizeError(e) {
49403
+ const msg = e instanceof Error ? e.message : String(e);
49404
+ return msg.replace(/\[[\d,\s]{20,}\]/g, "[REDACTED]").replace(/[0-9a-fA-F]{40,}/g, "[REDACTED]").replace(/[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{40,}/g, "[REDACTED]").replace(/[A-Za-z0-9+/]{40,}={0,2}/g, "[REDACTED]");
49405
+ }
49406
+
49370
49407
  // src/tools/custodies.ts
49371
49408
  function registerCustodyTools(server, client) {
49372
49409
  server.registerTool("get_custodies", {
49373
- description: "List all custody accounts. Custodies hold the actual tokens for each asset in a pool (e.g., the USDC custody, the SOL custody). Returns custody pubkeys, token info, and utilization metrics."
49410
+ description: "List all custody accounts (token vaults) across pools. Returns a compact summary with symbol, pool, and pubkey. Use get_custody with a specific pubkey for full details (utilization, fees, limits)."
49374
49411
  }, async () => {
49375
- const custodies = await client.getCustodies();
49376
- return { content: [{ type: "text", text: JSON.stringify(custodies, null, 2) }] };
49412
+ const [custResult, poolResult] = await Promise.allSettled([
49413
+ client.getCustodies(),
49414
+ client.getPoolData()
49415
+ ]);
49416
+ const custodies = custResult.status === "fulfilled" ? custResult.value : [];
49417
+ const custodyMap = poolResult.status === "fulfilled" ? buildCustodySymbolMap(poolResult.value) : new Map;
49418
+ if (custodies.length === 0) {
49419
+ const err = custResult.status === "rejected" ? `: ${sanitizeError(custResult.reason)}` : "";
49420
+ return { content: [{ type: "text", text: `No custody accounts found${err}` }] };
49421
+ }
49422
+ const lines = [
49423
+ `${custodies.length} custody accounts:
49424
+ `,
49425
+ "Symbol | Pool | Pubkey",
49426
+ "-----------|----------------|----------------------------------------------"
49427
+ ];
49428
+ for (const c of custodies) {
49429
+ const info = custodyMap.get(c.pubkey);
49430
+ const symbol2 = (info?.symbol ?? "?").padEnd(10);
49431
+ const pool = (info?.pool ?? "?").padEnd(14);
49432
+ lines.push(`${symbol2} | ${pool} | ${c.pubkey}`);
49433
+ }
49434
+ lines.push(`
49435
+ Use get_custody with a pubkey for full details (utilization, fees, limits).`);
49436
+ return { content: [{ type: "text", text: lines.join(`
49437
+ `) }] };
49377
49438
  });
49378
49439
  server.registerTool("get_custody", {
49379
49440
  description: "Get detailed custody information for a specific custody account. Includes utilization, fees, and limits.",
@@ -49434,7 +49495,7 @@ function formatEnrichedPosition(p) {
49434
49495
  }
49435
49496
  function registerPositionTools(server, client) {
49436
49497
  server.registerTool("get_positions", {
49437
- description: "List perpetual positions, optionally filtered by wallet owner. Without an owner filter, returns ALL open positions (may be large). With owner, returns enriched positions with computed PnL, leverage, and liquidation price.",
49498
+ description: "List open perpetual positions. Without owner: returns all positions (large response). With owner: returns enriched positions with live PnL, leverage, and liquidation price. For a complete wallet overview, use get_account_summary instead.",
49438
49499
  inputSchema: {
49439
49500
  owner: exports_external.string().regex(/^[1-9A-HJ-NP-Za-km-z]{32,44}$/).optional().describe("Wallet pubkey to filter by. When provided, returns enriched positions with PnL, leverage, and liquidation price.")
49440
49501
  }
@@ -49455,7 +49516,7 @@ ${text}` }] };
49455
49516
  return { content: [{ type: "text", text: JSON.stringify(positions, null, 2) }] };
49456
49517
  });
49457
49518
  server.registerTool("get_position", {
49458
- description: "Get a single position by its on-chain account pubkey. Returns raw position data. For enriched data (PnL, leverage, liq price), use get_positions with the owner filter instead.",
49519
+ description: "Get a single position by its on-chain pubkey. Returns raw position data. For enriched data with PnL and leverage, use get_positions with owner or get_account_summary.",
49459
49520
  inputSchema: { pubkey: exports_external.string().regex(/^[1-9A-HJ-NP-Za-km-z]{32,44}$/).describe("Position account pubkey") }
49460
49521
  }, async ({ pubkey }) => {
49461
49522
  const position = await client.getPosition(pubkey);
@@ -49480,7 +49541,7 @@ function formatEnrichedOrder(o) {
49480
49541
  }
49481
49542
  function registerOrderTools(server, client) {
49482
49543
  server.registerTool("get_orders", {
49483
- description: "List open orders (limit orders, take-profit, stop-loss), optionally filtered by wallet owner. When owner is provided, returns enriched order data with computed trigger prices and sizes.",
49544
+ description: "List pending orders (limit, take-profit, stop-loss). With owner: returns enriched orders with computed trigger prices and sizes. Needed to find order_id (0-7) for edit_trigger_order or cancel_trigger_order.",
49484
49545
  inputSchema: {
49485
49546
  owner: exports_external.string().regex(/^[1-9A-HJ-NP-Za-km-z]{32,44}$/).optional().describe("Wallet pubkey to filter by. When provided, returns enriched orders.")
49486
49547
  }
@@ -49544,15 +49605,169 @@ Use get_pool_data with pool_pubkey for full custody stats.`);
49544
49605
  });
49545
49606
  }
49546
49607
 
49547
- // src/sanitize.ts
49548
- var zBool = exports_external.preprocess((v) => {
49549
- if (typeof v === "string")
49550
- return v === "true";
49551
- return v;
49552
- }, exports_external.boolean());
49553
- function sanitizeError(e) {
49554
- const msg = e instanceof Error ? e.message : String(e);
49555
- return msg.replace(/\[[\d,\s]{20,}\]/g, "[REDACTED]").replace(/[0-9a-fA-F]{40,}/g, "[REDACTED]").replace(/[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{40,}/g, "[REDACTED]").replace(/[A-Za-z0-9+/]{40,}={0,2}/g, "[REDACTED]");
49608
+ // src/tools/account-summary.ts
49609
+ function registerAccountSummaryTool(server, client) {
49610
+ server.registerTool("get_account_summary", {
49611
+ description: "Get a complete wallet overview: all open positions (with PnL), all pending orders (limit/TP/SL), and current prices for held markets. " + "Recommended first call when managing a specific wallet — replaces calling get_positions + get_orders + get_prices separately.",
49612
+ inputSchema: {
49613
+ owner: exports_external.string().regex(/^[1-9A-HJ-NP-Za-km-z]{32,44}$/).describe("Wallet pubkey")
49614
+ }
49615
+ }, async ({ owner }) => {
49616
+ const [posResult, ordResult, priceResult] = await Promise.allSettled([
49617
+ client.getOwnerPositions(owner),
49618
+ client.getOwnerOrders(owner),
49619
+ client.getPrices()
49620
+ ]);
49621
+ const positions = posResult.status === "fulfilled" ? posResult.value : null;
49622
+ const orders = ordResult.status === "fulfilled" ? ordResult.value : null;
49623
+ const prices = priceResult.status === "fulfilled" ? priceResult.value : null;
49624
+ const lines = [`=== Account Summary for ${owner} ===
49625
+ `];
49626
+ const warnings = [];
49627
+ if (!positions)
49628
+ warnings.push(`Positions unavailable: ${sanitizeError(posResult.reason)}`);
49629
+ if (!orders)
49630
+ warnings.push(`Orders unavailable: ${sanitizeError(ordResult.reason)}`);
49631
+ if (!prices)
49632
+ warnings.push(`Prices unavailable: ${sanitizeError(priceResult.reason)}`);
49633
+ if (!positions) {
49634
+ lines.push(`Positions: unavailable
49635
+ `);
49636
+ } else if (positions.length === 0) {
49637
+ lines.push(`Positions: None
49638
+ `);
49639
+ } else {
49640
+ lines.push(`── ${positions.length} Position(s) ──`);
49641
+ for (const p of positions) {
49642
+ const pnl = p.pnlWithFeeUsdUi ? ` | PnL: $${p.pnlWithFeeUsdUi} (${p.pnlPercentageWithFee}%)` : "";
49643
+ lines.push(` ${p.sideUi} ${p.marketSymbol}: $${p.sizeUsdUi} @ $${p.entryPriceUi} (${p.leverageUi}x)${pnl}`);
49644
+ lines.push(` Key: ${p.key} | Liq: $${p.liquidationPriceUi}`);
49645
+ }
49646
+ lines.push("");
49647
+ }
49648
+ if (!orders) {
49649
+ lines.push(`Orders: unavailable
49650
+ `);
49651
+ } else if (orders.length === 0) {
49652
+ lines.push(`Orders: None
49653
+ `);
49654
+ } else {
49655
+ lines.push(`── Orders ──`);
49656
+ for (const o of orders) {
49657
+ for (const lo of o.limitOrders ?? []) {
49658
+ lines.push(` LIMIT ${lo.sideUi} ${lo.symbol}: $${lo.sizeUsdUi} @ $${lo.entryPriceUi} (${lo.leverageUi}x)`);
49659
+ }
49660
+ for (const tp of o.takeProfitOrders ?? []) {
49661
+ lines.push(` TP ${tp.sideUi} ${tp.symbol}: $${tp.sizeUsdUi} @ $${tp.triggerPriceUi}`);
49662
+ }
49663
+ for (const sl of o.stopLossOrders ?? []) {
49664
+ lines.push(` SL ${sl.sideUi} ${sl.symbol}: $${sl.sizeUsdUi} @ $${sl.triggerPriceUi}`);
49665
+ }
49666
+ }
49667
+ lines.push("");
49668
+ }
49669
+ if (positions && prices) {
49670
+ const marketSymbols = [...new Set(positions.map((p) => p.marketSymbol).filter((s) => !!s))];
49671
+ if (marketSymbols.length > 0) {
49672
+ lines.push("── Current Prices ──");
49673
+ for (const sym of marketSymbols) {
49674
+ const priceData = prices[sym];
49675
+ if (priceData) {
49676
+ const usd = formatPriceUsd(priceData);
49677
+ lines.push(` ${sym}: ${usd === "?" ? "price unavailable" : `$${usd}`}`);
49678
+ }
49679
+ }
49680
+ lines.push("");
49681
+ }
49682
+ }
49683
+ if (warnings.length > 0) {
49684
+ lines.push("── Warnings ──");
49685
+ for (const w of warnings)
49686
+ lines.push(` ${w}`);
49687
+ }
49688
+ lines.push("Note: Data is cached ~15 seconds. Recently closed positions may still appear briefly.");
49689
+ return { content: [{ type: "text", text: lines.join(`
49690
+ `) }] };
49691
+ });
49692
+ }
49693
+
49694
+ // src/tools/trading-overview.ts
49695
+ function registerTradingOverviewTool(server, client) {
49696
+ server.registerTool("get_trading_overview", {
49697
+ description: "Get a trading-ready market snapshot: all markets with current oracle prices, max leverage, and pool utilization. " + "Recommended first call when planning new trades — replaces calling get_markets + get_prices + get_pool_data separately."
49698
+ }, async () => {
49699
+ const [marketsResult, pricesResult, poolResult] = await Promise.allSettled([
49700
+ client.getMarkets(),
49701
+ client.getPrices(),
49702
+ client.getPoolData()
49703
+ ]);
49704
+ const markets = marketsResult.status === "fulfilled" ? marketsResult.value : null;
49705
+ const prices = pricesResult.status === "fulfilled" ? pricesResult.value : null;
49706
+ const poolData = poolResult.status === "fulfilled" ? poolResult.value : null;
49707
+ const lines = [`=== Trading Overview ===
49708
+ `];
49709
+ const warnings = [];
49710
+ if (!markets)
49711
+ warnings.push(`Markets unavailable: ${sanitizeError(marketsResult.reason)}`);
49712
+ if (!prices)
49713
+ warnings.push(`Prices unavailable: ${sanitizeError(pricesResult.reason)}`);
49714
+ if (!poolData)
49715
+ warnings.push(`Pool data unavailable: ${sanitizeError(poolResult.reason)}`);
49716
+ if (markets) {
49717
+ const custodyInfo = poolData ? buildCustodySymbolMap(poolData) : new Map;
49718
+ lines.push("── Markets ──");
49719
+ lines.push("Symbol | Price | Side | Max Lev | Pool");
49720
+ lines.push("-----------|---------------|-------|---------|---------------");
49721
+ const enriched = markets.map((m) => {
49722
+ const info = custodyInfo.get(m.account.target_custody);
49723
+ return {
49724
+ symbol: info?.symbol ?? "UNKNOWN",
49725
+ pool: info?.pool ?? "?",
49726
+ maxLeverage: info?.maxLeverage ?? "?",
49727
+ side: m.account.side,
49728
+ open: m.account.permissions.allow_open_position
49729
+ };
49730
+ }).sort((a, b) => a.pool.localeCompare(b.pool) || a.symbol.localeCompare(b.symbol) || a.side.localeCompare(b.side));
49731
+ const seen = new Set;
49732
+ for (const m of enriched) {
49733
+ const key = `${m.symbol}-${m.side}`;
49734
+ if (seen.has(key))
49735
+ continue;
49736
+ seen.add(key);
49737
+ let priceStr = "?";
49738
+ if (prices) {
49739
+ const priceData = prices[m.symbol];
49740
+ if (priceData)
49741
+ priceStr = `$${formatPriceUsd(priceData)}`;
49742
+ }
49743
+ const status = m.open ? "" : " [CLOSED]";
49744
+ lines.push(`${m.symbol.padEnd(10)} | ${priceStr.padEnd(13)} | ${m.side.padEnd(5)} | ${m.maxLeverage.padEnd(7)} | ${m.pool}${status}`);
49745
+ }
49746
+ }
49747
+ if (poolData) {
49748
+ lines.push(`
49749
+ ── Pool Utilization ──`);
49750
+ lines.push("Pool | AUM | LP Price | Stable%");
49751
+ lines.push("---------------|----------------|-----------|--------");
49752
+ for (const p of poolData.pools) {
49753
+ const name = (p.poolName ?? "Unknown").padEnd(14);
49754
+ const aum = formatCompactUsd(p.lpStats?.totalPoolValueUsd).padEnd(14);
49755
+ const lp = `$${p.lpStats?.lpPrice ?? "?"}`.padEnd(9);
49756
+ const stable = `${p.lpStats?.stableCoinPercentage ?? "?"}%`;
49757
+ lines.push(`${name} | ${aum} | ${lp} | ${stable}`);
49758
+ }
49759
+ }
49760
+ if (warnings.length > 0) {
49761
+ lines.push(`
49762
+ ── Warnings ──`);
49763
+ for (const w of warnings)
49764
+ lines.push(` ${w}`);
49765
+ }
49766
+ lines.push(`
49767
+ Use open_position to trade. Use get_account_summary to check existing positions.`);
49768
+ return { content: [{ type: "text", text: lines.join(`
49769
+ `) }] };
49770
+ });
49556
49771
  }
49557
49772
 
49558
49773
  // src/tools/open-position.ts
@@ -49599,7 +49814,7 @@ No transaction built (provide owner wallet pubkey to build transaction)`);
49599
49814
  }
49600
49815
  function registerOpenPositionTool(server, client) {
49601
49816
  server.registerTool("open_position", {
49602
- description: "Build a transaction to open a new perpetual position on Flash Trade. Returns a preview (entry price, fees, leverage, liquidation price) AND an unsigned transaction. The transaction must be signed by the user's wallet and submitted to Solana separately. IMPORTANT: Always present the preview to the user before they sign. This tool does NOT execute the trade. Supports both MARKET and LIMIT orders, with optional take-profit and stop-loss. COLLATERAL WARNING: Limit orders, take-profit, and stop-loss require >$10 collateral AFTER entry fees. A $10 position will have fees deducted, dropping collateral below $10 and preventing TP/SL/limit orders. Use at least $11-12 input_amount when planning to set TP/SL.",
49817
+ description: "Build a transaction to open a new perpetual futures position. Returns a preview (entry price, fees, leverage, liquidation price) AND an unsigned transaction. " + "Show the preview to the user before signing. Supports MARKET and LIMIT orders with optional TP/SL. " + "IMPORTANT: Use at least $11 input_amount if setting take_profit or stop_loss collateral after fees must exceed $10 for TP/SL to work on-chain.",
49603
49818
  inputSchema: {
49604
49819
  input_token_symbol: exports_external.string().max(16).describe('Token to pay with: "USDC", "SOL", etc.'),
49605
49820
  output_token_symbol: exports_external.string().max(16).describe('Market to trade: "SOL", "BTC", "ETH", etc.'),
@@ -49681,7 +49896,7 @@ Transaction (base64, unsigned — sign with wallet):`);
49681
49896
  }
49682
49897
  function registerClosePositionTool(server, client) {
49683
49898
  server.registerTool("close_position", {
49684
- description: "Build a transaction to close (fully or partially) an existing perpetual position. Returns a preview with PnL, fees, and receive amount, plus an unsigned transaction. For a full close, set input_usd to the position's full size. For a partial close, use a smaller amount. The transaction must be signed and submitted separately.",
49899
+ description: "Build a transaction to close (fully or partially) an existing position. Returns preview with PnL, fees, and receive amount, plus unsigned transaction. " + "For full close: set input_usd to position size. For partial: use smaller amount. Requires position_key from get_positions.",
49685
49900
  inputSchema: {
49686
49901
  position_key: exports_external.string().regex(/^[1-9A-HJ-NP-Za-km-z]{32,44}$/).describe("Position account pubkey to close"),
49687
49902
  input_usd: exports_external.string().max(32).describe('USD amount to close, e.g. "500.00" for full or "250.00" for partial'),
@@ -49815,7 +50030,7 @@ Transaction (base64, unsigned — sign with wallet):`);
49815
50030
  }
49816
50031
  function registerReversePositionTool(server, client) {
49817
50032
  server.registerTool("reverse_position", {
49818
- description: "Build a transaction to reverse a position (close current + open opposite direction). For example, close a LONG and open a SHORT with the same collateral. Returns combined close+open preview and a single unsigned transaction.",
50033
+ description: "Build a transaction to reverse a position (close current + open opposite direction). For example, close a LONG and open a SHORT with same collateral. " + "Returns combined preview and a single unsigned transaction. Requires position_key from get_positions.",
49819
50034
  inputSchema: {
49820
50035
  position_key: exports_external.string().regex(/^[1-9A-HJ-NP-Za-km-z]{32,44}$/).describe("Position account pubkey to reverse"),
49821
50036
  owner: exports_external.string().regex(/^[1-9A-HJ-NP-Za-km-z]{32,44}$/).describe("Wallet pubkey"),
@@ -56872,7 +57087,7 @@ var $VersionedTransaction = VersionedTransaction;
56872
57087
  import fs from "node:fs";
56873
57088
  function registerSignAndSendTool(server) {
56874
57089
  server.registerTool("sign_and_send", {
56875
- description: "Sign and submit a base64-encoded unsigned Solana transaction using the locally configured keypair. " + "Call this AFTER a transaction tool (open_position, close_position, add_collateral, remove_collateral, reverse_position) " + "returns a transactionBase64 string AND the user has reviewed and approved the preview. " + "The keypair is read from KEYPAIR_PATH (default: ~/.config/solana/id.json). " + "Returns the confirmed transaction signature and a Solscan link. " + "IMPORTANT: Always show the transaction preview to the user and get their approval BEFORE calling this tool. " + "This tool signs with the local keypair and submits to Solana mainnet — the action is IRREVERSIBLE. " + "NOTE: This tool never exposes private key material in its output.",
57090
+ description: "Sign and submit a base64 transaction to Solana mainnet using the local keypair. " + "Call ONLY after a transaction tool returns transactionBase64 AND the user has approved the preview. " + "IRREVERSIBLE always confirm with user first. Call immediately blockhashes expire in ~60 seconds. " + "Returns the confirmed signature and a Solscan link.",
56876
57091
  inputSchema: {
56877
57092
  transaction_base64: exports_external.string().max(1e4).describe("The base64-encoded unsigned transaction returned by a transaction tool")
56878
57093
  }
@@ -56937,13 +57152,28 @@ Signature: ${signature2}` }],
56937
57152
  const lines = [
56938
57153
  "=== Transaction Confirmed ===",
56939
57154
  `Signature: ${signature2}`,
56940
- `Explorer: https://solscan.io/tx/${signature2}`
57155
+ `Explorer: https://solscan.io/tx/${signature2}`,
57156
+ "",
57157
+ "Next: Call get_positions (with owner) to verify the position, or get_orders to check trigger orders."
56941
57158
  ];
56942
57159
  return { content: [{ type: "text", text: lines.join(`
56943
57160
  `) }] };
56944
57161
  } catch (e) {
57162
+ const msg = sanitizeError(e);
57163
+ const isBlockhashExpired = msg.includes("Blockhash not found") || msg.includes("block height exceeded");
57164
+ const isSignerMismatch = msg.includes("Cannot sign with non signer key");
57165
+ let hint = "";
57166
+ if (isBlockhashExpired) {
57167
+ hint = `
57168
+
57169
+ The blockhash has expired (~60 seconds). Re-call the original transaction tool to get a fresh transaction, then call sign_and_send immediately.`;
57170
+ } else if (isSignerMismatch) {
57171
+ hint = `
57172
+
57173
+ The transaction was built for a different wallet than the local keypair. Re-call the transaction tool to get a fresh transaction, then sign immediately — blockhashes expire in ~60 seconds.`;
57174
+ }
56945
57175
  return {
56946
- content: [{ type: "text", text: `Transaction send failed: ${sanitizeError(e)}` }],
57176
+ content: [{ type: "text", text: `Transaction send failed: ${msg}${hint}` }],
56947
57177
  isError: true
56948
57178
  };
56949
57179
  }
@@ -56953,7 +57183,7 @@ Signature: ${signature2}` }],
56953
57183
  // src/tools/trigger-orders.ts
56954
57184
  function registerTriggerOrderTools(server, client) {
56955
57185
  server.registerTool("place_trigger_order", {
56956
- description: "Place a take-profit (TP) or stop-loss (SL) trigger order on an existing position. The trigger order will automatically close part or all of the position when the price hits the trigger level. Requires an open position for the given market/side. Returns an unsigned transaction. Up to 5 trigger orders per position.",
57186
+ description: "Place a take-profit (TP) or stop-loss (SL) trigger order on an existing position. Up to 5 per position. " + "Use preview_tp_sl first to calculate optimal trigger prices. Returns unsigned transaction.",
56957
57187
  inputSchema: {
56958
57188
  market_symbol: exports_external.string().max(16).describe('Market symbol, e.g. "SOL", "BTC", "ETH"'),
56959
57189
  side: exports_external.enum(["LONG", "SHORT"]).describe("Position side"),
@@ -56984,12 +57214,14 @@ WARNING: ${res.err}`);
56984
57214
  lines.push(`
56985
57215
  Transaction (base64, unsigned — sign with wallet):`);
56986
57216
  lines.push(res.transactionBase64);
57217
+ lines.push(`
57218
+ Next: After signing, call get_orders with owner to see the order ID for editing/canceling.`);
56987
57219
  }
56988
57220
  return { content: [{ type: "text", text: lines.join(`
56989
57221
  `) }] };
56990
57222
  });
56991
57223
  server.registerTool("edit_trigger_order", {
56992
- description: "Edit an existing take-profit or stop-loss trigger order. Change the trigger price, size, or order type without canceling and re-placing. Requires the order_id (0-7) from get_orders. Returns an unsigned transaction.",
57224
+ description: "Edit an existing TP or SL trigger order. Change trigger price, size, or type. Requires order_id (0-7) from get_orders. Returns unsigned transaction.",
56993
57225
  inputSchema: {
56994
57226
  market_symbol: exports_external.string().max(16).describe('Market symbol, e.g. "SOL", "BTC", "ETH"'),
56995
57227
  side: exports_external.enum(["LONG", "SHORT"]).describe("Position side"),
@@ -57027,7 +57259,7 @@ Transaction (base64, unsigned — sign with wallet):`);
57027
57259
  `) }] };
57028
57260
  });
57029
57261
  server.registerTool("cancel_trigger_order", {
57030
- description: "Cancel a single take-profit or stop-loss trigger order. Requires the order_id (0-7) from get_orders. Returns an unsigned transaction.",
57262
+ description: "Cancel a single TP or SL trigger order. Requires order_id (0-7) from get_orders. Returns unsigned transaction.",
57031
57263
  inputSchema: {
57032
57264
  market_symbol: exports_external.string().max(16).describe('Market symbol, e.g. "SOL", "BTC", "ETH"'),
57033
57265
  side: exports_external.enum(["LONG", "SHORT"]).describe("Position side"),
@@ -57059,7 +57291,7 @@ Transaction (base64, unsigned — sign with wallet):`);
57059
57291
  `) }] };
57060
57292
  });
57061
57293
  server.registerTool("cancel_all_trigger_orders", {
57062
- description: "Cancel ALL take-profit and stop-loss trigger orders for a market+side. Removes all TP and SL orders in one transaction. Returns an unsigned transaction.",
57294
+ description: "Cancel ALL TP and SL trigger orders for a market+side in one transaction. Returns unsigned transaction.",
57063
57295
  inputSchema: {
57064
57296
  market_symbol: exports_external.string().max(16).describe('Market symbol, e.g. "SOL", "BTC", "ETH"'),
57065
57297
  side: exports_external.enum(["LONG", "SHORT"]).describe("Position side"),
@@ -57098,6 +57330,8 @@ function registerReadTools(server, client) {
57098
57330
  registerPositionTools(server, client);
57099
57331
  registerOrderTools(server, client);
57100
57332
  registerPoolDataTools(server, client);
57333
+ registerAccountSummaryTool(server, client);
57334
+ registerTradingOverviewTool(server, client);
57101
57335
  }
57102
57336
  function registerTransactionTools(server, client) {
57103
57337
  registerOpenPositionTool(server, client);
@@ -57181,7 +57415,7 @@ try {
57181
57415
  const client = new FlashApiClient(config2);
57182
57416
  const server = new McpServer({
57183
57417
  name: "flash-trade",
57184
- version: "0.3.5"
57418
+ version: "0.4.1"
57185
57419
  }, {
57186
57420
  capabilities: {
57187
57421
  tools: {},
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flash-trade-mcp",
3
- "version": "0.3.5",
3
+ "version": "0.4.1",
4
4
  "description": "MCP server for Flash Trade — perpetual futures DEX on Solana. Provides AI agents with typed tools for trading, position management, and market data.",
5
5
  "module": "src/index.ts",
6
6
  "main": "dist/index.js",