shll-skills 5.5.1 → 5.5.2

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/SKILL.md CHANGED
@@ -153,6 +153,8 @@ If `-l/--listing-id` is omitted, `setup-guide` auto-selects an active listing fr
153
153
  - `shll-run four-buy --token <address> -a <bnb> -k <tokenId>`
154
154
  - `shll-run four-sell --token <address> -a <tokenAmount> -k <tokenId>`
155
155
 
156
+ `four-buy` amount unit is BNB, not USD. If user gives a USD target, convert to BNB first and confirm final BNB amount before execution.
157
+
156
158
  ### Read-only and audit
157
159
 
158
160
  - `shll-run portfolio -k <tokenId>`
package/dist/index.js CHANGED
@@ -2747,13 +2747,17 @@ fourBuyCmd.action(async (opts) => {
2747
2747
  const tokenAddr = opts.token;
2748
2748
  const slippage = Number(opts.slippage);
2749
2749
  const bnbAmount = parseAmount(opts.amount, 18);
2750
+ if (bnbAmount <= 0n) {
2751
+ outputError("Invalid amount.", "four-buy amount must be a positive BNB value.");
2752
+ process.exit(1);
2753
+ }
2750
2754
  const info = await publicClient.readContract({
2751
2755
  address: FOUR_MEME_HELPER_V3,
2752
2756
  abi: FOUR_MEME_HELPER_ABI,
2753
2757
  functionName: "getTokenInfo",
2754
2758
  args: [tokenAddr]
2755
2759
  });
2756
- const [version, tokenManager, quote, , , , , , , , , liquidityAdded] = info;
2760
+ const [version, tokenManager, quote, , , minTradingFee, , , , , , liquidityAdded] = info;
2757
2761
  if (liquidityAdded) {
2758
2762
  output({ status: "error", message: "Token has already migrated to DEX. Use the 'swap' command instead of 'four-buy'." });
2759
2763
  process.exit(1);
@@ -2763,6 +2767,20 @@ fourBuyCmd.action(async (opts) => {
2763
2767
  output({ status: "error", message: `Token uses BEP20 quote (${quote}), not BNB. BEP20 quote pairs are not yet supported in this tool.` });
2764
2768
  process.exit(1);
2765
2769
  }
2770
+ if (bnbAmount < minTradingFee) {
2771
+ outputError(
2772
+ "Input amount is below Four.meme minimum trading fee threshold.",
2773
+ "Increase --amount and retry.",
2774
+ {
2775
+ token: tokenAddr,
2776
+ inputBnb: opts.amount,
2777
+ inputWei: bnbAmount.toString(),
2778
+ minTradingFeeWei: minTradingFee.toString(),
2779
+ minTradingFeeBnb: (Number(minTradingFee) / 1e18).toFixed(6)
2780
+ }
2781
+ );
2782
+ process.exit(1);
2783
+ }
2766
2784
  const tryBuyResult = await publicClient.readContract({
2767
2785
  address: FOUR_MEME_HELPER_V3,
2768
2786
  abi: FOUR_MEME_HELPER_ABI,
@@ -2770,6 +2788,48 @@ fourBuyCmd.action(async (opts) => {
2770
2788
  args: [tokenAddr, 0n, bnbAmount]
2771
2789
  });
2772
2790
  const [, , estimatedAmount, estimatedCost, estimatedFee, amountMsgValue] = tryBuyResult;
2791
+ if (amountMsgValue < minTradingFee) {
2792
+ outputError(
2793
+ "Computed payable amount is below Four.meme minimum trading fee threshold.",
2794
+ "Increase --amount and retry.",
2795
+ {
2796
+ token: tokenAddr,
2797
+ inputBnb: opts.amount,
2798
+ payableWei: amountMsgValue.toString(),
2799
+ minTradingFeeWei: minTradingFee.toString(),
2800
+ minTradingFeeBnb: (Number(minTradingFee) / 1e18).toFixed(6)
2801
+ }
2802
+ );
2803
+ process.exit(1);
2804
+ }
2805
+ if (estimatedAmount <= 0n || amountMsgValue <= 0n) {
2806
+ outputError(
2807
+ "four-buy quote returned zero output.",
2808
+ "Requested amount is likely too small for this market. Increase --amount and retry.",
2809
+ {
2810
+ token: tokenAddr,
2811
+ inputBnb: opts.amount,
2812
+ estimatedCostWei: estimatedCost.toString(),
2813
+ estimatedFeeWei: estimatedFee.toString()
2814
+ }
2815
+ );
2816
+ process.exit(1);
2817
+ }
2818
+ const vaultBnbBalance = await publicClient.getBalance({ address: vault });
2819
+ if (amountMsgValue > vaultBnbBalance) {
2820
+ outputError(
2821
+ "Vault BNB balance is insufficient for four-buy.",
2822
+ "Deposit more BNB to vault or reduce --amount, then retry.",
2823
+ {
2824
+ vault,
2825
+ requiredBnb: (Number(amountMsgValue) / 1e18).toFixed(6),
2826
+ availableBnb: (Number(vaultBnbBalance) / 1e18).toFixed(6),
2827
+ requiredWei: amountMsgValue.toString(),
2828
+ availableWei: vaultBnbBalance.toString()
2829
+ }
2830
+ );
2831
+ process.exit(1);
2832
+ }
2773
2833
  const minAmount = estimatedAmount * BigInt(100 - slippage) / 100n;
2774
2834
  const alignedMinAmount = minAmount / 1000000000n * 1000000000n;
2775
2835
  output({
@@ -2800,7 +2860,19 @@ fourBuyCmd.action(async (opts) => {
2800
2860
  output({ status: "rejected", reason: simResult.reason });
2801
2861
  process.exit(0);
2802
2862
  }
2803
- const result = await client.execute(tokenId, action, true);
2863
+ let result;
2864
+ try {
2865
+ result = await client.execute(tokenId, action, true);
2866
+ } catch (error) {
2867
+ const message = error instanceof Error ? error.message : "Unknown execution revert";
2868
+ outputError(
2869
+ "four-buy execution reverted on-chain.",
2870
+ "Likely market-side failure (amount too small, temporary pool state change, or token-side constraint).",
2871
+ { reason: message, token: tokenAddr, inputBnb: opts.amount }
2872
+ );
2873
+ process.exit(1);
2874
+ return;
2875
+ }
2804
2876
  output({
2805
2877
  status: "success",
2806
2878
  hash: result.hash,
package/dist/index.mjs CHANGED
@@ -2259,13 +2259,17 @@ fourBuyCmd.action(async (opts) => {
2259
2259
  const tokenAddr = opts.token;
2260
2260
  const slippage = Number(opts.slippage);
2261
2261
  const bnbAmount = parseAmount(opts.amount, 18);
2262
+ if (bnbAmount <= 0n) {
2263
+ outputError("Invalid amount.", "four-buy amount must be a positive BNB value.");
2264
+ process.exit(1);
2265
+ }
2262
2266
  const info = await publicClient.readContract({
2263
2267
  address: FOUR_MEME_HELPER_V3,
2264
2268
  abi: FOUR_MEME_HELPER_ABI,
2265
2269
  functionName: "getTokenInfo",
2266
2270
  args: [tokenAddr]
2267
2271
  });
2268
- const [version, tokenManager, quote, , , , , , , , , liquidityAdded] = info;
2272
+ const [version, tokenManager, quote, , , minTradingFee, , , , , , liquidityAdded] = info;
2269
2273
  if (liquidityAdded) {
2270
2274
  output({ status: "error", message: "Token has already migrated to DEX. Use the 'swap' command instead of 'four-buy'." });
2271
2275
  process.exit(1);
@@ -2275,6 +2279,20 @@ fourBuyCmd.action(async (opts) => {
2275
2279
  output({ status: "error", message: `Token uses BEP20 quote (${quote}), not BNB. BEP20 quote pairs are not yet supported in this tool.` });
2276
2280
  process.exit(1);
2277
2281
  }
2282
+ if (bnbAmount < minTradingFee) {
2283
+ outputError(
2284
+ "Input amount is below Four.meme minimum trading fee threshold.",
2285
+ "Increase --amount and retry.",
2286
+ {
2287
+ token: tokenAddr,
2288
+ inputBnb: opts.amount,
2289
+ inputWei: bnbAmount.toString(),
2290
+ minTradingFeeWei: minTradingFee.toString(),
2291
+ minTradingFeeBnb: (Number(minTradingFee) / 1e18).toFixed(6)
2292
+ }
2293
+ );
2294
+ process.exit(1);
2295
+ }
2278
2296
  const tryBuyResult = await publicClient.readContract({
2279
2297
  address: FOUR_MEME_HELPER_V3,
2280
2298
  abi: FOUR_MEME_HELPER_ABI,
@@ -2282,6 +2300,48 @@ fourBuyCmd.action(async (opts) => {
2282
2300
  args: [tokenAddr, 0n, bnbAmount]
2283
2301
  });
2284
2302
  const [, , estimatedAmount, estimatedCost, estimatedFee, amountMsgValue] = tryBuyResult;
2303
+ if (amountMsgValue < minTradingFee) {
2304
+ outputError(
2305
+ "Computed payable amount is below Four.meme minimum trading fee threshold.",
2306
+ "Increase --amount and retry.",
2307
+ {
2308
+ token: tokenAddr,
2309
+ inputBnb: opts.amount,
2310
+ payableWei: amountMsgValue.toString(),
2311
+ minTradingFeeWei: minTradingFee.toString(),
2312
+ minTradingFeeBnb: (Number(minTradingFee) / 1e18).toFixed(6)
2313
+ }
2314
+ );
2315
+ process.exit(1);
2316
+ }
2317
+ if (estimatedAmount <= 0n || amountMsgValue <= 0n) {
2318
+ outputError(
2319
+ "four-buy quote returned zero output.",
2320
+ "Requested amount is likely too small for this market. Increase --amount and retry.",
2321
+ {
2322
+ token: tokenAddr,
2323
+ inputBnb: opts.amount,
2324
+ estimatedCostWei: estimatedCost.toString(),
2325
+ estimatedFeeWei: estimatedFee.toString()
2326
+ }
2327
+ );
2328
+ process.exit(1);
2329
+ }
2330
+ const vaultBnbBalance = await publicClient.getBalance({ address: vault });
2331
+ if (amountMsgValue > vaultBnbBalance) {
2332
+ outputError(
2333
+ "Vault BNB balance is insufficient for four-buy.",
2334
+ "Deposit more BNB to vault or reduce --amount, then retry.",
2335
+ {
2336
+ vault,
2337
+ requiredBnb: (Number(amountMsgValue) / 1e18).toFixed(6),
2338
+ availableBnb: (Number(vaultBnbBalance) / 1e18).toFixed(6),
2339
+ requiredWei: amountMsgValue.toString(),
2340
+ availableWei: vaultBnbBalance.toString()
2341
+ }
2342
+ );
2343
+ process.exit(1);
2344
+ }
2285
2345
  const minAmount = estimatedAmount * BigInt(100 - slippage) / 100n;
2286
2346
  const alignedMinAmount = minAmount / 1000000000n * 1000000000n;
2287
2347
  output({
@@ -2312,7 +2372,19 @@ fourBuyCmd.action(async (opts) => {
2312
2372
  output({ status: "rejected", reason: simResult.reason });
2313
2373
  process.exit(0);
2314
2374
  }
2315
- const result = await client.execute(tokenId, action, true);
2375
+ let result;
2376
+ try {
2377
+ result = await client.execute(tokenId, action, true);
2378
+ } catch (error) {
2379
+ const message = error instanceof Error ? error.message : "Unknown execution revert";
2380
+ outputError(
2381
+ "four-buy execution reverted on-chain.",
2382
+ "Likely market-side failure (amount too small, temporary pool state change, or token-side constraint).",
2383
+ { reason: message, token: tokenAddr, inputBnb: opts.amount }
2384
+ );
2385
+ process.exit(1);
2386
+ return;
2387
+ }
2316
2388
  output({
2317
2389
  status: "success",
2318
2390
  hash: result.hash,
package/dist/mcp.js CHANGED
@@ -2035,19 +2035,47 @@ server.tool(
2035
2035
  if (expiryCheck.blocked) return { content: expiryCheck.content };
2036
2036
  const tokenAddr = token;
2037
2037
  const bnbAmount = parseAmount(amount, 18);
2038
+ if (bnbAmount <= 0n) {
2039
+ return {
2040
+ content: [{
2041
+ type: "text",
2042
+ text: JSON.stringify({
2043
+ status: "error",
2044
+ message: "Invalid amount. four_buy amount must be a positive BNB value."
2045
+ })
2046
+ }]
2047
+ };
2048
+ }
2049
+ const vault = await policyClient.getVault(tokenId);
2038
2050
  const info = await publicClient.readContract({
2039
2051
  address: FOUR_MEME_HELPER_V3,
2040
2052
  abi: FOUR_MEME_HELPER_ABI,
2041
2053
  functionName: "getTokenInfo",
2042
2054
  args: [tokenAddr]
2043
2055
  });
2044
- const [version, tokenManager, quote, , , , , , , , , liquidityAdded] = info;
2056
+ const [version, tokenManager, quote, , , minTradingFee, , , , , , liquidityAdded] = info;
2045
2057
  if (liquidityAdded) {
2046
2058
  return { content: [{ type: "text", text: JSON.stringify({ status: "error", message: "Token has already migrated to DEX. Use the 'swap' tool instead." }) }] };
2047
2059
  }
2048
2060
  if (quote !== "0x0000000000000000000000000000000000000000") {
2049
2061
  return { content: [{ type: "text", text: JSON.stringify({ status: "error", message: `Token uses BEP20 quote (${quote}), not BNB. BEP20 pairs not yet supported.` }) }] };
2050
2062
  }
2063
+ if (bnbAmount < minTradingFee) {
2064
+ return {
2065
+ content: [{
2066
+ type: "text",
2067
+ text: JSON.stringify({
2068
+ status: "error",
2069
+ message: "Input amount is below Four.meme minimum trading fee threshold.",
2070
+ next_step: "Increase amount and retry.",
2071
+ token: tokenAddr,
2072
+ inputWei: bnbAmount.toString(),
2073
+ minTradingFeeWei: minTradingFee.toString(),
2074
+ minTradingFeeBnb: (Number(minTradingFee) / 1e18).toFixed(6)
2075
+ })
2076
+ }]
2077
+ };
2078
+ }
2051
2079
  const tryBuyResult = await publicClient.readContract({
2052
2080
  address: FOUR_MEME_HELPER_V3,
2053
2081
  abi: FOUR_MEME_HELPER_ABI,
@@ -2055,6 +2083,53 @@ server.tool(
2055
2083
  args: [tokenAddr, 0n, bnbAmount]
2056
2084
  });
2057
2085
  const [, , estimatedAmount, , estimatedFee, amountMsgValue] = tryBuyResult;
2086
+ if (amountMsgValue < minTradingFee) {
2087
+ return {
2088
+ content: [{
2089
+ type: "text",
2090
+ text: JSON.stringify({
2091
+ status: "error",
2092
+ message: "Computed payable amount is below Four.meme minimum trading fee threshold.",
2093
+ next_step: "Increase amount and retry.",
2094
+ token: tokenAddr,
2095
+ payableWei: amountMsgValue.toString(),
2096
+ minTradingFeeWei: minTradingFee.toString(),
2097
+ minTradingFeeBnb: (Number(minTradingFee) / 1e18).toFixed(6)
2098
+ })
2099
+ }]
2100
+ };
2101
+ }
2102
+ if (estimatedAmount <= 0n || amountMsgValue <= 0n) {
2103
+ return {
2104
+ content: [{
2105
+ type: "text",
2106
+ text: JSON.stringify({
2107
+ status: "error",
2108
+ message: "four_buy quote returned zero output. Requested amount is likely too small for this market.",
2109
+ next_step: "Increase amount and retry.",
2110
+ token: tokenAddr
2111
+ })
2112
+ }]
2113
+ };
2114
+ }
2115
+ const vaultBnbBalance = await publicClient.getBalance({ address: vault });
2116
+ if (amountMsgValue > vaultBnbBalance) {
2117
+ return {
2118
+ content: [{
2119
+ type: "text",
2120
+ text: JSON.stringify({
2121
+ status: "error",
2122
+ message: "Vault BNB balance is insufficient for four_buy.",
2123
+ next_step: "Deposit more BNB to vault or reduce amount, then retry.",
2124
+ vault,
2125
+ requiredBnb: (Number(amountMsgValue) / 1e18).toFixed(6),
2126
+ availableBnb: (Number(vaultBnbBalance) / 1e18).toFixed(6),
2127
+ requiredWei: amountMsgValue.toString(),
2128
+ availableWei: vaultBnbBalance.toString()
2129
+ })
2130
+ }]
2131
+ };
2132
+ }
2058
2133
  const minAmount = estimatedAmount * BigInt(100 - slippage) / 100n;
2059
2134
  const alignedMinAmount = minAmount / 1000000000n * 1000000000n;
2060
2135
  let data;
@@ -2066,7 +2141,23 @@ server.tool(
2066
2141
  const action = { target: tokenManager, value: amountMsgValue, data };
2067
2142
  const sim = await policyClient.validate(tokenId, action);
2068
2143
  if (!sim.ok) return { content: [{ type: "text", text: JSON.stringify({ status: "rejected", reason: sim.reason, ...policyRejectionHelp(sim.reason, token_id) }) }] };
2069
- const result = await policyClient.execute(tokenId, action, true);
2144
+ let result;
2145
+ try {
2146
+ result = await policyClient.execute(tokenId, action, true);
2147
+ } catch (error) {
2148
+ const message = error instanceof Error ? error.message : "Unknown execution revert";
2149
+ return {
2150
+ content: [{
2151
+ type: "text",
2152
+ text: JSON.stringify({
2153
+ status: "error",
2154
+ message: "four_buy execution reverted on-chain",
2155
+ reason: message,
2156
+ hint: "This is usually market-side failure (amount too small, temporary pool state change, or token-side constraint), not a PolicyGuard rejection."
2157
+ })
2158
+ }]
2159
+ };
2160
+ }
2070
2161
  return {
2071
2162
  content: [{
2072
2163
  type: "text",
package/dist/mcp.mjs CHANGED
@@ -1547,19 +1547,47 @@ server.tool(
1547
1547
  if (expiryCheck.blocked) return { content: expiryCheck.content };
1548
1548
  const tokenAddr = token;
1549
1549
  const bnbAmount = parseAmount(amount, 18);
1550
+ if (bnbAmount <= 0n) {
1551
+ return {
1552
+ content: [{
1553
+ type: "text",
1554
+ text: JSON.stringify({
1555
+ status: "error",
1556
+ message: "Invalid amount. four_buy amount must be a positive BNB value."
1557
+ })
1558
+ }]
1559
+ };
1560
+ }
1561
+ const vault = await policyClient.getVault(tokenId);
1550
1562
  const info = await publicClient.readContract({
1551
1563
  address: FOUR_MEME_HELPER_V3,
1552
1564
  abi: FOUR_MEME_HELPER_ABI,
1553
1565
  functionName: "getTokenInfo",
1554
1566
  args: [tokenAddr]
1555
1567
  });
1556
- const [version, tokenManager, quote, , , , , , , , , liquidityAdded] = info;
1568
+ const [version, tokenManager, quote, , , minTradingFee, , , , , , liquidityAdded] = info;
1557
1569
  if (liquidityAdded) {
1558
1570
  return { content: [{ type: "text", text: JSON.stringify({ status: "error", message: "Token has already migrated to DEX. Use the 'swap' tool instead." }) }] };
1559
1571
  }
1560
1572
  if (quote !== "0x0000000000000000000000000000000000000000") {
1561
1573
  return { content: [{ type: "text", text: JSON.stringify({ status: "error", message: `Token uses BEP20 quote (${quote}), not BNB. BEP20 pairs not yet supported.` }) }] };
1562
1574
  }
1575
+ if (bnbAmount < minTradingFee) {
1576
+ return {
1577
+ content: [{
1578
+ type: "text",
1579
+ text: JSON.stringify({
1580
+ status: "error",
1581
+ message: "Input amount is below Four.meme minimum trading fee threshold.",
1582
+ next_step: "Increase amount and retry.",
1583
+ token: tokenAddr,
1584
+ inputWei: bnbAmount.toString(),
1585
+ minTradingFeeWei: minTradingFee.toString(),
1586
+ minTradingFeeBnb: (Number(minTradingFee) / 1e18).toFixed(6)
1587
+ })
1588
+ }]
1589
+ };
1590
+ }
1563
1591
  const tryBuyResult = await publicClient.readContract({
1564
1592
  address: FOUR_MEME_HELPER_V3,
1565
1593
  abi: FOUR_MEME_HELPER_ABI,
@@ -1567,6 +1595,53 @@ server.tool(
1567
1595
  args: [tokenAddr, 0n, bnbAmount]
1568
1596
  });
1569
1597
  const [, , estimatedAmount, , estimatedFee, amountMsgValue] = tryBuyResult;
1598
+ if (amountMsgValue < minTradingFee) {
1599
+ return {
1600
+ content: [{
1601
+ type: "text",
1602
+ text: JSON.stringify({
1603
+ status: "error",
1604
+ message: "Computed payable amount is below Four.meme minimum trading fee threshold.",
1605
+ next_step: "Increase amount and retry.",
1606
+ token: tokenAddr,
1607
+ payableWei: amountMsgValue.toString(),
1608
+ minTradingFeeWei: minTradingFee.toString(),
1609
+ minTradingFeeBnb: (Number(minTradingFee) / 1e18).toFixed(6)
1610
+ })
1611
+ }]
1612
+ };
1613
+ }
1614
+ if (estimatedAmount <= 0n || amountMsgValue <= 0n) {
1615
+ return {
1616
+ content: [{
1617
+ type: "text",
1618
+ text: JSON.stringify({
1619
+ status: "error",
1620
+ message: "four_buy quote returned zero output. Requested amount is likely too small for this market.",
1621
+ next_step: "Increase amount and retry.",
1622
+ token: tokenAddr
1623
+ })
1624
+ }]
1625
+ };
1626
+ }
1627
+ const vaultBnbBalance = await publicClient.getBalance({ address: vault });
1628
+ if (amountMsgValue > vaultBnbBalance) {
1629
+ return {
1630
+ content: [{
1631
+ type: "text",
1632
+ text: JSON.stringify({
1633
+ status: "error",
1634
+ message: "Vault BNB balance is insufficient for four_buy.",
1635
+ next_step: "Deposit more BNB to vault or reduce amount, then retry.",
1636
+ vault,
1637
+ requiredBnb: (Number(amountMsgValue) / 1e18).toFixed(6),
1638
+ availableBnb: (Number(vaultBnbBalance) / 1e18).toFixed(6),
1639
+ requiredWei: amountMsgValue.toString(),
1640
+ availableWei: vaultBnbBalance.toString()
1641
+ })
1642
+ }]
1643
+ };
1644
+ }
1570
1645
  const minAmount = estimatedAmount * BigInt(100 - slippage) / 100n;
1571
1646
  const alignedMinAmount = minAmount / 1000000000n * 1000000000n;
1572
1647
  let data;
@@ -1578,7 +1653,23 @@ server.tool(
1578
1653
  const action = { target: tokenManager, value: amountMsgValue, data };
1579
1654
  const sim = await policyClient.validate(tokenId, action);
1580
1655
  if (!sim.ok) return { content: [{ type: "text", text: JSON.stringify({ status: "rejected", reason: sim.reason, ...policyRejectionHelp(sim.reason, token_id) }) }] };
1581
- const result = await policyClient.execute(tokenId, action, true);
1656
+ let result;
1657
+ try {
1658
+ result = await policyClient.execute(tokenId, action, true);
1659
+ } catch (error) {
1660
+ const message = error instanceof Error ? error.message : "Unknown execution revert";
1661
+ return {
1662
+ content: [{
1663
+ type: "text",
1664
+ text: JSON.stringify({
1665
+ status: "error",
1666
+ message: "four_buy execution reverted on-chain",
1667
+ reason: message,
1668
+ hint: "This is usually market-side failure (amount too small, temporary pool state change, or token-side constraint), not a PolicyGuard rejection."
1669
+ })
1670
+ }]
1671
+ };
1672
+ }
1582
1673
  return {
1583
1674
  content: [{
1584
1675
  type: "text",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shll-skills",
3
- "version": "5.5.1",
3
+ "version": "5.5.2",
4
4
  "description": "SHLL DeFi Agent — CLI + MCP Server for BSC",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -24,4 +24,4 @@
24
24
  "tsup": "^8.0.2",
25
25
  "typescript": "^5.4.5"
26
26
  }
27
- }
27
+ }
package/src/index.ts CHANGED
@@ -2746,18 +2746,22 @@ fourBuyCmd.action(async (opts) => {
2746
2746
  const publicClient = createPublicClient({ chain: bsc, transport: http(rpcUrl) });
2747
2747
  const vault = await client.getVault(tokenId);
2748
2748
 
2749
- const tokenAddr = opts.token as Address;
2750
- const slippage = Number(opts.slippage);
2751
- const bnbAmount = parseAmount(opts.amount, 18);
2752
-
2753
- // 1. Get token info to check status
2754
- const info = await publicClient.readContract({
2755
- address: FOUR_MEME_HELPER_V3,
2756
- abi: FOUR_MEME_HELPER_ABI,
2749
+ const tokenAddr = opts.token as Address;
2750
+ const slippage = Number(opts.slippage);
2751
+ const bnbAmount = parseAmount(opts.amount, 18);
2752
+ if (bnbAmount <= 0n) {
2753
+ outputError("Invalid amount.", "four-buy amount must be a positive BNB value.");
2754
+ process.exit(1);
2755
+ }
2756
+
2757
+ // 1. Get token info to check status
2758
+ const info = await publicClient.readContract({
2759
+ address: FOUR_MEME_HELPER_V3,
2760
+ abi: FOUR_MEME_HELPER_ABI,
2757
2761
  functionName: "getTokenInfo",
2758
2762
  args: [tokenAddr],
2759
2763
  });
2760
- const [version, tokenManager, quote, , , , , , , , , liquidityAdded] = info;
2764
+ const [version, tokenManager, quote, , , minTradingFee, , , , , , liquidityAdded] = info;
2761
2765
 
2762
2766
  if (liquidityAdded) {
2763
2767
  output({ status: "error", message: "Token has already migrated to DEX. Use the 'swap' command instead of 'four-buy'." });
@@ -2765,19 +2769,75 @@ fourBuyCmd.action(async (opts) => {
2765
2769
  }
2766
2770
 
2767
2771
  const isQuoteBNB = quote === "0x0000000000000000000000000000000000000000";
2768
- if (!isQuoteBNB) {
2769
- output({ status: "error", message: `Token uses BEP20 quote (${quote}), not BNB. BEP20 quote pairs are not yet supported in this tool.` });
2770
- process.exit(1);
2771
- }
2772
+ if (!isQuoteBNB) {
2773
+ output({ status: "error", message: `Token uses BEP20 quote (${quote}), not BNB. BEP20 quote pairs are not yet supported in this tool.` });
2774
+ process.exit(1);
2775
+ }
2776
+ if (bnbAmount < minTradingFee) {
2777
+ outputError(
2778
+ "Input amount is below Four.meme minimum trading fee threshold.",
2779
+ "Increase --amount and retry.",
2780
+ {
2781
+ token: tokenAddr,
2782
+ inputBnb: opts.amount,
2783
+ inputWei: bnbAmount.toString(),
2784
+ minTradingFeeWei: minTradingFee.toString(),
2785
+ minTradingFeeBnb: (Number(minTradingFee) / 1e18).toFixed(6),
2786
+ },
2787
+ );
2788
+ process.exit(1);
2789
+ }
2772
2790
 
2773
2791
  // 2. Pre-calculate buy estimate
2774
- const tryBuyResult = await publicClient.readContract({
2775
- address: FOUR_MEME_HELPER_V3,
2776
- abi: FOUR_MEME_HELPER_ABI,
2777
- functionName: "tryBuy",
2778
- args: [tokenAddr, 0n, bnbAmount],
2779
- });
2780
- const [, , estimatedAmount, estimatedCost, estimatedFee, amountMsgValue] = tryBuyResult;
2792
+ const tryBuyResult = await publicClient.readContract({
2793
+ address: FOUR_MEME_HELPER_V3,
2794
+ abi: FOUR_MEME_HELPER_ABI,
2795
+ functionName: "tryBuy",
2796
+ args: [tokenAddr, 0n, bnbAmount],
2797
+ });
2798
+ const [, , estimatedAmount, estimatedCost, estimatedFee, amountMsgValue] = tryBuyResult;
2799
+ if (amountMsgValue < minTradingFee) {
2800
+ outputError(
2801
+ "Computed payable amount is below Four.meme minimum trading fee threshold.",
2802
+ "Increase --amount and retry.",
2803
+ {
2804
+ token: tokenAddr,
2805
+ inputBnb: opts.amount,
2806
+ payableWei: amountMsgValue.toString(),
2807
+ minTradingFeeWei: minTradingFee.toString(),
2808
+ minTradingFeeBnb: (Number(minTradingFee) / 1e18).toFixed(6),
2809
+ },
2810
+ );
2811
+ process.exit(1);
2812
+ }
2813
+ if (estimatedAmount <= 0n || amountMsgValue <= 0n) {
2814
+ outputError(
2815
+ "four-buy quote returned zero output.",
2816
+ "Requested amount is likely too small for this market. Increase --amount and retry.",
2817
+ {
2818
+ token: tokenAddr,
2819
+ inputBnb: opts.amount,
2820
+ estimatedCostWei: estimatedCost.toString(),
2821
+ estimatedFeeWei: estimatedFee.toString(),
2822
+ },
2823
+ );
2824
+ process.exit(1);
2825
+ }
2826
+ const vaultBnbBalance = await publicClient.getBalance({ address: vault });
2827
+ if (amountMsgValue > vaultBnbBalance) {
2828
+ outputError(
2829
+ "Vault BNB balance is insufficient for four-buy.",
2830
+ "Deposit more BNB to vault or reduce --amount, then retry.",
2831
+ {
2832
+ vault,
2833
+ requiredBnb: (Number(amountMsgValue) / 1e18).toFixed(6),
2834
+ availableBnb: (Number(vaultBnbBalance) / 1e18).toFixed(6),
2835
+ requiredWei: amountMsgValue.toString(),
2836
+ availableWei: vaultBnbBalance.toString(),
2837
+ },
2838
+ );
2839
+ process.exit(1);
2840
+ }
2781
2841
 
2782
2842
  // Align to GWEI precision (Four.meme requirement)
2783
2843
  const minAmount = (estimatedAmount * BigInt(100 - slippage)) / 100n;
@@ -2818,11 +2878,23 @@ fourBuyCmd.action(async (opts) => {
2818
2878
  process.exit(0);
2819
2879
  }
2820
2880
 
2821
- const result = await client.execute(tokenId, action, true);
2822
- output({
2823
- status: "success",
2824
- hash: result.hash,
2825
- protocol: "four.meme",
2881
+ let result: { hash: Hex };
2882
+ try {
2883
+ result = await client.execute(tokenId, action, true);
2884
+ } catch (error: unknown) {
2885
+ const message = error instanceof Error ? error.message : "Unknown execution revert";
2886
+ outputError(
2887
+ "four-buy execution reverted on-chain.",
2888
+ "Likely market-side failure (amount too small, temporary pool state change, or token-side constraint).",
2889
+ { reason: message, token: tokenAddr, inputBnb: opts.amount },
2890
+ );
2891
+ process.exit(1);
2892
+ return;
2893
+ }
2894
+ output({
2895
+ status: "success",
2896
+ hash: result.hash,
2897
+ protocol: "four.meme",
2826
2898
  action: "buy",
2827
2899
  bnbSpent: opts.amount,
2828
2900
  estimatedTokens: (Number(estimatedAmount) / 1e18).toFixed(4),
package/src/mcp.ts CHANGED
@@ -1730,39 +1730,109 @@ server.tool(
1730
1730
  amount: z.string().describe("BNB amount to spend (human-readable, e.g. 0.01)"),
1731
1731
  slippage: z.number().default(10).describe("Slippage tolerance percent (default: 10, meme tokens are volatile)"),
1732
1732
  },
1733
- async ({ token_id, token, amount, slippage }) => {
1734
- const { publicClient, policyClient } = createClients();
1735
- const tokenId = BigInt(token_id);
1736
- const expiryCheck = await checkAgentExpiry(tokenId);
1737
- if (expiryCheck.blocked) return { content: expiryCheck.content! };
1738
-
1739
- const tokenAddr = token as Address;
1740
- const bnbAmount = parseAmount(amount, 18);
1741
-
1742
- // 1. Get token info
1743
- const info = await publicClient.readContract({
1744
- address: FOUR_MEME_HELPER_V3,
1745
- abi: FOUR_MEME_HELPER_ABI,
1733
+ async ({ token_id, token, amount, slippage }) => {
1734
+ const { publicClient, policyClient } = createClients();
1735
+ const tokenId = BigInt(token_id);
1736
+ const expiryCheck = await checkAgentExpiry(tokenId);
1737
+ if (expiryCheck.blocked) return { content: expiryCheck.content! };
1738
+
1739
+ const tokenAddr = token as Address;
1740
+ const bnbAmount = parseAmount(amount, 18);
1741
+ if (bnbAmount <= 0n) {
1742
+ return {
1743
+ content: [{
1744
+ type: "text" as const, text: JSON.stringify({
1745
+ status: "error",
1746
+ message: "Invalid amount. four_buy amount must be a positive BNB value.",
1747
+ })
1748
+ }],
1749
+ };
1750
+ }
1751
+ const vault = await policyClient.getVault(tokenId);
1752
+
1753
+ // 1. Get token info
1754
+ const info = await publicClient.readContract({
1755
+ address: FOUR_MEME_HELPER_V3,
1756
+ abi: FOUR_MEME_HELPER_ABI,
1746
1757
  functionName: "getTokenInfo",
1747
1758
  args: [tokenAddr],
1748
1759
  });
1749
- const [version, tokenManager, quote, , , , , , , , , liquidityAdded] = info;
1760
+ const [version, tokenManager, quote, , , minTradingFee, , , , , , liquidityAdded] = info;
1750
1761
 
1751
1762
  if (liquidityAdded) {
1752
1763
  return { content: [{ type: "text" as const, text: JSON.stringify({ status: "error", message: "Token has already migrated to DEX. Use the 'swap' tool instead." }) }] };
1753
1764
  }
1754
- if (quote !== "0x0000000000000000000000000000000000000000") {
1755
- return { content: [{ type: "text" as const, text: JSON.stringify({ status: "error", message: `Token uses BEP20 quote (${quote}), not BNB. BEP20 pairs not yet supported.` }) }] };
1756
- }
1765
+ if (quote !== "0x0000000000000000000000000000000000000000") {
1766
+ return { content: [{ type: "text" as const, text: JSON.stringify({ status: "error", message: `Token uses BEP20 quote (${quote}), not BNB. BEP20 pairs not yet supported.` }) }] };
1767
+ }
1768
+ if (bnbAmount < minTradingFee) {
1769
+ return {
1770
+ content: [{
1771
+ type: "text" as const, text: JSON.stringify({
1772
+ status: "error",
1773
+ message: "Input amount is below Four.meme minimum trading fee threshold.",
1774
+ next_step: "Increase amount and retry.",
1775
+ token: tokenAddr,
1776
+ inputWei: bnbAmount.toString(),
1777
+ minTradingFeeWei: minTradingFee.toString(),
1778
+ minTradingFeeBnb: (Number(minTradingFee) / 1e18).toFixed(6),
1779
+ })
1780
+ }],
1781
+ };
1782
+ }
1757
1783
 
1758
1784
  // 2. Pre-calculate
1759
- const tryBuyResult = await publicClient.readContract({
1760
- address: FOUR_MEME_HELPER_V3,
1761
- abi: FOUR_MEME_HELPER_ABI,
1762
- functionName: "tryBuy",
1763
- args: [tokenAddr, 0n, bnbAmount],
1764
- });
1765
- const [, , estimatedAmount, , estimatedFee, amountMsgValue] = tryBuyResult;
1785
+ const tryBuyResult = await publicClient.readContract({
1786
+ address: FOUR_MEME_HELPER_V3,
1787
+ abi: FOUR_MEME_HELPER_ABI,
1788
+ functionName: "tryBuy",
1789
+ args: [tokenAddr, 0n, bnbAmount],
1790
+ });
1791
+ const [, , estimatedAmount, , estimatedFee, amountMsgValue] = tryBuyResult;
1792
+ if (amountMsgValue < minTradingFee) {
1793
+ return {
1794
+ content: [{
1795
+ type: "text" as const, text: JSON.stringify({
1796
+ status: "error",
1797
+ message: "Computed payable amount is below Four.meme minimum trading fee threshold.",
1798
+ next_step: "Increase amount and retry.",
1799
+ token: tokenAddr,
1800
+ payableWei: amountMsgValue.toString(),
1801
+ minTradingFeeWei: minTradingFee.toString(),
1802
+ minTradingFeeBnb: (Number(minTradingFee) / 1e18).toFixed(6),
1803
+ })
1804
+ }],
1805
+ };
1806
+ }
1807
+ if (estimatedAmount <= 0n || amountMsgValue <= 0n) {
1808
+ return {
1809
+ content: [{
1810
+ type: "text" as const, text: JSON.stringify({
1811
+ status: "error",
1812
+ message: "four_buy quote returned zero output. Requested amount is likely too small for this market.",
1813
+ next_step: "Increase amount and retry.",
1814
+ token: tokenAddr,
1815
+ })
1816
+ }],
1817
+ };
1818
+ }
1819
+ const vaultBnbBalance = await publicClient.getBalance({ address: vault });
1820
+ if (amountMsgValue > vaultBnbBalance) {
1821
+ return {
1822
+ content: [{
1823
+ type: "text" as const, text: JSON.stringify({
1824
+ status: "error",
1825
+ message: "Vault BNB balance is insufficient for four_buy.",
1826
+ next_step: "Deposit more BNB to vault or reduce amount, then retry.",
1827
+ vault,
1828
+ requiredBnb: (Number(amountMsgValue) / 1e18).toFixed(6),
1829
+ availableBnb: (Number(vaultBnbBalance) / 1e18).toFixed(6),
1830
+ requiredWei: amountMsgValue.toString(),
1831
+ availableWei: vaultBnbBalance.toString(),
1832
+ })
1833
+ }],
1834
+ };
1835
+ }
1766
1836
 
1767
1837
  // Align to GWEI precision (Four.meme requirement)
1768
1838
  const minAmount = (estimatedAmount * BigInt(100 - slippage)) / 100n;
@@ -1781,11 +1851,26 @@ server.tool(
1781
1851
  const sim = await policyClient.validate(tokenId, action);
1782
1852
  if (!sim.ok) return { content: [{ type: "text" as const, text: JSON.stringify({ status: "rejected", reason: sim.reason, ...policyRejectionHelp(sim.reason, token_id) }) }] };
1783
1853
 
1784
- const result = await policyClient.execute(tokenId, action, true);
1785
- return {
1786
- content: [{
1787
- type: "text" as const, text: JSON.stringify({
1788
- status: "success", hash: result.hash, protocol: "four.meme", action: "buy",
1854
+ let result: { hash: Hex };
1855
+ try {
1856
+ result = await policyClient.execute(tokenId, action, true);
1857
+ } catch (error: unknown) {
1858
+ const message = error instanceof Error ? error.message : "Unknown execution revert";
1859
+ return {
1860
+ content: [{
1861
+ type: "text" as const, text: JSON.stringify({
1862
+ status: "error",
1863
+ message: "four_buy execution reverted on-chain",
1864
+ reason: message,
1865
+ hint: "This is usually market-side failure (amount too small, temporary pool state change, or token-side constraint), not a PolicyGuard rejection.",
1866
+ })
1867
+ }],
1868
+ };
1869
+ }
1870
+ return {
1871
+ content: [{
1872
+ type: "text" as const, text: JSON.stringify({
1873
+ status: "success", hash: result.hash, protocol: "four.meme", action: "buy",
1789
1874
  bnbSpent: amount,
1790
1875
  estimatedTokens: (Number(estimatedAmount) / 1e18).toFixed(4),
1791
1876
  fee: (Number(estimatedFee) / 1e18).toFixed(6),