@aibtc/mcp-server 1.73.0 → 1.73.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.
@@ -41,7 +41,8 @@ export declare class SbtcService {
41
41
  getBalance(address: string): Promise<SbtcBalance>;
42
42
  /**
43
43
  * Transfer sBTC to a recipient
44
- * @param fee Optional fee in micro-STX. If omitted, fee is auto-estimated.
44
+ * @param fee Optional fee in micro-STX. If omitted, the sbtc_transfer clamp
45
+ * is used rather than callContract's contract_call default.
45
46
  */
46
47
  transfer(account: Account, recipient: string, amount: bigint, memo?: string, fee?: bigint, sponsored?: boolean): Promise<TransferResult>;
47
48
  /**
@@ -4,6 +4,7 @@ import { getContracts, parseContractId } from "../config/index.js";
4
4
  import { callContract } from "../transactions/builder.js";
5
5
  import { sponsoredContractCall } from "../transactions/sponsor-builder.js";
6
6
  import { createFungiblePostCondition } from "../transactions/post-conditions.js";
7
+ import { resolveDefaultFee } from "../utils/fee.js";
7
8
  // ============================================================================
8
9
  // sBTC Service
9
10
  // ============================================================================
@@ -33,7 +34,8 @@ export class SbtcService {
33
34
  }
34
35
  /**
35
36
  * Transfer sBTC to a recipient
36
- * @param fee Optional fee in micro-STX. If omitted, fee is auto-estimated.
37
+ * @param fee Optional fee in micro-STX. If omitted, the sbtc_transfer clamp
38
+ * is used rather than callContract's contract_call default.
37
39
  */
38
40
  async transfer(account, recipient, amount, memo, fee, sponsored) {
39
41
  const sbtcContract = this.contracts.SBTC_TOKEN;
@@ -57,7 +59,10 @@ export class SbtcService {
57
59
  if (sponsored) {
58
60
  return sponsoredContractCall(account, contractCallOptions, this.network);
59
61
  }
60
- return callContract(account, contractCallOptions);
62
+ return callContract(account, {
63
+ ...contractCallOptions,
64
+ fee: fee ?? (await resolveDefaultFee(this.network, "sbtc_transfer")),
65
+ });
61
66
  }
62
67
  /**
63
68
  * Get sBTC deposit instructions
@@ -1,8 +1,9 @@
1
1
  import { uintCV, principalCV, noneCV, someCV, bufferCV } from "@stacks/transactions";
2
2
  import { getHiroApi } from "./hiro-api.js";
3
- import { parseContractId, getWellKnownTokens } from "../config/index.js";
3
+ import { parseContractId, getWellKnownTokens, getContracts } from "../config/index.js";
4
4
  import { callContract } from "../transactions/builder.js";
5
5
  import { createFungiblePostCondition } from "../transactions/post-conditions.js";
6
+ import { resolveDefaultFee } from "../utils/fee.js";
6
7
  // ============================================================================
7
8
  // Tokens Service (SIP-010)
8
9
  // ============================================================================
@@ -102,7 +103,13 @@ export class TokensService {
102
103
  functionName: "transfer",
103
104
  functionArgs,
104
105
  postConditions: [postCondition],
105
- ...(fee !== undefined && { fee }),
106
+ // A plain sBTC transfer gets the sbtc_transfer clamp, not the
107
+ // contract_call default that callContract would apply.
108
+ ...(fee !== undefined
109
+ ? { fee }
110
+ : contractId === getContracts(this.network).SBTC_TOKEN && {
111
+ fee: await resolveDefaultFee(this.network, "sbtc_transfer"),
112
+ }),
106
113
  });
107
114
  }
108
115
  /**
@@ -705,10 +705,10 @@ export async function createApiClient(baseUrl, options) {
705
705
  .willSendEq(amount)
706
706
  .ft(contracts.SBTC_TOKEN, "sbtc-token"),
707
707
  ],
708
- // Clamped medium fee — do NOT let @stacks auto-estimate, since
709
- // Hiro's contract_call high_priority tier is polluted by outliers
710
- // (observed >2000 STX). resolveDefaultFee caps it at 0.05 STX.
711
- fee: await resolveDefaultFee(acct.network, "contract_call"),
708
+ // Clamped fee — do NOT let @stacks auto-estimate, since Hiro's
709
+ // contract_call tiers are polluted by outliers (observed >2000
710
+ // STX). The sbtc_transfer clamp caps it at 0.003 STX.
711
+ fee: await resolveDefaultFee(acct.network, "sbtc_transfer"),
712
712
  });
713
713
  }
714
714
  else {
@@ -1062,7 +1062,7 @@ export async function checkSufficientBalance(account, amount, asset, sponsored =
1062
1062
  const stxBalanceForSbtc = BigInt(stxInfoForSbtc.balance);
1063
1063
  // Same clamped fee the interceptor actually sets on the sBTC contract
1064
1064
  // call — so this check is exact, not a high-priority over-estimate.
1065
- const estimatedSbtcFee = await resolveDefaultFee(account.network, "contract_call");
1065
+ const estimatedSbtcFee = await resolveDefaultFee(account.network, "sbtc_transfer");
1066
1066
  if (stxBalanceForSbtc < estimatedSbtcFee) {
1067
1067
  const stxShortfall = estimatedSbtcFee - stxBalanceForSbtc;
1068
1068
  throw new InsufficientBalanceError(`Insufficient STX balance to cover sBTC transfer fee: need ${formatStx(estimatedSbtcFee.toString())} estimated fee, ` +
@@ -18,7 +18,7 @@ const INBOX_BASE = "https://aibtc.com/api/inbox";
18
18
  * We budget against `medium_priority`, capped at this ceiling, so the pre-flight
19
19
  * reflects what the transaction will actually cost.
20
20
  */
21
- const MAX_REALISTIC_FEE_USTX = 50000n; // 0.05 STX — generous headroom
21
+ const MAX_REALISTIC_FEE_USTX = 3000n; // 0.003 STX — the sbtc_transfer fee ceiling
22
22
  /**
23
23
  * Pre-flight balance check for a non-sponsored sBTC inbox payment.
24
24
  * Verifies the wallet holds enough sBTC for the message AND enough STX for a
@@ -6,6 +6,44 @@
6
6
  * fee estimates from the Stacks mempool.
7
7
  */
8
8
  import type { Network } from "../config/networks.js";
9
+ /**
10
+ * Fee floor and ceiling clamps by transaction type (in micro-STX).
11
+ * Prevents absurd fees during mempool spikes while allowing reasonable variation.
12
+ *
13
+ * Ceilings are set conservatively:
14
+ * token_transfer: 3,000 uSTX (0.003 STX) — simple transfer
15
+ * contract_call: 50,000 uSTX (0.05 STX) — complex contract call
16
+ * smart_contract: 50,000 uSTX (0.05 STX) — deployment
17
+ * These match the x402-sponsor-relay reference implementation.
18
+ *
19
+ * sbtc_transfer is a plain sBTC `transfer` call. It is priced from Hiro's
20
+ * contract_call tier, whose medium estimate is skewed by heavy calls and sat
21
+ * at 2.5 STX, so every x402 sBTC payment paid the 0.05 STX ceiling. Mainnet
22
+ * sBTC transfers confirm at 500-3,000 uSTX.
23
+ */
24
+ declare const FEE_CLAMPS: {
25
+ readonly token_transfer: {
26
+ readonly floor: 180n;
27
+ readonly ceiling: 3000n;
28
+ };
29
+ readonly contract_call: {
30
+ readonly floor: 3000n;
31
+ readonly ceiling: 50000n;
32
+ };
33
+ readonly sbtc_transfer: {
34
+ readonly floor: 500n;
35
+ readonly ceiling: 3000n;
36
+ };
37
+ readonly smart_contract: {
38
+ readonly floor: 10000n;
39
+ readonly ceiling: 50000n;
40
+ };
41
+ readonly all: {
42
+ readonly floor: 180n;
43
+ readonly ceiling: 50000n;
44
+ };
45
+ };
46
+ type FeeTxType = keyof typeof FEE_CLAMPS;
9
47
  /**
10
48
  * Valid fee preset strings.
11
49
  * These map to the mempool fee priorities:
@@ -37,7 +75,7 @@ export declare function isFeePreset(value: string): value is FeePreset;
37
75
  * // Undefined - returns undefined (auto-estimate)
38
76
  * await resolveFee(undefined, "mainnet") // -> undefined
39
77
  */
40
- export declare function resolveFee(fee: string | undefined, network: Network, txType?: "all" | "token_transfer" | "contract_call" | "smart_contract"): Promise<bigint | undefined>;
78
+ export declare function resolveFee(fee: string | undefined, network: Network, txType?: FeeTxType): Promise<bigint | undefined>;
41
79
  /**
42
80
  * Resolve a default medium-priority fee for a given transaction type.
43
81
  *
@@ -52,5 +90,6 @@ export declare function resolveFee(fee: string | undefined, network: Network, tx
52
90
  * @param txType - The transaction type for ceiling/floor selection
53
91
  * @returns Fee in micro-STX as bigint
54
92
  */
55
- export declare function resolveDefaultFee(network: Network, txType?: "token_transfer" | "contract_call" | "smart_contract"): Promise<bigint>;
93
+ export declare function resolveDefaultFee(network: Network, txType?: Exclude<FeeTxType, "all">): Promise<bigint>;
94
+ export {};
56
95
  //# sourceMappingURL=fee.d.ts.map
package/dist/utils/fee.js CHANGED
@@ -15,13 +15,27 @@ import { getHiroApi } from "../services/hiro-api.js";
15
15
  * contract_call: 50,000 uSTX (0.05 STX) — complex contract call
16
16
  * smart_contract: 50,000 uSTX (0.05 STX) — deployment
17
17
  * These match the x402-sponsor-relay reference implementation.
18
+ *
19
+ * sbtc_transfer is a plain sBTC `transfer` call. It is priced from Hiro's
20
+ * contract_call tier, whose medium estimate is skewed by heavy calls and sat
21
+ * at 2.5 STX, so every x402 sBTC payment paid the 0.05 STX ceiling. Mainnet
22
+ * sBTC transfers confirm at 500-3,000 uSTX.
18
23
  */
19
24
  const FEE_CLAMPS = {
20
25
  token_transfer: { floor: 180n, ceiling: 3000n },
21
26
  contract_call: { floor: 3000n, ceiling: 50000n },
27
+ sbtc_transfer: { floor: 500n, ceiling: 3000n },
22
28
  smart_contract: { floor: 10000n, ceiling: 50000n },
23
29
  all: { floor: 180n, ceiling: 50000n }, // Widest range for aggregate fees
24
30
  };
31
+ /** Hiro mempool fee tier each clamp type is estimated from. */
32
+ const FEE_TIER = {
33
+ token_transfer: "token_transfer",
34
+ contract_call: "contract_call",
35
+ sbtc_transfer: "contract_call",
36
+ smart_contract: "smart_contract",
37
+ all: "all",
38
+ };
25
39
  /**
26
40
  * Check if a string is a valid fee preset.
27
41
  */
@@ -77,7 +91,7 @@ export async function resolveFee(fee, network, txType = "all") {
77
91
  const hiroApi = getHiroApi(network);
78
92
  try {
79
93
  const mempoolFees = await hiroApi.getMempoolFees();
80
- const feeTier = mempoolFees[txType];
94
+ const feeTier = mempoolFees[FEE_TIER[txType]];
81
95
  const priorityKey = presetToPriorityKey(fee);
82
96
  const rawFee = BigInt(Math.ceil(feeTier[priorityKey]));
83
97
  const clamps = FEE_CLAMPS[txType];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aibtc/mcp-server",
3
- "version": "1.73.0",
3
+ "version": "1.73.1",
4
4
  "description": "Bitcoin-native MCP server for AI agents: BTC/STX wallets, DeFi yield, sBTC peg, NFTs, and x402 payments.",
5
5
  "mcpName": "io.github.aibtcdev/mcp-server",
6
6
  "type": "module",
package/skill/SKILL.md CHANGED
@@ -4,7 +4,7 @@ description: Bitcoin L1 wallet for agents - check balances, send BTC, manage UTX
4
4
  license: MIT
5
5
  metadata:
6
6
  author: aibtcdev
7
- version: 1.73.0 # x-release-please-version
7
+ version: 1.73.1 # x-release-please-version
8
8
  npm: "@aibtc/mcp-server"
9
9
  github: https://github.com/aibtcdev/aibtc-mcp-server
10
10
  ---