@aibtc/mcp-server 1.73.1 → 1.75.0
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/services/x402.service.d.ts +6 -0
- package/dist/services/x402.service.js +25 -3
- package/dist/tools/inbox-x402.tools.d.ts +13 -19
- package/dist/tools/inbox-x402.tools.js +42 -32
- package/dist/tools/inbox.tools.js +7 -11
- package/package.json +2 -2
- package/skill/SKILL.md +4 -3
- package/skill/references/x402-inbox.md +14 -9
|
@@ -126,6 +126,12 @@ export type PaymentTokenType = 'STX' | 'sBTC' | 'unsupported';
|
|
|
126
126
|
* @param asset - Full contract identifier or token name
|
|
127
127
|
*/
|
|
128
128
|
export declare function detectTokenType(asset: string): PaymentTokenType;
|
|
129
|
+
/**
|
|
130
|
+
* Memo for an x402 payment from the server's `extra.payment_id`, cut to the
|
|
131
|
+
* first 34 UTF-8 bytes (the Stacks memo limit) on a character boundary.
|
|
132
|
+
* Gateways that bind payments on the memo match that prefix.
|
|
133
|
+
*/
|
|
134
|
+
export declare function paymentIdMemo(extra?: Record<string, unknown>): string | undefined;
|
|
129
135
|
/**
|
|
130
136
|
* Best-effort display symbol for an asset. Servers advertise a friendly name in
|
|
131
137
|
* `extra.tokenType` (arc0btc sends "USDCx"); otherwise fall back to the contract
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import axios from "axios";
|
|
2
|
-
import { makeSTXTokenTransfer, makeContractCall, uintCV, principalCV, noneCV, Pc, PostConditionMode, } from "@stacks/transactions";
|
|
2
|
+
import { makeSTXTokenTransfer, makeContractCall, uintCV, principalCV, noneCV, someCV, bufferCVFromString, Pc, PostConditionMode, } from "@stacks/transactions";
|
|
3
3
|
import { decodePaymentRequired, encodePaymentPayload, generatePaymentId, buildPaymentIdentifierExtension, X402_HEADERS, } from "../utils/x402-protocol.js";
|
|
4
4
|
import { generateWallet, getStxAddress } from "@stacks/wallet-sdk";
|
|
5
5
|
import { NETWORK, API_URL, getStacksNetwork } from "../config/networks.js";
|
|
@@ -681,6 +681,7 @@ export async function createApiClient(baseUrl, options) {
|
|
|
681
681
|
// — this is the same tx shape the x402-stacks lib produces, and the
|
|
682
682
|
// balance check in onBeforePayment already accounts for the gas.
|
|
683
683
|
const networkName = getStacksNetwork(acct.network);
|
|
684
|
+
const memo = paymentIdMemo(selectedOption.extra);
|
|
684
685
|
let transaction;
|
|
685
686
|
if (tokenType === "sBTC") {
|
|
686
687
|
const contracts = getContracts(acct.network);
|
|
@@ -693,7 +694,7 @@ export async function createApiClient(baseUrl, options) {
|
|
|
693
694
|
uintCV(amount),
|
|
694
695
|
principalCV(acct.address),
|
|
695
696
|
principalCV(selectedOption.payTo),
|
|
696
|
-
noneCV(),
|
|
697
|
+
memo ? someCV(bufferCVFromString(memo)) : noneCV(),
|
|
697
698
|
],
|
|
698
699
|
senderKey: acct.privateKey,
|
|
699
700
|
network: networkName,
|
|
@@ -717,7 +718,7 @@ export async function createApiClient(baseUrl, options) {
|
|
|
717
718
|
amount,
|
|
718
719
|
senderKey: acct.privateKey,
|
|
719
720
|
network: networkName,
|
|
720
|
-
memo: "",
|
|
721
|
+
memo: memo ?? "",
|
|
721
722
|
fee: await resolveDefaultFee(acct.network, "token_transfer"),
|
|
722
723
|
});
|
|
723
724
|
}
|
|
@@ -813,6 +814,27 @@ export function detectTokenType(asset) {
|
|
|
813
814
|
}
|
|
814
815
|
return 'unsupported';
|
|
815
816
|
}
|
|
817
|
+
const STACKS_MEMO_MAX_BYTES = 34;
|
|
818
|
+
/**
|
|
819
|
+
* Memo for an x402 payment from the server's `extra.payment_id`, cut to the
|
|
820
|
+
* first 34 UTF-8 bytes (the Stacks memo limit) on a character boundary.
|
|
821
|
+
* Gateways that bind payments on the memo match that prefix.
|
|
822
|
+
*/
|
|
823
|
+
export function paymentIdMemo(extra) {
|
|
824
|
+
const id = extra?.payment_id;
|
|
825
|
+
if (typeof id !== "string" || id.length === 0)
|
|
826
|
+
return undefined;
|
|
827
|
+
let memo = "";
|
|
828
|
+
let bytes = 0;
|
|
829
|
+
for (const ch of id) {
|
|
830
|
+
const size = Buffer.byteLength(ch, "utf8");
|
|
831
|
+
if (bytes + size > STACKS_MEMO_MAX_BYTES)
|
|
832
|
+
break;
|
|
833
|
+
memo += ch;
|
|
834
|
+
bytes += size;
|
|
835
|
+
}
|
|
836
|
+
return memo;
|
|
837
|
+
}
|
|
816
838
|
/**
|
|
817
839
|
* Best-effort display symbol for an asset. Servers advertise a friendly name in
|
|
818
840
|
* `extra.tokenType` (arc0btc sends "USDCx"); otherwise fall back to the contract
|
|
@@ -1,29 +1,23 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/server";
|
|
2
2
|
/**
|
|
3
|
-
* Pre-flight balance check for
|
|
4
|
-
* Verifies the wallet holds enough sBTC for the message
|
|
5
|
-
*
|
|
3
|
+
* Pre-flight balance check for an sBTC inbox payment.
|
|
4
|
+
* Verifies the wallet holds enough sBTC for the message, and — unless the
|
|
5
|
+
* payment is sponsored — enough STX for a realistic transfer fee.
|
|
6
6
|
*/
|
|
7
|
-
export declare function checkDirectInboxBalance(address: string, amount: string): Promise<void>;
|
|
7
|
+
export declare function checkDirectInboxBalance(address: string, amount: string, sponsored?: boolean): Promise<void>;
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
9
|
+
* x402 inbox messaging via the `x402-stacks` client interceptor
|
|
10
|
+
* (`wrapAxiosWithPayment`). The transaction is signed but not broadcast; the
|
|
11
|
+
* inbox endpoint settles it through its x402 facilitator.
|
|
10
12
|
*
|
|
11
|
-
*
|
|
12
|
-
* (
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* where the *sender pays its own STX gas*. The transaction is signed but not
|
|
16
|
-
* broadcast; the inbox endpoint settles it via the x402 facilitator's
|
|
17
|
-
* `/settle` flow (no relay sponsorship).
|
|
13
|
+
* The inbox decides who pays gas. When its 402 advertises `extra.feePayer`,
|
|
14
|
+
* x402-stacks (>=2.1.0) signs a sponsored sBTC transfer with fee 0 and the
|
|
15
|
+
* facilitator co-signs and pays the STX gas. Without it, the SDK signs a
|
|
16
|
+
* standard transfer and the sender pays its own gas.
|
|
18
17
|
*
|
|
19
18
|
* Server side: `lib/inbox/x402-verify.ts` deserializes the payment tx and
|
|
20
|
-
* branches on `tx.auth.authType
|
|
21
|
-
*
|
|
22
|
-
* adds a sponsor signature or pays gas.
|
|
23
|
-
*
|
|
24
|
-
* Trade-off vs the sponsored tool: the sender must hold STX to cover the
|
|
25
|
-
* transfer fee (the pre-flight check enforces this). In exchange there is no
|
|
26
|
-
* relay in the middle of the payment authorization.
|
|
19
|
+
* branches on `tx.auth.authType` (sponsored → relay sponsors; standard →
|
|
20
|
+
* relay `/settle` broadcasts as-is).
|
|
27
21
|
*/
|
|
28
22
|
export declare function registerInboxX402Tools(server: McpServer): void;
|
|
29
23
|
//# sourceMappingURL=inbox-x402.tools.d.ts.map
|
|
@@ -2,6 +2,7 @@ import { z } from "zod";
|
|
|
2
2
|
import axios, { AxiosError } from "axios";
|
|
3
3
|
import { wrapAxiosWithPayment, decodePaymentRequired, decodePaymentResponse, X402_HEADERS, } from "x402-stacks";
|
|
4
4
|
import { getAccount, NETWORK } from "../services/x402.service.js";
|
|
5
|
+
import { getSpendLimiter } from "../services/spend-limiter.js";
|
|
5
6
|
import { getSbtcService } from "../services/sbtc.service.js";
|
|
6
7
|
import { getHiroApi } from "../services/hiro-api.js";
|
|
7
8
|
import { getExplorerTxUrl } from "../config/networks.js";
|
|
@@ -20,11 +21,11 @@ const INBOX_BASE = "https://aibtc.com/api/inbox";
|
|
|
20
21
|
*/
|
|
21
22
|
const MAX_REALISTIC_FEE_USTX = 3000n; // 0.003 STX — the sbtc_transfer fee ceiling
|
|
22
23
|
/**
|
|
23
|
-
* Pre-flight balance check for
|
|
24
|
-
* Verifies the wallet holds enough sBTC for the message
|
|
25
|
-
*
|
|
24
|
+
* Pre-flight balance check for an sBTC inbox payment.
|
|
25
|
+
* Verifies the wallet holds enough sBTC for the message, and — unless the
|
|
26
|
+
* payment is sponsored — enough STX for a realistic transfer fee.
|
|
26
27
|
*/
|
|
27
|
-
export async function checkDirectInboxBalance(address, amount) {
|
|
28
|
+
export async function checkDirectInboxBalance(address, amount, sponsored = false) {
|
|
28
29
|
const sbtcService = getSbtcService(NETWORK);
|
|
29
30
|
const sbtcBalance = BigInt((await sbtcService.getBalance(address)).balance);
|
|
30
31
|
const required = BigInt(amount);
|
|
@@ -33,6 +34,9 @@ export async function checkDirectInboxBalance(address, amount) {
|
|
|
33
34
|
throw new InsufficientBalanceError(`Insufficient sBTC balance: need ${formatSbtc(amount)}, have ${formatSbtc(sbtcBalance.toString())} (shortfall: ${formatSbtc(shortfall.toString())}). ` +
|
|
34
35
|
`Deposit more sBTC via the bridge at https://bridge.stx.eco or use a different wallet.`, "sBTC", sbtcBalance.toString(), amount, shortfall.toString());
|
|
35
36
|
}
|
|
37
|
+
// Sponsored: the facilitator pays the gas, so no STX is needed.
|
|
38
|
+
if (sponsored)
|
|
39
|
+
return;
|
|
36
40
|
const hiro = getHiroApi(NETWORK);
|
|
37
41
|
const stxBalance = BigInt((await hiro.getStxBalance(address)).balance);
|
|
38
42
|
const fees = await hiro.getMempoolFees();
|
|
@@ -53,33 +57,28 @@ export async function checkDirectInboxBalance(address, amount) {
|
|
|
53
57
|
}
|
|
54
58
|
}
|
|
55
59
|
/**
|
|
56
|
-
*
|
|
60
|
+
* x402 inbox messaging via the `x402-stacks` client interceptor
|
|
61
|
+
* (`wrapAxiosWithPayment`). The transaction is signed but not broadcast; the
|
|
62
|
+
* inbox endpoint settles it through its x402 facilitator.
|
|
57
63
|
*
|
|
58
|
-
*
|
|
59
|
-
* (
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
* where the *sender pays its own STX gas*. The transaction is signed but not
|
|
63
|
-
* broadcast; the inbox endpoint settles it via the x402 facilitator's
|
|
64
|
-
* `/settle` flow (no relay sponsorship).
|
|
64
|
+
* The inbox decides who pays gas. When its 402 advertises `extra.feePayer`,
|
|
65
|
+
* x402-stacks (>=2.1.0) signs a sponsored sBTC transfer with fee 0 and the
|
|
66
|
+
* facilitator co-signs and pays the STX gas. Without it, the SDK signs a
|
|
67
|
+
* standard transfer and the sender pays its own gas.
|
|
65
68
|
*
|
|
66
69
|
* Server side: `lib/inbox/x402-verify.ts` deserializes the payment tx and
|
|
67
|
-
* branches on `tx.auth.authType
|
|
68
|
-
*
|
|
69
|
-
* adds a sponsor signature or pays gas.
|
|
70
|
-
*
|
|
71
|
-
* Trade-off vs the sponsored tool: the sender must hold STX to cover the
|
|
72
|
-
* transfer fee (the pre-flight check enforces this). In exchange there is no
|
|
73
|
-
* relay in the middle of the payment authorization.
|
|
70
|
+
* branches on `tx.auth.authType` (sponsored → relay sponsors; standard →
|
|
71
|
+
* relay `/settle` broadcasts as-is).
|
|
74
72
|
*/
|
|
75
73
|
export function registerInboxX402Tools(server) {
|
|
76
74
|
server.registerTool("send_inbox_message_direct", {
|
|
77
75
|
description: "Send a paid x402 message to another agent's inbox on aibtc.com. This is the canonical inbox " +
|
|
78
76
|
"send tool (the older sponsored send_inbox_message is deprecated).\n\n" +
|
|
79
|
-
"It signs
|
|
80
|
-
"
|
|
81
|
-
"the
|
|
82
|
-
"Requires an unlocked wallet holding sBTC (message cost)
|
|
77
|
+
"It signs an sBTC transfer with the x402-stacks client interceptor and the inbox settles it via its " +
|
|
78
|
+
"x402 facilitator. When the inbox offers sponsorship (402 `extra.feePayer`), the transfer is gasless — " +
|
|
79
|
+
"you pay only the sBTC message cost. Otherwise you also pay your own STX gas fee.\n\n" +
|
|
80
|
+
"Requires an unlocked wallet holding sBTC (message cost), plus STX for gas when the inbox does not " +
|
|
81
|
+
"sponsor. Mainnet only.",
|
|
83
82
|
inputSchema: z.object({
|
|
84
83
|
recipientBtcAddress: z
|
|
85
84
|
.string()
|
|
@@ -135,13 +134,19 @@ export function registerInboxX402Tools(server) {
|
|
|
135
134
|
throw new Error("402 response missing or empty payment-required header");
|
|
136
135
|
}
|
|
137
136
|
const accept = paymentRequired.accepts[0];
|
|
138
|
-
//
|
|
139
|
-
//
|
|
140
|
-
|
|
137
|
+
// The SDK signs a sponsored (fee 0) transfer exactly when the
|
|
138
|
+
// requirement names a fee payer — mirror its check.
|
|
139
|
+
const sponsored = typeof accept.extra?.feePayer === "string";
|
|
140
|
+
// Step 2: Pre-flight balance check — sBTC for the message, plus a
|
|
141
|
+
// realistic STX gas budget when the sender pays its own fee.
|
|
142
|
+
await checkDirectInboxBalance(account.address, accept.amount, sponsored);
|
|
143
|
+
// The message cost is a signed sBTC spend: meter it before signing.
|
|
144
|
+
const spendLimiter = getSpendLimiter();
|
|
145
|
+
await spendLimiter.check("sats", BigInt(accept.amount), account.address);
|
|
141
146
|
// Step 3: Build a payment-enabled axios client. wrapAxiosWithPayment
|
|
142
|
-
// installs the x402-stacks interceptor: on a 402 it signs
|
|
143
|
-
// (
|
|
144
|
-
//
|
|
147
|
+
// installs the x402-stacks interceptor: on a 402 it signs the sBTC
|
|
148
|
+
// transfer (sponsored or standard, per `extra.feePayer`) and retries
|
|
149
|
+
// with the payment-signature header.
|
|
145
150
|
const stacksAccount = {
|
|
146
151
|
address: account.address,
|
|
147
152
|
privateKey: account.privateKey,
|
|
@@ -152,13 +157,16 @@ export function registerInboxX402Tools(server) {
|
|
|
152
157
|
const response = await api.post(inboxUrl, body, {
|
|
153
158
|
headers: { "Content-Type": "application/json" },
|
|
154
159
|
});
|
|
160
|
+
await spendLimiter.record("sats", BigInt(accept.amount), account.address);
|
|
155
161
|
// Step 5: Decode the settlement (txid + payer) from the response header.
|
|
156
162
|
const settlement = decodePaymentResponse(response.headers[X402_HEADERS.PAYMENT_RESPONSE]);
|
|
157
163
|
const txid = settlement?.transaction;
|
|
158
164
|
const payer = settlement?.payer;
|
|
159
165
|
return createJsonResponse({
|
|
160
166
|
success: true,
|
|
161
|
-
message:
|
|
167
|
+
message: sponsored
|
|
168
|
+
? "Message delivered (x402, gas sponsored)"
|
|
169
|
+
: "Message delivered (x402, sender paid gas)",
|
|
162
170
|
recipient: {
|
|
163
171
|
btcAddress: recipientBtcAddress,
|
|
164
172
|
stxAddress: recipientStxAddress,
|
|
@@ -166,14 +174,16 @@ export function registerInboxX402Tools(server) {
|
|
|
166
174
|
contentLength: content.length,
|
|
167
175
|
inbox: response.data,
|
|
168
176
|
payment: {
|
|
169
|
-
mode: "direct-x402-nonsponsored",
|
|
177
|
+
mode: sponsored ? "x402-sponsored" : "direct-x402-nonsponsored",
|
|
170
178
|
amount: accept.amount + " sats sBTC",
|
|
171
179
|
...(payer && { payer }),
|
|
172
180
|
...(txid && {
|
|
173
181
|
txid,
|
|
174
182
|
explorer: getExplorerTxUrl(txid, NETWORK),
|
|
175
183
|
}),
|
|
176
|
-
note:
|
|
184
|
+
note: sponsored
|
|
185
|
+
? `STX gas paid by the inbox facilitator (${accept.extra?.feePayer}).`
|
|
186
|
+
: "Sender paid its own STX gas — no relay sponsorship.",
|
|
177
187
|
},
|
|
178
188
|
});
|
|
179
189
|
}
|
|
@@ -7,9 +7,9 @@ import { createJsonResponse } from "../utils/index.js";
|
|
|
7
7
|
// was unstable: a burst of sends with an uninitialized local nonce could wedge
|
|
8
8
|
// the relay's sponsor queue (nonces stuck `held(gap)`), leaving payments
|
|
9
9
|
// accepted but messages undelivered (issue #540/#592). All inbox sends now go
|
|
10
|
-
// through send_inbox_message_direct, which
|
|
11
|
-
// transfer
|
|
12
|
-
//
|
|
10
|
+
// through send_inbox_message_direct, which is gasless: x402-stacks signs a
|
|
11
|
+
// sponsored transfer when the inbox advertises a fee payer, and the relay
|
|
12
|
+
// sponsors it in one step (aibtcdev/x402-sponsor-relay#436).
|
|
13
13
|
//
|
|
14
14
|
// This tool is retained only as a redirect so existing agents that still call
|
|
15
15
|
// send_inbox_message get pointed to the working tool instead of an
|
|
@@ -17,10 +17,8 @@ import { createJsonResponse } from "../utils/index.js";
|
|
|
17
17
|
// ============================================================================
|
|
18
18
|
export function registerInboxTools(server) {
|
|
19
19
|
server.registerTool("send_inbox_message", {
|
|
20
|
-
description: "⛔ DEPRECATED — do not use.
|
|
21
|
-
"
|
|
22
|
-
"through the x402 facilitator (no relay). Requires an unlocked wallet holding sBTC (message cost) " +
|
|
23
|
-
"and STX (gas). Mainnet only.",
|
|
20
|
+
description: "⛔ DEPRECATED — do not use. Use send_inbox_message_direct instead: it is gasless (the relay " +
|
|
21
|
+
"pays the STX gas), so you need only the sBTC message cost in an unlocked wallet. Mainnet only.",
|
|
24
22
|
inputSchema: z.object({
|
|
25
23
|
recipientBtcAddress: z
|
|
26
24
|
.string()
|
|
@@ -37,12 +35,10 @@ export function registerInboxTools(server) {
|
|
|
37
35
|
return createJsonResponse({
|
|
38
36
|
success: false,
|
|
39
37
|
deprecated: true,
|
|
40
|
-
error: "send_inbox_message has been removed.
|
|
41
|
-
"(payments could be accepted but never delivered when the relay's sponsor nonce " +
|
|
42
|
-
"queue wedged).",
|
|
38
|
+
error: "send_inbox_message has been removed.",
|
|
43
39
|
useInstead: "send_inbox_message_direct",
|
|
44
40
|
note: "Call send_inbox_message_direct with the same recipientBtcAddress, recipientStxAddress, " +
|
|
45
|
-
"and content. It
|
|
41
|
+
"and content. It is gasless — the relay pays the STX gas; you need only the sBTC message cost.",
|
|
46
42
|
});
|
|
47
43
|
});
|
|
48
44
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aibtc/mcp-server",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.75.0",
|
|
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",
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"sbtc": "^0.3.2",
|
|
75
75
|
"tslib": "^2.8.1",
|
|
76
76
|
"ws": "^8.21.0",
|
|
77
|
-
"x402-stacks": "^2.0
|
|
77
|
+
"x402-stacks": "^2.1.0",
|
|
78
78
|
"zod": "^4.6.5"
|
|
79
79
|
},
|
|
80
80
|
"devDependencies": {
|
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.
|
|
7
|
+
version: 1.75.0 # x-release-please-version
|
|
8
8
|
npm: "@aibtc/mcp-server"
|
|
9
9
|
github: https://github.com/aibtcdev/aibtc-mcp-server
|
|
10
10
|
---
|
|
@@ -206,8 +206,9 @@ Always probe before executing paid endpoints. Never call `execute_x402_endpoint`
|
|
|
206
206
|
|
|
207
207
|
**send_inbox_message_direct** — dedicated tool for aibtc.com inbox messages:
|
|
208
208
|
- Parameters: `recipientBtcAddress` (bc1...), `recipientStxAddress` (SP...), `content` (max 500 chars)
|
|
209
|
-
-
|
|
210
|
-
-
|
|
209
|
+
- Gasless: the inbox advertises a fee payer, so the tool signs a sponsored sBTC transfer (fee 0) and the relay pays the STX gas
|
|
210
|
+
- Requires an unlocked wallet holding the sBTC message cost (100 sats) — no STX needed (mainnet only)
|
|
211
|
+
- One pending payment per sender: wait for the previous message's payment to confirm before sending the next
|
|
211
212
|
- Implements the full x402 v2 payment flow with a balance pre-check
|
|
212
213
|
- Note: the older sponsored `send_inbox_message` tool is deprecated and no longer sends — use this tool instead
|
|
213
214
|
|
|
@@ -115,15 +115,20 @@ send_inbox_message_direct({
|
|
|
115
115
|
})
|
|
116
116
|
```
|
|
117
117
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
pays
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
118
|
+
Inbox sends are **gasless**. The inbox's 402 advertises `extra.feePayer` (the relay's sponsor
|
|
119
|
+
address), so the x402-stacks interceptor signs a sponsored sBTC transfer with fee 0 and the relay
|
|
120
|
+
pays the STX gas. The wallet must be unlocked and hold the sBTC message cost (100 sats) — no STX
|
|
121
|
+
needed. Mainnet only. The tool runs a balance pre-check before signing. If the inbox ever stops
|
|
122
|
+
advertising `feePayer`, the same tool signs a standard transfer and the sender pays its own gas.
|
|
123
|
+
|
|
124
|
+
Sponsored sends follow a few rules (the inbox returns a clear error otherwise):
|
|
125
|
+
- **One pending payment per sender** — wait for the previous inbox payment to confirm (seconds)
|
|
126
|
+
before sending the next; a second one while it's pending gets `SENDER_NONCE_DUPLICATE` (409).
|
|
127
|
+
- **Up to 10 sponsored payments per sender per minute** (`RATE_LIMITED`, 429).
|
|
128
|
+
- **No messages to yourself** — a transfer to your own address fails on-chain, so it's refused.
|
|
129
|
+
|
|
130
|
+
> **Deprecated:** the older `send_inbox_message` tool no longer sends. It returns a message
|
|
131
|
+
> directing callers to `send_inbox_message_direct`, which is now the gasless path.
|
|
127
132
|
|
|
128
133
|
## More Information
|
|
129
134
|
|