This is page 2 of 2. Use http://codebase.md/b-open-io/bsv-mcp?page={x} to view the full context.
# Directory Structure
```
├── .gitignore
├── biome.json
├── bun.lock
├── CHANGELOG.md
├── Dockerfile
├── docs
│ ├── a2b.md
│ └── images
│ └── mcp-config-example.png
├── index.ts
├── LICENSE
├── package.json
├── prompts
│ ├── bsvSdk
│ │ ├── auth.ts
│ │ ├── cryptography.ts
│ │ ├── index.ts
│ │ ├── overview.ts
│ │ ├── primitives.ts
│ │ ├── script.ts
│ │ ├── transaction.ts
│ │ └── wallet.ts
│ ├── index.ts
│ └── ordinals.ts
├── README.md
├── resources
│ ├── brcs.ts
│ ├── changelog.ts
│ ├── junglebus.ts
│ └── resources.ts
├── smithery.yaml
├── tools
│ ├── a2b
│ │ ├── call.ts
│ │ └── discover.ts
│ ├── bsv
│ │ ├── decodeTransaction.ts
│ │ ├── explore.ts
│ │ ├── getPrice.ts
│ │ ├── index.ts
│ │ └── token.ts
│ ├── constants.ts
│ ├── index.ts
│ ├── mnee
│ │ ├── getBalance.ts
│ │ ├── index.ts
│ │ ├── parseTx.ts
│ │ └── sendMnee.ts
│ ├── ordinals
│ │ ├── getInscription.ts
│ │ ├── getTokenByIdOrTicker.ts
│ │ ├── index.ts
│ │ ├── marketListings.ts
│ │ ├── marketSales.ts
│ │ └── searchInscriptions.ts
│ ├── utils
│ │ ├── conversion.ts
│ │ └── index.ts
│ └── wallet
│ ├── a2bPublishAgent.ts
│ ├── a2bPublishMcp.ts
│ ├── createOrdinals.ts
│ ├── fetchPaymentUtxos.ts
│ ├── getAddress.ts
│ ├── getPublicKey.ts
│ ├── purchaseListing.ts
│ ├── refreshUtxos.ts
│ ├── schemas.ts
│ ├── sendOrdinals.ts
│ ├── sendToAddress.ts
│ ├── tools.ts
│ ├── transferOrdToken.ts
│ └── wallet.ts
└── tsconfig.json
```
# Files
--------------------------------------------------------------------------------
/tools/wallet/tools.ts:
--------------------------------------------------------------------------------
```typescript
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import type { ToolCallback } from "@modelcontextprotocol/sdk/server/mcp.js";
import type { RequestHandlerExtra } from "@modelcontextprotocol/sdk/shared/protocol.js";
import type {
CallToolResult,
ServerNotification,
ServerRequest,
} from "@modelcontextprotocol/sdk/types.js";
import type { z } from "zod";
import type {
abortActionArgsSchema,
acquireCertificateArgsSchema,
createHmacArgsSchema,
discoverByAttributesArgsSchema,
discoverByIdentityKeyArgsSchema,
getHeaderArgsSchema,
internalizeActionArgsSchema,
listActionsArgsSchema,
listCertificatesArgsSchema,
listOutputsArgsSchema,
proveCertificateArgsSchema,
relinquishCertificateArgsSchema,
relinquishOutputArgsSchema,
revealCounterpartyKeyLinkageArgsSchema,
revealSpecificKeyLinkageArgsSchema,
verifyHmacArgsSchema,
} from "./schemas";
import type { Wallet } from "./wallet";
import {
createSignatureArgsSchema,
type emptyArgsSchema,
type getAddressArgsSchema,
type getPublicKeyArgsSchema,
type purchaseListingArgsSchema,
type sendToAddressArgsSchema,
verifySignatureArgsSchema,
walletEncryptionArgsSchema,
} from "./schemas";
import { Utils, type WalletProtocol } from "@bsv/sdk";
import { registerCreateOrdinalsTool } from "./createOrdinals";
import type { createOrdinalsArgsSchema } from "./createOrdinals";
import { registerGetAddressTool } from "./getAddress";
import { registerGetPublicKeyTool } from "./getPublicKey";
import { registerPurchaseListingTool } from "./purchaseListing";
import { registerRefreshUtxosTool } from "./refreshUtxos";
import { registerSendToAddressTool } from "./sendToAddress";
import { registerTransferOrdTokenTool } from "./transferOrdToken";
import type { a2bPublishArgsSchema } from "./a2bPublishAgent";
import { registerA2bPublishMcpTool } from "./a2bPublishMcp";
import type { transferOrdTokenArgsSchema } from "./transferOrdToken";
// Define mapping from tool names to argument schemas
type ToolArgSchemas = {
wallet_getPublicKey: typeof getPublicKeyArgsSchema;
wallet_createSignature: typeof createSignatureArgsSchema;
wallet_verifySignature: typeof verifySignatureArgsSchema;
wallet_encryption: typeof walletEncryptionArgsSchema;
wallet_listActions: typeof listActionsArgsSchema;
wallet_listOutputs: typeof listOutputsArgsSchema;
wallet_getNetwork: typeof emptyArgsSchema;
wallet_getVersion: typeof emptyArgsSchema;
wallet_revealCounterpartyKeyLinkage: typeof revealCounterpartyKeyLinkageArgsSchema;
wallet_revealSpecificKeyLinkage: typeof revealSpecificKeyLinkageArgsSchema;
wallet_createHmac: typeof createHmacArgsSchema;
wallet_verifyHmac: typeof verifyHmacArgsSchema;
wallet_abortAction: typeof abortActionArgsSchema;
wallet_internalizeAction: typeof internalizeActionArgsSchema;
wallet_relinquishOutput: typeof relinquishOutputArgsSchema;
wallet_acquireCertificate: typeof acquireCertificateArgsSchema;
wallet_listCertificates: typeof listCertificatesArgsSchema;
wallet_proveCertificate: typeof proveCertificateArgsSchema;
wallet_relinquishCertificate: typeof relinquishCertificateArgsSchema;
wallet_discoverByIdentityKey: typeof discoverByIdentityKeyArgsSchema;
wallet_discoverByAttributes: typeof discoverByAttributesArgsSchema;
wallet_isAuthenticated: typeof emptyArgsSchema;
wallet_waitForAuthentication: typeof emptyArgsSchema;
wallet_getHeaderForHeight: typeof getHeaderArgsSchema;
wallet_getAddress: typeof getAddressArgsSchema;
wallet_sendToAddress: typeof sendToAddressArgsSchema;
wallet_purchaseListing: typeof purchaseListingArgsSchema;
wallet_transferOrdToken: typeof transferOrdTokenArgsSchema;
wallet_a2bPublish: typeof a2bPublishArgsSchema;
wallet_createOrdinals: typeof createOrdinalsArgsSchema;
wallet_refreshUtxos: typeof emptyArgsSchema;
};
// Define a type for the handler function with proper argument types
type ToolHandler = (
params: { args: unknown },
extra: RequestHandlerExtra<ServerRequest, ServerNotification>,
) => Promise<CallToolResult>;
// Define a map type for tool name to handler functions
type ToolHandlerMap = {
[K in keyof ToolArgSchemas]: ToolHandler;
};
export function registerWalletTools(
server: McpServer,
wallet: Wallet,
config: {
disableBroadcasting: boolean;
enableA2bTools: boolean;
},
): ToolHandlerMap {
const handlers = {} as ToolHandlerMap;
// Handle tools registration with properly typed parameters
function registerTool<T extends z.ZodType>(
name: keyof ToolArgSchemas,
description: string,
schema: { args: T },
handler: ToolCallback<{ args: T }>,
): void {
// Register all tools normally
server.tool(name, description, schema, handler);
handlers[name] = handler as ToolHandler;
}
// Register the wallet_sendToAddress tool
registerSendToAddressTool(server, wallet);
// Register the wallet_getAddress tool
registerGetAddressTool(server);
// Register the wallet_getPublicKey tool
registerGetPublicKeyTool(server, wallet);
// Register the wallet_purchaseListing tool
registerPurchaseListingTool(server, wallet);
// Register the wallet_transferOrdToken tool
registerTransferOrdTokenTool(server, wallet);
// Register the wallet_refreshUtxos tool
registerRefreshUtxosTool(server, wallet);
// A2B tools have to be explicitly enabled
if (config.enableA2bTools) {
// Register the wallet_a2bPublishAgent tool
// registerA2bPublishAgentTool(server, wallet);
// Register the wallet_a2bPublishMcp tool
registerA2bPublishMcpTool(server, wallet, {
disableBroadcasting: config.disableBroadcasting,
});
}
// Register only the minimal public-facing tools
// wallet_createAction, wallet_signAction and wallet_getHeight have been removed
// Register wallet_createSignature
registerTool(
"wallet_createSignature",
"Creates a cryptographic signature using the wallet's private key. This tool enables secure message signing and transaction authorization, supporting various signature protocols.",
{ args: createSignatureArgsSchema },
async (
{ args }: { args: z.infer<typeof createSignatureArgsSchema> },
extra: RequestHandlerExtra<ServerRequest, ServerNotification>,
) => {
try {
const result = await wallet.createSignature(args);
return { content: [{ type: "text", text: JSON.stringify(result) }] };
} catch (err: unknown) {
const msg = err instanceof Error ? err.message : String(err);
return { content: [{ type: "text", text: msg }], isError: true };
}
},
);
// Register wallet_verifySignature
registerTool(
"wallet_verifySignature",
"Verifies a cryptographic signature against a message or data. This tool supports various verification protocols and can validate signatures from both the wallet's own keys and external public keys.",
{ args: verifySignatureArgsSchema },
async (
{ args }: { args: z.infer<typeof verifySignatureArgsSchema> },
extra: RequestHandlerExtra<ServerRequest, ServerNotification>,
) => {
try {
const result = await wallet.verifySignature(args);
return { content: [{ type: "text", text: JSON.stringify(result) }] };
} catch (err: unknown) {
const msg = err instanceof Error ? err.message : String(err);
return { content: [{ type: "text", text: msg }], isError: true };
}
},
);
// Register combined wallet_encryption tool
registerTool(
"wallet_encryption",
"Combined tool for encrypting and decrypting data using the wallet's cryptographic keys.\n\n" +
"PARAMETERS:\n" +
'- mode: (required) Either "encrypt" to encrypt plaintext or "decrypt" to decrypt ciphertext\n' +
"- data: (required) Text string or array of numbers to process\n" +
"- encoding: (optional) For text input, the encoding format (utf8, hex, base64) - default is utf8\n\n" +
"EXAMPLES:\n" +
"1. Encrypt text data:\n" +
" {\n" +
' "mode": "encrypt",\n' +
' "data": "Hello World"\n' +
" }\n\n" +
"2. Decrypt previously encrypted data:\n" +
" {\n" +
' "mode": "decrypt",\n' +
' "data": [encrypted bytes from previous response]\n' +
" }",
{ args: walletEncryptionArgsSchema },
async (
{ args }: { args: z.infer<typeof walletEncryptionArgsSchema> },
extra: RequestHandlerExtra<ServerRequest, ServerNotification>,
) => {
try {
const { mode, data, encoding } = args;
// Set default values for required parameters
const protocolID: WalletProtocol = [1, "aes256"];
const keyID = "default";
// Convert string data to binary if needed
let binaryData: number[];
if (Array.isArray(data)) {
binaryData = data;
} else {
// String data with encoding
const { toArray } = Utils;
binaryData = toArray(data, encoding || "utf8");
}
let result: { ciphertext?: number[]; plaintext?: number[] | string } =
{};
if (mode === "encrypt") {
result = await wallet.encrypt({
plaintext: binaryData,
protocolID,
keyID,
});
} else if (mode === "decrypt") {
result = await wallet.decrypt({
ciphertext: binaryData,
protocolID,
keyID,
});
// For decryption, convert plaintext back to string if it's likely UTF-8 text
if (result.plaintext as number[]) {
try {
const { toUTF8 } = Utils;
const textResult = toUTF8(result.plaintext as number[]);
// If conversion succeeds and seems like valid text, return as string
if (textResult && textResult.length > 0) {
return {
content: [
{
type: "text",
text: JSON.stringify({ plaintext: textResult }),
},
],
};
}
} catch (e) {
// If UTF-8 conversion fails, continue with binary result
}
}
}
return { content: [{ type: "text", text: JSON.stringify(result) }] };
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : String(error);
return {
content: [
{
type: "text",
text: `Error during ${args.mode}: ${errorMessage}`,
},
],
isError: true,
};
}
},
);
// Register createOrdinals tool
registerCreateOrdinalsTool(server, wallet);
return handlers;
}
```
--------------------------------------------------------------------------------
/tools/wallet/purchaseListing.ts:
--------------------------------------------------------------------------------
```typescript
import { PrivateKey } from "@bsv/sdk";
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import type { RequestHandlerExtra } from "@modelcontextprotocol/sdk/shared/protocol.js";
import type {
ServerNotification,
ServerRequest,
} from "@modelcontextprotocol/sdk/types.js";
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
import {
type ChangeResult,
type ExistingListing,
type LocalSigner,
type Payment,
type PurchaseOrdListingConfig,
type Royalty,
TokenType,
type TokenUtxo,
type Utxo,
oneSatBroadcaster,
purchaseOrdListing,
purchaseOrdTokenListing,
} from "js-1sat-ord";
import { Sigma } from "sigma-protocol";
import type { z } from "zod";
import {
MARKET_FEE_PERCENTAGE,
MARKET_WALLET_ADDRESS,
MINIMUM_MARKET_FEE_SATOSHIS,
} from "../constants";
import { purchaseListingArgsSchema } from "./schemas";
import type { Wallet } from "./wallet";
// Define types for 1Sat API response
interface OrdUtxo {
txid: string;
vout: number;
satoshis: number;
script: string;
origin?: {
outpoint: string;
data?: {
map?: {
royalties?: string;
[key: string]: string | number | boolean | null | undefined;
};
insc?: {
text?: string;
file?: {
type?: string;
size?: number;
};
};
};
};
data?: {
list?: {
price: number;
payout: string;
};
bsv20?: {
amt?: string;
tick?: string;
id?: string;
};
};
}
/**
* Register the purchaseListing tool
*
* This tool enables purchasing listed ordinals (NFTs or tokens) from the marketplace:
* 1. Parses the listing outpoint to get the txid and vout
* 2. Fetches the listing UTXO from the ordinals API
* 3. Gets the wallet's payment UTXOs (using the wallet's internal UTXO management)
* 4. Uses purchaseOrdListing or purchaseOrdTokenListing based on the listing type
* 5. For NFTs, automatically detects and processes royalty payments to original creators
* 6. Broadcasts the transaction
* 7. Returns the transaction details including success status and txid
*
* The tool supports both NFT and token listings with appropriate type-specific handling.
* Royalty payments are supported for NFT purchases only (based on creator-defined metadata).
*/
export function registerPurchaseListingTool(server: McpServer, wallet: Wallet) {
// Store a reference to check if wallet is persistent
server.tool(
"wallet_purchaseListing",
"Purchases a listing from the Bitcoin SV ordinals marketplace. Supports both NFT purchases (with royalty payments to original creators) and BSV-20/BSV-21 token purchases. The tool handles all aspects of the transaction - from fetching listing details, calculating fees, creating and broadcasting the transaction.",
{ args: purchaseListingArgsSchema },
async (
{ args }: { args: z.infer<typeof purchaseListingArgsSchema> },
extra: RequestHandlerExtra<ServerRequest, ServerNotification>,
): Promise<CallToolResult> => {
try {
// Load optional identity key for sigma signing
const identityKeyWif = process.env.IDENTITY_KEY_WIF;
let identityPk: PrivateKey | undefined;
if (identityKeyWif) {
try {
identityPk = PrivateKey.fromWif(identityKeyWif);
} catch (e) {
console.warn(
"Warning: Invalid IDENTITY_KEY_WIF environment variable; sigma signing disabled",
e,
);
}
}
// Fetch the listing info directly from the API
const response = await fetch(
`https://ordinals.gorillapool.io/api/txos/${args.listingOutpoint}?script=true`,
);
if (!response.ok) {
throw new Error(
`Failed to fetch listing data: ${response.statusText}`,
);
}
const listingData = (await response.json()) as OrdUtxo;
// Check if the listing is valid and has a price
if (!listingData.data?.list?.price) {
throw new Error("Listing is either not for sale or invalid");
}
// Check if payout is available
if (!listingData.data.list.payout) {
throw new Error("Listing doesn't have payout information");
}
// Calculate the market fee (3% of listing price)
const listingPrice = listingData.data.list.price;
let marketFee = Math.round(listingPrice * MARKET_FEE_PERCENTAGE);
// Ensure minimum fee
if (marketFee < MINIMUM_MARKET_FEE_SATOSHIS) {
marketFee = MINIMUM_MARKET_FEE_SATOSHIS;
}
// Parse the listing outpoint to get txid and vout
const [txid, voutStr] = args.listingOutpoint.split("_");
if (!txid) {
throw new Error("Invalid outpoint format. Expected txid_vout");
}
const vout = Number.parseInt(voutStr || "0", 10);
// Get private key from the wallet
const paymentPk = wallet.getPrivateKey();
if (!paymentPk) {
throw new Error("No private key available in wallet");
}
// Get payment address
const paymentAddress = paymentPk.toAddress().toString();
// Get payment UTXOs from the wallet's managed UTXOs
const { paymentUtxos } = await wallet.getUtxos();
if (!paymentUtxos || paymentUtxos.length === 0) {
// Provide more helpful error message with instructions
throw new Error(
`No payment UTXOs available for address ${paymentAddress}.
Please fund this wallet address with enough BSV to cover the purchase price
(${listingData.data.list.price} satoshis) plus market fee (${marketFee} satoshis) and transaction fees.`,
);
}
// Define market fee payment
const additionalPayments: Payment[] = [
{
to: MARKET_WALLET_ADDRESS,
amount: marketFee,
},
];
// Define metadata for the transaction
const metaData = {
app: "bsv-mcp",
type: "ord",
op: "purchase",
};
// Create the purchase transaction based on listing type
let transaction: ChangeResult;
if (args.listingType === "token") {
if (!args.tokenProtocol) {
throw new Error("tokenProtocol is required for token listings");
}
if (!args.tokenID) {
throw new Error("tokenID is required for token listings");
}
// Validate token data from the listing
if (!listingData.data.bsv20) {
throw new Error("This is not a valid BSV-20 token listing");
}
// For BSV-20, the amount should be included in the listing data
if (!listingData.data.bsv20.amt) {
throw new Error("Token listing doesn't have an amount specified");
}
// Convert the token protocol to the enum type expected by js-1sat-ord
const protocol =
args.tokenProtocol === "bsv-20" ? TokenType.BSV20 : TokenType.BSV21;
// Create a TokenUtxo with the required fields
const listingUtxo: TokenUtxo = {
txid,
vout,
script: listingData.script,
satoshis: 1, // TokenUtxo's satoshis must be exactly 1
amt: listingData.data.bsv20.amt,
id: args.tokenID,
payout: listingData.data.list.payout,
};
transaction = await purchaseOrdTokenListing({
protocol,
tokenID: args.tokenID,
utxos: paymentUtxos,
paymentPk,
listingUtxo,
ordAddress: args.ordAddress,
additionalPayments,
metaData,
});
} else {
// Create a regular Utxo for NFT listing
const listingUtxo: Utxo = {
txid,
vout,
script: listingData.script,
satoshis: listingData.satoshis,
};
// Create the ExistingListing object for NFT listings
const listing: ExistingListing = {
payout: listingData.data.list.payout,
listingUtxo,
};
// Check for royalties in the NFT origin data
// Royalties are only supported for NFTs, not for tokens
// The royalties are defined by the original creator as a JSON string
// in the NFT's metadata and parsed into a Royalty[] array
let royalties: Royalty[] = [];
if (listingData.origin?.data?.map?.royalties) {
try {
royalties = JSON.parse(listingData.origin.data.map.royalties);
} catch (error) {
// Remove console.warn
}
}
const purchaseOrdListingConfig: PurchaseOrdListingConfig = {
utxos: paymentUtxos,
paymentPk,
ordAddress: args.ordAddress,
listing,
additionalPayments,
metaData,
royalties,
};
transaction = await purchaseOrdListing(purchaseOrdListingConfig);
}
// After successful transaction creation, refresh the wallet's UTXOs
// This ensures the wallet doesn't try to reuse spent UTXOs
try {
await wallet.refreshUtxos();
} catch (refreshError) {
// Remove console.warn
}
// Optionally sign with identity key then broadcast the transaction
const disableBroadcasting = process.env.DISABLE_BROADCASTING === "true";
if (!disableBroadcasting) {
const broadcastResult = await transaction.tx.broadcast(
oneSatBroadcaster(),
);
// Handle broadcast response
const resultStatus =
typeof broadcastResult === "object" && "status" in broadcastResult
? broadcastResult.status
: "unknown";
const resultMessage =
typeof broadcastResult === "object" && "error" in broadcastResult
? broadcastResult.error
: "Transaction broadcast successful";
return {
content: [
{
type: "text",
text: JSON.stringify({
status: resultStatus,
message: resultMessage,
txid: transaction.tx.id("hex"),
listingOutpoint: args.listingOutpoint,
destinationAddress: args.ordAddress,
listingType: args.listingType,
tokenProtocol: args.tokenID ? args.tokenProtocol : undefined,
tokenID: args.tokenID,
price: listingData.data.list.price,
marketFee,
marketFeeAddress: MARKET_WALLET_ADDRESS,
royaltiesPaid:
args.listingType === "nft" &&
listingData.origin?.data?.map?.royalties
? JSON.parse(listingData.origin.data.map.royalties)
: undefined,
}),
},
],
};
}
return {
content: [
{
type: "text",
text: transaction.tx.toHex(),
},
],
};
} catch (err: unknown) {
const msg = err instanceof Error ? err.message : String(err);
return { content: [{ type: "text", text: msg }], isError: true };
}
},
);
}
```
--------------------------------------------------------------------------------
/tools/wallet/a2bPublishMcp.ts:
--------------------------------------------------------------------------------
```typescript
import { PrivateKey, Utils } from "@bsv/sdk";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import type { RequestHandlerExtra } from "@modelcontextprotocol/sdk/shared/protocol.js";
import type {
ClientNotification,
ClientRequest,
ServerNotification,
ServerRequest,
} from "@modelcontextprotocol/sdk/types.js";
import { createOrdinals } from "js-1sat-ord";
import type {
ChangeResult,
CreateOrdinalsConfig,
Destination,
Inscription,
LocalSigner,
PreMAP,
} from "js-1sat-ord";
import { Sigma } from "sigma-protocol";
import { z } from "zod";
import type { Wallet } from "./wallet";
const { toArray, toBase64 } = Utils;
// API endpoint for the A2B Overlay service
const OVERLAY_API_URL = "https://a2b-overlay-production.up.railway.app/v1";
// Schema for the MCP tool configuration
export const McpConfigSchema = z.object({
command: z.string().describe("The command to execute the tool"),
args: z.array(z.string()).describe("Arguments to pass to the command"),
tools: z.array(z.string()).optional().describe("Available tool names"),
prompts: z.array(z.string()).optional().describe("Available prompt names"),
resources: z.array(z.string()).optional().describe("Available resource URIs"),
env: z.record(z.string()).optional().describe("Environment variables"),
});
export type McpConfig = z.infer<typeof McpConfigSchema>;
// Schema for on-chain tool publish parameters
export const a2bPublishMcpArgsSchema = z.object({
toolName: z.string().describe("Human-friendly tool name"),
command: z.string().describe("The command to execute the tool"),
args: z.array(z.string()).describe("Arguments to pass to the command"),
keywords: z
.array(z.string())
.optional()
.describe("Optional keywords to improve tool discoverability"),
env: z
.array(
z.object({
key: z.string().describe("Environment variable name"),
description: z
.string()
.describe("Description of the environment variable"),
}),
)
.optional()
.describe("Optional environment variables with descriptions"),
description: z.string().optional().describe("Optional tool description"),
destinationAddress: z
.string()
.optional()
.describe("Optional target address for inscription"),
});
export type A2bPublishMcpArgs = z.infer<typeof a2bPublishMcpArgsSchema>;
/**
* Call the ingest endpoint to process a transaction
*/
async function callIngestEndpoint(txid: string): Promise<boolean> {
try {
const response = await fetch(`${OVERLAY_API_URL}/ingest`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ txid }),
});
if (!response.ok) {
console.warn(
`Ingest API returned status ${response.status}: ${response.statusText}`,
);
return false;
}
const result = await response.json();
// console.log('Ingest result:', result);
return true;
} catch (error) {
console.warn("Error calling ingest endpoint:", error);
return false;
}
}
/**
* Fetches MCP metadata (tools, prompts, resources) by running the command
* and connecting to it via the MCP client
*/
async function fetchMcpMetadata(
command: string,
args: string[],
): Promise<{
tools: string[];
prompts: string[];
resources: string[];
}> {
// console.log(`Fetching MCP metadata by running: ${command} ${args.join(' ')}`);
let transport: StdioClientTransport | undefined;
let client:
| Client<
ClientRequest,
ServerRequest,
ClientNotification | ServerNotification
>
| undefined;
try {
// Create a transport to the MCP server
// Pass through all current environment variables to ensure the same configuration
transport = new StdioClientTransport({
command,
args,
env: {
...process.env, // Pass through all current environment variables
DISABLE_BROADCASTING: "true", // Prevent actual broadcasting during tool discovery
},
});
// Create and connect a client
client = new Client({
name: "metadata-fetcher",
version: "1.0.0",
});
await client.connect(transport);
// Fetch available tools, prompts, and resources
const toolsResponse = await client.listTools();
const promptsResponse = await client.listPrompts();
const resourcesResponse = await client.listResources();
// Extract the names/URIs
const tools = toolsResponse.tools.map((tool) => tool.name);
const prompts = promptsResponse.prompts.map((prompt) => prompt.name);
const resources = resourcesResponse.resources.map(
(resource) => resource.uri,
);
// Add known tools that might be missing, avoid duplicates
const allTools = [...new Set([...tools])];
return {
tools: allTools,
prompts,
resources,
};
} catch (error) {
console.error("Error fetching MCP metadata:", error);
// Return hardcoded list of known tools if fetching fails
return {
tools: [],
prompts: [],
resources: [],
};
} finally {
// Clean up resources
if (transport) {
try {
// Close the transport to shut down the child process
await transport.close();
} catch (e) {
console.error("Error closing transport:", e);
}
}
}
}
/**
* Registers the wallet_a2bPublishMcp for publishing an MCP tool configuration on-chain
*/
export function registerA2bPublishMcpTool(
server: McpServer,
wallet: Wallet,
config: { disableBroadcasting: boolean },
) {
server.tool(
"wallet_a2bPublishMcp",
"Publish an MCP tool configuration record on-chain via Ordinal inscription. This creates a permanent, immutable, and discoverable tool definition that can be accessed by other MCP servers. The tool is published as a JSON inscription with metadata and optional digital signatures for authenticity verification.",
{ args: a2bPublishMcpArgsSchema },
async (
{ args }: { args: A2bPublishMcpArgs },
extra: RequestHandlerExtra<ServerRequest, ServerNotification>,
) => {
try {
// Load optional identity key for sigma signing
const identityKeyWif = process.env.IDENTITY_KEY_WIF;
let identityPk: PrivateKey | undefined;
if (identityKeyWif) {
try {
identityPk = PrivateKey.fromWif(identityKeyWif);
} catch (e) {
console.warn(
"Warning: Invalid IDENTITY_KEY_WIF environment variable; sigma signing disabled",
e,
);
}
}
const paymentPk = wallet.getPrivateKey();
if (!paymentPk) throw new Error("No private key available");
const { paymentUtxos } = await wallet.getUtxos();
if (!paymentUtxos?.length)
throw new Error("No payment UTXOs available to fund inscription");
const walletAddress = paymentPk.toAddress().toString();
// Fetch MCP metadata (tools, prompts, resources)
const metadata = await fetchMcpMetadata(args.command, args.args);
// console.log(`Discovered ${metadata.tools.length} tools, ${metadata.prompts.length} prompts, and ${metadata.resources.length} resources`);
// Assemble tool configuration
const toolConfig: McpConfig = {
command: args.command,
args: args.args,
tools: metadata.tools,
prompts: metadata.prompts,
resources: metadata.resources,
env: args.env
? args.env.reduce(
(acc, { key, description }) => {
acc[key] = description;
return acc;
},
{} as Record<string, string>,
)
: undefined,
};
// Validate compliance
McpConfigSchema.parse(toolConfig);
// Prepare the full configuration with metadata
const fullConfig = {
mcpServers: {
[args.toolName]: {
description: args.description || "",
keywords: args.keywords || [],
tools: metadata.tools || [],
prompts: metadata.prompts || [],
resources: metadata.resources || [],
...toolConfig,
},
},
};
const fileContent = JSON.stringify(fullConfig, null, 2);
// Base64 payload for inscription
const dataB64 = toBase64(toArray(fileContent));
const inscription: Inscription = {
dataB64,
contentType: "application/json",
};
// Destination for the ordinal
const targetAddress = args.destinationAddress ?? walletAddress;
const destinations: Destination[] = [
{ address: targetAddress, inscription },
];
// Default MAP metadata: file path, content type, encoding
const metaData: PreMAP = { app: "bsv-mcp", type: "a2b-mcp" };
const createOrdinalsConfig = {
utxos: paymentUtxos,
destinations,
paymentPk,
changeAddress: walletAddress,
metaData,
} as CreateOrdinalsConfig;
if (identityPk) {
createOrdinalsConfig.signer = {
idKey: identityPk,
} as LocalSigner;
}
// Inscribe the ordinal on-chain via js-1sat-ord
const result = await createOrdinals(createOrdinalsConfig);
const changeResult = result as ChangeResult;
// Broadcast the transaction
if (!config.disableBroadcasting) {
await changeResult.tx.broadcast();
const txid = changeResult.tx.id("hex");
setTimeout(async () => {
// Call the ingest endpoint to process the transaction
await callIngestEndpoint(txid);
}, 1000);
// Refresh UTXOs after spending
try {
await wallet.refreshUtxos();
} catch (refreshError) {
console.warn(
"Failed to refresh UTXOs after transaction:",
refreshError,
);
}
// Build a nicely formatted result
const outpointIndex = 0; // First output with the inscription
const outpoint = `${txid}_${outpointIndex}`;
// Tool URL for discovery is the outpoint
const onchainUrl = `ord://${outpoint}`;
return {
content: [
{
type: "text",
text: JSON.stringify(
{
status: "success",
txid,
outpoint,
onchainUrl,
toolName: args.toolName,
toolCount: metadata.tools.length,
promptCount: metadata.prompts.length,
resourceCount: metadata.resources.length,
description:
args.description || `MCP Tool: ${args.toolName}`,
address: targetAddress,
},
null,
2,
),
},
],
};
}
return {
content: [
{
type: "text",
text: changeResult.tx.toHex(),
},
],
};
} catch (err: unknown) {
const msg = err instanceof Error ? err.message : String(err);
return { content: [{ type: "text", text: msg }], isError: true };
}
},
);
}
```
--------------------------------------------------------------------------------
/tools/bsv/explore.ts:
--------------------------------------------------------------------------------
```typescript
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
// Base URL for WhatsOnChain API
const WOC_API_BASE_URL = "https://api.whatsonchain.com/v1/bsv";
/**
* WhatsOnChain API endpoints available for exploration
*/
enum ExploreEndpoint {
// Chain endpoints
CHAIN_INFO = "chain_info",
CHAIN_TIPS = "chain_tips",
CIRCULATING_SUPPLY = "circulating_supply",
PEER_INFO = "peer_info",
// Block endpoints
BLOCK_BY_HASH = "block_by_hash",
BLOCK_BY_HEIGHT = "block_by_height",
BLOCK_HEADERS = "block_headers",
BLOCK_PAGES = "block_pages",
// Stats endpoints
TAG_COUNT_BY_HEIGHT = "tag_count_by_height",
BLOCK_STATS_BY_HEIGHT = "block_stats_by_height",
BLOCK_MINER_STATS = "block_miner_stats",
MINER_SUMMARY_STATS = "miner_summary_stats",
// Transaction endpoints
TX_BY_HASH = "tx_by_hash",
TX_RAW = "tx_raw",
TX_RECEIPT = "tx_receipt",
BULK_TX_DETAILS = "bulk_tx_details",
ADDRESS_HISTORY = "address_history",
ADDRESS_UTXOS = "address_utxos",
// Health endpoint
HEALTH = "health",
}
enum Network {
MAIN = "main",
TEST = "test",
}
// Schema for the bsv_explore tool arguments
const exploreArgsSchema = z.object({
endpoint: z
.nativeEnum(ExploreEndpoint)
.describe("WhatsOnChain API endpoint to call"),
network: z
.nativeEnum(Network)
.default(Network.MAIN)
.describe("Network to use (main or test)"),
// Parameters for specific endpoints
blockHash: z
.string()
.optional()
.describe("Block hash (required for block_by_hash endpoint)"),
blockHeight: z
.number()
.optional()
.describe(
"Block height (required for block_by_height and block_stats_by_height endpoints)",
),
txHash: z
.string()
.optional()
.describe(
"Transaction hash (required for tx_by_hash, tx_raw, and tx_receipt endpoints)",
),
txids: z
.array(z.string())
.optional()
.describe("Array of transaction IDs for bulk_tx_details endpoint"),
address: z
.string()
.optional()
.describe(
"Bitcoin address (required for address_history and address_utxos endpoints)",
),
limit: z
.number()
.optional()
.describe("Limit for paginated results (optional for address_history)"),
pageNumber: z
.number()
.optional()
.describe("Page number for block_pages endpoint (defaults to 1)"),
days: z
.number()
.optional()
.describe("Number of days for miner stats endpoints (defaults to 7)"),
});
// Type for the tool arguments
type ExploreArgs = z.infer<typeof exploreArgsSchema>;
/**
* Register the bsv_explore tool with the MCP server
* @param server The MCP server instance
*/
export function registerExploreTool(server: McpServer): void {
server.tool(
"bsv_explore",
"Explore Bitcoin SV blockchain data using the WhatsOnChain API. Access multiple data types:\n\n" +
"CHAIN DATA:\n" +
"- chain_info: Network stats, difficulty, and chain work\n" +
"- chain_tips: Current chain tips including heights and states\n" +
"- circulating_supply: Current BSV circulating supply\n" +
"- peer_info: Connected peer statistics\n\n" +
"BLOCK DATA:\n" +
"- block_by_hash: Complete block data via hash (requires blockHash parameter)\n" +
"- block_by_height: Complete block data via height (requires blockHeight parameter)\n" +
"- tag_count_by_height: Stats on tag count for a specific block via height (requires blockHeight parameter)\n" +
"- block_headers: Retrieves the last 10 block headers\n" +
"- block_pages: Retrieves pages of transaction IDs for large blocks (requires blockHash and optional pageNumber)\n\n" +
"STATS DATA:\n" +
"- block_stats_by_height: Block statistics for a specific height (requires blockHeight parameter)\n" +
"- block_miner_stats: Block mining statistics for a time period (optional days parameter, default 7)\n" +
"- miner_summary_stats: Summary of mining statistics (optional days parameter, default 7)\n\n" +
"TRANSACTION DATA:\n" +
"- tx_by_hash: Detailed transaction data (requires txHash parameter)\n" +
"- tx_raw: Raw transaction hex data (requires txHash parameter)\n" +
"- tx_receipt: Transaction receipt (requires txHash parameter)\n" +
"- bulk_tx_details: Bulk transaction details (requires txids parameter as array of transaction hashes)\n\n" +
"ADDRESS DATA:\n" +
"- address_history: Transaction history for address (requires address parameter, optional limit)\n" +
"- address_utxos: Unspent outputs for address (requires address parameter)\n\n" +
"NETWORK:\n" +
"- health: API health check\n\n" +
"Use the appropriate parameters for each endpoint type and specify 'main' or 'test' network.",
{ args: exploreArgsSchema },
async ({ args }) => {
try {
const params = exploreArgsSchema.parse(args);
// Validate required parameters for specific endpoints
if (
params.endpoint === ExploreEndpoint.BLOCK_BY_HASH &&
!params.blockHash
) {
throw new Error("blockHash is required for block_by_hash endpoint");
}
if (
(params.endpoint === ExploreEndpoint.BLOCK_BY_HEIGHT ||
params.endpoint === ExploreEndpoint.TAG_COUNT_BY_HEIGHT) &&
params.blockHeight === undefined
) {
throw new Error(
"blockHeight is required for block_by_height and tag_count_by_height endpoints",
);
}
if (
[
ExploreEndpoint.TX_BY_HASH,
ExploreEndpoint.TX_RAW,
ExploreEndpoint.TX_RECEIPT,
].includes(params.endpoint) &&
!params.txHash
) {
throw new Error("txHash is required for transaction endpoints");
}
if (
[
ExploreEndpoint.ADDRESS_HISTORY,
ExploreEndpoint.ADDRESS_UTXOS,
].includes(params.endpoint) &&
!params.address
) {
throw new Error("address is required for address endpoints");
}
if (
params.endpoint === ExploreEndpoint.BLOCK_PAGES &&
!params.blockHash
) {
throw new Error("blockHash is required for block_pages endpoint");
}
if (
params.endpoint === ExploreEndpoint.BLOCK_STATS_BY_HEIGHT &&
params.blockHeight === undefined
) {
throw new Error(
"blockHeight is required for block_stats_by_height endpoint",
);
}
if (
params.endpoint === ExploreEndpoint.BULK_TX_DETAILS &&
(!params.txids || params.txids.length === 0)
) {
throw new Error(
"txids array is required for bulk_tx_details endpoint",
);
}
// Build API URL based on the selected endpoint
let apiUrl = `${WOC_API_BASE_URL}/${params.network}`;
switch (params.endpoint) {
case ExploreEndpoint.CHAIN_INFO:
apiUrl += "/chain/info";
break;
case ExploreEndpoint.CHAIN_TIPS:
apiUrl += "/chain/tips";
break;
case ExploreEndpoint.CIRCULATING_SUPPLY:
apiUrl += "/circulatingsupply";
break;
case ExploreEndpoint.PEER_INFO:
apiUrl += "/peer/info";
break;
case ExploreEndpoint.BLOCK_BY_HASH:
apiUrl += `/block/hash/${params.blockHash}`;
break;
case ExploreEndpoint.BLOCK_BY_HEIGHT:
apiUrl += `/block/height/${params.blockHeight}`;
break;
case ExploreEndpoint.TAG_COUNT_BY_HEIGHT:
apiUrl += `/block/tagcount/height/${params.blockHeight}/stats`;
break;
case ExploreEndpoint.BLOCK_HEADERS:
apiUrl += "/block/headers";
break;
case ExploreEndpoint.BLOCK_PAGES: {
const pageNumber = params.pageNumber || 1;
apiUrl += `/block/hash/${params.blockHash}/page/${pageNumber}`;
break;
}
case ExploreEndpoint.BLOCK_STATS_BY_HEIGHT:
apiUrl += `/block/height/${params.blockHeight}/stats`;
break;
case ExploreEndpoint.BLOCK_MINER_STATS: {
const days = params.days !== undefined ? params.days : 7;
apiUrl += `/miner/blocks/stats?days=${days}`;
break;
}
case ExploreEndpoint.MINER_SUMMARY_STATS: {
const days = params.days !== undefined ? params.days : 7;
apiUrl += `/miner/summary/stats?days=${days}`;
break;
}
case ExploreEndpoint.TX_BY_HASH:
apiUrl += `/tx/hash/${params.txHash}`;
break;
case ExploreEndpoint.TX_RAW:
apiUrl += `/tx/${params.txHash}/hex`;
break;
case ExploreEndpoint.TX_RECEIPT:
apiUrl += `/tx/${params.txHash}/receipt`;
break;
case ExploreEndpoint.BULK_TX_DETAILS:
apiUrl += "/txs";
break;
case ExploreEndpoint.ADDRESS_HISTORY:
apiUrl += `/address/${params.address}/history`;
if (params.limit !== undefined) {
apiUrl += `?limit=${params.limit}`;
}
break;
case ExploreEndpoint.ADDRESS_UTXOS:
apiUrl += `/address/${params.address}/unspent`;
break;
case ExploreEndpoint.HEALTH:
apiUrl += "/woc";
break;
default:
throw new Error(`Unsupported endpoint: ${params.endpoint}`);
}
// Special handling for bulk_tx_details which requires a POST request
if (params.endpoint === ExploreEndpoint.BULK_TX_DETAILS) {
const response = await fetch(apiUrl, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ txids: params.txids }),
});
if (!response.ok) {
let errorMsg = `API error: ${response.status} ${response.statusText}`;
if (response.status === 404) {
errorMsg += " - One or more transaction IDs not found";
}
throw new Error(errorMsg);
}
const result = await response.json();
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
}
// For all other endpoints, use GET request
const response = await fetch(apiUrl);
if (!response.ok) {
let errorMsg = `API error: ${response.status} ${response.statusText}`;
// Provide helpful tips for specific error codes
if (response.status === 404) {
switch (params.endpoint) {
case ExploreEndpoint.BLOCK_BY_HASH:
errorMsg += ` - Block hash "${params.blockHash}" not found`;
break;
case ExploreEndpoint.BLOCK_BY_HEIGHT:
errorMsg += ` - Block at height ${params.blockHeight} not found`;
break;
case ExploreEndpoint.BLOCK_PAGES:
errorMsg += ` - Block hash "${params.blockHash}" not found or page ${params.pageNumber || 1} does not exist`;
break;
case ExploreEndpoint.TX_BY_HASH:
case ExploreEndpoint.TX_RAW:
case ExploreEndpoint.TX_RECEIPT:
errorMsg += ` - Transaction hash "${params.txHash}" not found`;
break;
case ExploreEndpoint.ADDRESS_HISTORY:
case ExploreEndpoint.ADDRESS_UTXOS:
errorMsg += ` - No data found for address "${params.address}"`;
break;
}
}
throw new Error(errorMsg);
}
const result = await response.json();
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2),
},
],
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Error: ${error instanceof Error ? error.message : String(error)}`,
},
],
isError: true,
};
}
},
);
}
```
--------------------------------------------------------------------------------
/resources/brcs.ts:
--------------------------------------------------------------------------------
```typescript
import {
type McpServer,
ResourceTemplate,
} from "@modelcontextprotocol/sdk/server/mcp.js";
/**
* BRC Categories for organizing the Bitcoin Request for Comments specifications
*/
export enum BRCCategory {
Wallet = "wallet",
Transactions = "transactions",
Scripts = "scripts",
Tokens = "tokens",
Overlays = "overlays",
Payments = "payments",
PeerToPeer = "peer-to-peer",
KeyDerivation = "key-derivation",
Outpoints = "outpoints",
Opinions = "opinions",
StateMachines = "state-machines",
Apps = "apps",
}
/**
* Interface defining a BRC document
*/
interface BRCDocument {
number: string;
title: string;
category: BRCCategory;
}
/**
* Register all BRC-related resources with the MCP server
* @param server The MCP server instance
*/
export function registerBRCsResources(server: McpServer): void {
// Register BRCs repository main README
server.resource(
"brcs_readme",
"https://raw.githubusercontent.com/bitcoin-sv/BRCs/master/README.md",
{
title: "Bitcoin SV BRCs Overview",
description:
"Overview of all Bitcoin SV protocol specifications in the BRCs repository",
},
async (uri) => {
const resp = await fetch(uri.href);
const text = await resp.text();
return {
contents: [
{
uri: uri.href,
text,
},
],
};
},
);
// Register SUMMARY file which has the ToC
server.resource(
"brcs_summary",
"https://raw.githubusercontent.com/bitcoin-sv/BRCs/master/SUMMARY.md",
{
title: "Bitcoin SV BRCs Summary",
description: "Table of contents for all Bitcoin SV BRCs",
},
async (uri) => {
const resp = await fetch(uri.href);
const text = await resp.text();
return {
contents: [
{
uri: uri.href,
text,
},
],
};
},
);
// Add a dynamic BRC specification resource for any BRC by path
server.resource(
"brc_spec",
new ResourceTemplate("brc://{category}/{brcNumber}", { list: undefined }),
{
title: "Bitcoin SV BRC Specification",
description: "Access specific BRC specifications by category and number",
},
async (uri, { category, brcNumber }) => {
const path = `${category}/${brcNumber}.md`;
const resp = await fetch(
`https://raw.githubusercontent.com/bitcoin-sv/BRCs/master/${path}`,
);
if (!resp.ok) {
throw new Error(`BRC specification not found: ${path}`);
}
const text = await resp.text();
return {
contents: [
{
uri: uri.href,
text,
},
],
};
},
);
// Register individual BRCs
registerAllBRCs(server);
}
/**
* Register all BRC specifications from the master list
*/
function registerAllBRCs(server: McpServer): void {
// Complete list of BRCs from the README.md
const brcs: BRCDocument[] = [
{
number: "0",
title: "Banana-Powered Bitcoin Wallet Control Protocol",
category: BRCCategory.Wallet,
},
{
number: "1",
title: "Transaction Creation",
category: BRCCategory.Wallet,
},
{
number: "2",
title: "Data Encryption and Decryption",
category: BRCCategory.Wallet,
},
{
number: "3",
title: "Digital Signature Creation and Verification",
category: BRCCategory.Wallet,
},
{ number: "4", title: "Input Redemption", category: BRCCategory.Wallet },
{
number: "5",
title: "HTTP Wallet Communications Substrate",
category: BRCCategory.Wallet,
},
{
number: "6",
title: "XDM Wallet Communications Substrate",
category: BRCCategory.Wallet,
},
{
number: "7",
title: "Window Wallet Communication Substrate",
category: BRCCategory.Wallet,
},
{
number: "8",
title: "Everett-style Transaction Envelopes",
category: BRCCategory.Transactions,
},
{
number: "9",
title: "Simplified Payment Verification",
category: BRCCategory.Transactions,
},
{
number: "10",
title: "Merkle proof standardised format",
category: BRCCategory.Transactions,
},
{
number: "11",
title: "TSC Proof Format with Heights",
category: BRCCategory.Transactions,
},
{
number: "12",
title: "Raw Transaction Format",
category: BRCCategory.Transactions,
},
{
number: "13",
title: "TXO Transaction Object Format",
category: BRCCategory.Transactions,
},
{
number: "14",
title: "Bitcoin Script Binary, Hex and ASM Formats",
category: BRCCategory.Scripts,
},
{
number: "15",
title: "Bitcoin Script Assembly Language",
category: BRCCategory.Scripts,
},
{
number: "16",
title: "Pay to Public Key Hash",
category: BRCCategory.Scripts,
},
{
number: "17",
title: "Pay to R Puzzle Hash",
category: BRCCategory.Scripts,
},
{
number: "18",
title: "Pay to False Return",
category: BRCCategory.Scripts,
},
{
number: "19",
title: "Pay to True Return",
category: BRCCategory.Scripts,
},
{ number: "20", title: "There is no BRC-20", category: BRCCategory.Tokens },
{ number: "21", title: "Push TX", category: BRCCategory.Scripts },
{
number: "22",
title: "Overlay Network Data Synchronization",
category: BRCCategory.Overlays,
},
{
number: "23",
title: "Confederacy Host Interconnect Protocol (CHIP)",
category: BRCCategory.Overlays,
},
{
number: "24",
title: "Overlay Network Lookup Services",
category: BRCCategory.Overlays,
},
{
number: "25",
title: "Confederacy Lookup Availability Protocol (CLAP)",
category: BRCCategory.Overlays,
},
{
number: "26",
title: "Universal Hash Resolution Protocol",
category: BRCCategory.Overlays,
},
{
number: "27",
title: "Direct Payment Protocol (DPP)",
category: BRCCategory.Payments,
},
{
number: "28",
title: "Paymail Payment Destinations",
category: BRCCategory.Payments,
},
{
number: "29",
title: "Simple Authenticated BSV P2PKH Payment Protocol",
category: BRCCategory.Payments,
},
{
number: "30",
title: "Transaction Extended Format (EF)",
category: BRCCategory.Transactions,
},
{
number: "31",
title: "Authrite Mutual Authentication",
category: BRCCategory.PeerToPeer,
},
{
number: "32",
title: "BIP32 Key Derivation Scheme",
category: BRCCategory.KeyDerivation,
},
{
number: "33",
title: "PeerServ Message Relay Interface",
category: BRCCategory.PeerToPeer,
},
{
number: "34",
title: "PeerServ Host Interconnect Protocol",
category: BRCCategory.PeerToPeer,
},
// #35 is unused
{
number: "36",
title: "Format for Bitcoin Outpoints",
category: BRCCategory.Outpoints,
},
{
number: "37",
title: "Spending Instructions Extension for UTXO Storage Format",
category: BRCCategory.Outpoints,
},
// 38, 39, 40 are placeholders in the README
{
number: "41",
title: "PacketPay HTTP Payment Mechanism",
category: BRCCategory.Payments,
},
{
number: "42",
title: "BSV Key Derivation Scheme (BKDS)",
category: BRCCategory.KeyDerivation,
},
{
number: "43",
title: "Security Levels, Protocol IDs, Key IDs and Counterparties",
category: BRCCategory.KeyDerivation,
},
{
number: "44",
title: "Admin-reserved and Prohibited Key Derivation Protocols",
category: BRCCategory.KeyDerivation,
},
{
number: "45",
title: "Definition of UTXOs as Bitcoin Tokens",
category: BRCCategory.Tokens,
},
{
number: "46",
title: "Wallet Transaction Output Tracking (Output Baskets)",
category: BRCCategory.Wallet,
},
{
number: "47",
title: "Bare Multi-Signature",
category: BRCCategory.Scripts,
},
{ number: "48", title: "Pay to Push Drop", category: BRCCategory.Scripts },
{
number: "49",
title: "Users should never see an address",
category: BRCCategory.Opinions,
},
{
number: "50",
title: "Submitting Received Payments to a Wallet",
category: BRCCategory.Wallet,
},
{
number: "51",
title: "List of user experiences",
category: BRCCategory.Opinions,
},
{
number: "52",
title: "Identity Certificates",
category: BRCCategory.PeerToPeer,
},
{
number: "53",
title: "Certificate Creation and Revelation",
category: BRCCategory.Wallet,
},
{
number: "54",
title: "Hybrid Payment Mode for DPP",
category: BRCCategory.Payments,
},
{
number: "55",
title: "HTTPS Transport Mechanism for DPP",
category: BRCCategory.Payments,
},
{
number: "56",
title: "Unified Abstract Wallet-to-Application Messaging Layer",
category: BRCCategory.Wallet,
},
{
number: "57",
title: "Legitimate Uses for mAPI",
category: BRCCategory.Opinions,
},
{
number: "58",
title: "Merkle Path JSON format",
category: BRCCategory.Transactions,
},
{
number: "59",
title: "Security and Scalability Benefits of UTXO-based Overlay Networks",
category: BRCCategory.Opinions,
},
{
number: "60",
title: "Simplifying State Machine Event Chains in Bitcoin",
category: BRCCategory.StateMachines,
},
{
number: "61",
title: "Compound Merkle Path Format",
category: BRCCategory.Transactions,
},
{
number: "62",
title: "Background Evaluation Extended Format (BEEF) Transactions",
category: BRCCategory.Transactions,
},
{
number: "63",
title: "Genealogical Identity Protocol",
category: BRCCategory.PeerToPeer,
},
{
number: "64",
title: "Overlay Network Transaction History Tracking",
category: BRCCategory.Overlays,
},
{
number: "65",
title: "Transaction Labels and List Actions",
category: BRCCategory.Wallet,
},
{
number: "66",
title: "Output Basket Removal and Certificate Deletion",
category: BRCCategory.Wallet,
},
{
number: "67",
title: "Simplified Payment Verification",
category: BRCCategory.Transactions,
},
{
number: "68",
title: "Publishing Trust Anchor Details at an Internet Domain",
category: BRCCategory.PeerToPeer,
},
{
number: "69",
title: "Revealing Key Linkages",
category: BRCCategory.KeyDerivation,
},
{
number: "70",
title: "Paymail BEEF Transaction",
category: BRCCategory.Payments,
},
{
number: "71",
title: "Merkle Path Binary Format",
category: BRCCategory.Transactions,
},
{
number: "72",
title: "Protecting BRC-69 Key Linkage Information in Transit",
category: BRCCategory.KeyDerivation,
},
{
number: "73",
title: "Group Permissions for App Access",
category: BRCCategory.Wallet,
},
{
number: "74",
title: "BSV Unified Merkle Path (BUMP) Format",
category: BRCCategory.Transactions,
},
{
number: "75",
title: "Mnemonic For Master Private Key",
category: BRCCategory.KeyDerivation,
},
{
number: "76",
title: "Graph Aware Sync Protocol",
category: BRCCategory.Transactions,
},
{
number: "77",
title: "Message Signature Creation and Verification",
category: BRCCategory.PeerToPeer,
},
{
number: "78",
title: "Serialization Format for Portable Encrypted Messages",
category: BRCCategory.PeerToPeer,
},
{
number: "79",
title: "Token Exchange Protocol for UTXO-based Overlay Networks",
category: BRCCategory.Tokens,
},
{
number: "80",
title: "Improving on MLD for BSV Multicast Services",
category: BRCCategory.Opinions,
},
{
number: "81",
title: "Private Overlays with P2PKH Transactions",
category: BRCCategory.Overlays,
},
{
number: "82",
title:
"Defining a Scalable IPv6 Multicast Protocol for Blockchain Transaction Broadcast",
category: BRCCategory.PeerToPeer,
},
{
number: "83",
title: "Scalable Transaction Processing in the BSV Network",
category: BRCCategory.Transactions,
},
{
number: "84",
title: "Linked Key Derivation Scheme",
category: BRCCategory.KeyDerivation,
},
{
number: "85",
title: "Proven Identity Key Exchange (PIKE)",
category: BRCCategory.PeerToPeer,
},
{
number: "86",
title:
"Bidirectionally Authenticated Derivation of Privacy Restricted Type 42 Keys",
category: BRCCategory.KeyDerivation,
},
{
number: "87",
title:
"Standardized Naming Conventions for BRC-22 Topic Managers and BRC-24 Lookup Services",
category: BRCCategory.Overlays,
},
{
number: "88",
title: "Overlay Services Synchronization Architecture",
category: BRCCategory.Overlays,
},
{
number: "89",
title: "Web 3.0 Standard (at a high level)",
category: BRCCategory.Opinions,
},
{
number: "90",
title: "Thoughts on the Mandala Network",
category: BRCCategory.Opinions,
},
{
number: "91",
title: "Outputs, Overlays, and Scripts in the Mandala Network",
category: BRCCategory.Opinions,
},
{
number: "92",
title: "Mandala Token Protocol",
category: BRCCategory.Tokens,
},
{
number: "93",
title: "Limitations of BRC-69 Key Linkage Revelation",
category: BRCCategory.KeyDerivation,
},
{
number: "94",
title: "Verifiable Revelation of Shared Secrets Using Schnorr Protocol",
category: BRCCategory.KeyDerivation,
},
{
number: "95",
title: "Atomic BEEF Transactions",
category: BRCCategory.Transactions,
},
{
number: "96",
title: "BEEF V2 Txid Only Extension",
category: BRCCategory.Transactions,
},
{
number: "97",
title: "Extensible Proof-Type Format for Specific Key Linkage Claims",
category: BRCCategory.Wallet,
},
{
number: "98",
title: "P Protocols: Allowing future wallet protocol permission schemes",
category: BRCCategory.Wallet,
},
{
number: "99",
title:
"P Baskets: Allowing Future Wallet Basket and Digital Asset Permission Schemes",
category: BRCCategory.Wallet,
},
{
number: "100",
title:
"Unified, Vendor-Neutral, Unchanging, and Open BSV Blockchain Standard Wallet-to-Application Interface",
category: BRCCategory.Wallet,
},
{
number: "101",
title:
"Diverse Facilitators and URL Protocols for SHIP and SLAP Overlay Advertisements",
category: BRCCategory.Overlays,
},
{
number: "102",
title: "The deployment-info.json Specification",
category: BRCCategory.Apps,
},
{
number: "103",
title:
"Peer-to-Peer Mutual Authentication and Certificate Exchange Protocol",
category: BRCCategory.PeerToPeer,
},
{
number: "104",
title: "HTTP Transport for BRC-103 Mutual Authentication",
category: BRCCategory.PeerToPeer,
},
{
number: "105",
title: "HTTP Service Monetization Framework",
category: BRCCategory.Payments,
},
];
// Register each BRC
for (const brc of brcs) {
// Format the BRC number with leading zeros for consistency in resource IDs
const paddedNumber = brc.number.padStart(4, "0");
// Use a shorter resource ID that includes a relevant key from the title
const titleKeywords = brc.title.replace(/[^a-zA-Z0-9 ]/g, "").split(" ");
const keyword =
titleKeywords.find(
(word) =>
word.length > 3 &&
![
"with",
"from",
"that",
"this",
"your",
"when",
"then",
"them",
"they",
"bitcoin",
].includes(word.toLowerCase()),
) ||
titleKeywords[0] ||
"brc";
// Create a descriptive resource ID
const resourceId = `brc_${paddedNumber}_${keyword.toLowerCase()}`;
// Build the URL to the BRC file
const url = `https://raw.githubusercontent.com/bitcoin-sv/BRCs/master/${brc.category}/${brc.number.padStart(4, "0")}.md`;
server.resource(
resourceId,
url,
{
title: `BRC-${brc.number}: ${brc.title}`,
description: `Bitcoin SV BRC-${brc.number}: ${brc.title}`,
},
async (uri) => {
const resp = await fetch(uri.href);
if (!resp.ok) {
throw new Error(`BRC-${brc.number} not found at ${uri.href}`);
}
const text = await resp.text();
return {
contents: [
{
uri: uri.href,
text,
},
],
};
},
);
}
}
```
--------------------------------------------------------------------------------
/docs/a2b.md:
--------------------------------------------------------------------------------
```markdown
# A2B (Agent‑to‑Bitcoin)
*A payment + discovery extension for Google A2A*
---
## Table of Contents
1. [Introduction](#introduction)
2. [Motivation](#motivation)
3. [End‑to‑End Flow (Diagram)](#end-to-end-flow-diagram)
4. [Data Structures](#data-structures)
* 4.1 [Pricing Configuration (`x-payment-config`)](#pricing-configuration)
* 4.2 [Payment Claim (`x-payment` DataPart)](#payment-claim)
* 4.3 [Agent Card Examples](#agent-card-examples)
5. [Payment Flow](#payment-flow)
* 5.1 [Two‑Stage Deposit Model](#two-stage-deposit-model)
* 5.2 [FX Conversion & Price Feeds](#fx-conversion--price-feeds)
* 5.3 [Error Codes](#error-codes)
* 5.4 [Task State Machine](#task-state-machine)
* 5.5 [Subscription Renewal Flow](#subscription-renewal-flow)
6. [On‑Chain Registry](#on-chain-registry)
* 6.1 [1Sat Ordinal + MAP Format](#1sat-ordinal--map-format)
* 6.2 [Updating via Re‑inscription](#updating-via-re-inscription)
* 6.3 [Overlay Search UX](#overlay-search-ux)
* 6.4 [Cross‑Chain Registry Compatibility](#cross-chain-registry-compatibility)
7. [Protocol Guidelines](#protocol-guidelines)
8. [Security Considerations](#security-considerations)
9. [Payment Verification Algorithm](#payment-verification-algorithm)
10. [Implementation Guide](#implementation-guide)
* 10.1 [Client](#client)
* 10.2 [Server](#server)
* 10.3 [Publisher](#publisher)
---
## 1 Introduction<a id="introduction"></a>
A2B (Agent‑to‑Bitcoin) is a modular extension to Google's Agent‑to‑Agent (A2A) protocol, introducing **native payment** and **decentralized discovery** capabilities. It comprises two interoperable components:
* **Payment Protocol** – Extends A2A's JSON‑RPC schema by embedding **signed cryptocurrency transactions** directly within task requests, enabling agents to require and process payments seamlessly.
* **Registry Protocol** – Utilizes **1Sat Ordinals** to inscribe agent metadata onto the blockchain, creating an immutable, decentralized registry that supports ownership transfer and updates.
These components can function **independently or in tandem**, providing flexibility for diverse use‑cases ranging from one‑off micro‑payments to long‑term subscription services.
---
## 2 Motivation<a id="motivation"></a>
AI agents are increasingly ubiquitous, yet two core requirements remain underserved:
1. **Discovery** – Today, agent discovery relies on centralized registries or ad‑hoc web crawling. This both limits reach for smaller publishers and introduces single points of failure.
2. **Monetization** – Payment flows typically depend on proprietary API keys and external billing systems, resulting in fragmented, less secure implementations.
A2B addresses these challenges by:
* **Democratizing Discovery** – By inscribing AgentCards as 1Sat Ordinals, any participant can run an indexer and surface agents without heavy infrastructure.
* **Streamlining Monetization** – Payment configurations (`x‑payment‑config`) live in the same AgentCard that advertises an agent's skills, while signed payment claims (`x‑payment`) travel with each request – eliminating external billing glue.
* **Enabling Ownership & Transferability** – The inscription model confers on‑chain ownership. Registry entries can be transferred or sold, allowing a marketplace of agent reputations and services.
Together, these features pave the way for a more **robust, decentralized, and monetizable** agent ecosystem that scales with the evolving needs of AI‑driven interactions.
---
## 3 End‑to‑End Flow (Diagram)<a id="end-to-end-flow-diagram"></a>
```mermaid
sequenceDiagram
autonumber
participant C as A2B Client
participant O as A2B Overlay
participant B as Blockchain
participant S as A2B Agent (A2A Server)
participant T as MCP Server
Note over O,B: Overlay indexes Blockchain (1Sat Ordinal + MAP) and exposes search
C->>O: search("watchtower penalty")
O->>B: query inscriptions
B-->>O: matching entries
O-->>C: results list (AgentCard hash + summary)
C->>S: GET /.well-known/agent.json
S-->>C: AgentCard JSON
C->>C: verify hash == inscription
C->>C: sign rawTx (deposit/full)
C->>S: tasks/send + x‑payment
S->>B: Validate payment claim
alt Agent needs external compute
S-->>T: mcp.tool.call(params)
T-->>S: result / partial stream
end
S-->>C: TaskStatus / data stream
S->>B: broadcast rawTx (deposit or full)
```
---
## 4 Data Structures<a id="data-structures"></a>
### Type Definitions
```typescript
/** On‑chain pricing entry */
export interface PricingConfig {
id: string;
name: string;
currency: string; // anchor ticker
amount: number; // price in anchor units
address: string; // pay‑to address for anchor currency
acceptedCurrencies: string[]; // other tickers accepted
skillIds: string[]; // skills this price unlocks
interval?: 'day'|'week'|'month'|'year'|string|null;
description?: string|null;
depositPct?: number; // 0‑1 for two‑stage payments
priceFeedUrl?: string; // optional FX oracle
}
/** DataPart claim sent with tasks/send */
export interface PaymentClaim {
configId: string;
stage: 'deposit' | 'final' | 'full';
rawTx: string; // hex‑encoded unsigned or signed tx
currency: string; // ticker of UTXO value
refundAddress?: string; // optional alt‑chain refund
}
```
### 4.1 Pricing Configuration<a id="pricing-configuration"></a>
**Minimal**
```jsonc
{
"id": "wt-basic",
"name": "Basic Pay‑Per‑Call",
"currency": "BSV",
"amount": 0.0005,
"address": "1WatchtowerAddr",
"acceptedCurrencies": ["BSV"],
"skillIds": ["watchChannels"]
}
```
<details>
<summary>Extensive</summary>
```jsonc
{
"id": "watchtower-18m",
"name": "18‑Month Enterprise Watchtower",
"currency": "BSV",
"amount": 0.030,
"address": "1WatchtowerAddr",
"acceptedCurrencies": ["BSV","BTC","USD"],
"depositPct": 0.20,
"priceFeedUrl": "https://oracle.example/spot",
"interval": "P18M",
"skillIds": ["watchChannels"],
"description": "Long‑term SLA — 20 % up‑front, 80 % on success."
}
```
</details>
### 4.2 Payment Claim (`x-payment` DataPart)<a id="payment-claim"></a>
**Minimal**
```jsonc
{
"type": "data",
"data": {
"x-payment": {
"configId": "wt-basic",
"stage": "full",
"rawTx": "<hex>",
"currency": "BSV"
}
}
}
```
<details>
<summary>Extensive</summary>
```jsonc
{
"type": "data",
"data": {
"x-payment": {
"configId": "dex-chart-sub-month",
"stage": "deposit",
"rawTx": "<signed‑hex>",
"currency": "SOL",
"refundAddress": "solRefundPubKey"
}
}
}
```
</details>
### 4.3 Agent Card Examples<a id="agent-card-examples"></a>
#### Watchtower Agent — Minimal
```jsonc
{
"name": "Tower‑Guard (Minimal)",
"url": "https://watchtower.example",
"version": "1.0.0",
"capabilities": {},
"skills": [
{ "id": "watchChannels", "name": "Watch Lightning Channels" }
],
"x-payment-config": [
{ "id": "wt-basic", "name": "Basic Pay‑Per‑Call", "currency": "BSV", "amount": 0.0005, "address": "1WatchtowerAddr","acceptedCurrencies": ["BSV"],"skillIds": ["watchChannels"] }
]
}
```
<details>
<summary>Watchtower Agent — Extensive</summary>
```jsonc
{
"name": "Tower‑Guard Watch Services",
"url": "https://watchtower.example",
"version": "2.1.0",
"capabilities": {
"streaming": true,
"pushNotifications": true,
"stateTransitionHistory": true
},
"skills": [
{
"id": "watchChannels",
"name": "Lightning Watchtower",
"description": "Monitors LN channels and broadcasts penalty transactions.",
"tags": ["lightning","security","fraud-prevention"],
"examples": [
"watch channel 0234abcd… for 30 days",
"monitor my node for revoked states"
],
"inputModes": ["data"],
"outputModes": ["stream"]
}
],
"x-payment-config": [
{
"id": "watchtower-month",
"name": "30‑Day Watchtower",
"currency": "BSV",
"amount": 0.002,
"address": "1WatchtowerAddr",
"acceptedCurrencies": ["BSV","BTC","USD"],
"interval": "month",
"skillIds": ["watchChannels"],
"description": "Penalty‑tx monitoring for 30 days."
},
{
"id": "watchtower-18m",
"name": "18‑Month Enterprise Watchtower",
"currency": "BSV",
"amount": 0.030,
"address": "1WatchtowerAddr",
"acceptedCurrencies": ["BSV","BTC","USD"],
"interval": "P18M",
"depositPct": 0.20,
"priceFeedUrl": "https://oracle.example/spot",
"skillIds": ["watchChannels"],
"description": "Long‑term SLA; 20 % deposit, 80 % on completion."
}
]
}
```
</details>
#### DEX Chart Agent — Minimal
```jsonc
{
"name": "DEX Chart API (Minimal)",
"url": "https://dexcharts.example",
"version": "1.0.0",
"capabilities": {},
"skills": [
{ "id": "getDexChart", "name": "DEX Chart JSON" }
],
"x-payment-config": [
{
"id": "dex-chart-call",
"name": "Single OHLCV Snapshot",
"currency": "USD",
"amount": 0.05,
"address": "1DexDataAddr",
"acceptedCurrencies": ["USD"],
"skillIds": ["getDexChart"]
}
]
}
```
<details>
<summary>DEX Chart Agent — Extensive</summary>
```jsonc
{
"name": "On‑Chain DEX Chart API",
"url": "https://dexcharts.example",
"version": "1.0.0",
"capabilities": { "streaming": false },
"skills": [
{
"id": "getDexChart",
"name": "DEX Chart JSON",
"description": "Returns OHLCV data for any on‑chain DEX pair.",
"tags": ["markets","dex","charts"],
"examples": ["dex chart BSV/USDC 1h 500"],
"inputModes": ["text"],
"outputModes": ["data"]
}
],
"x-payment-config": [
{
"id": "dex-chart-call",
"name": "Single OHLCV Snapshot",
"currency": "USD",
"amount": 0.05,
"address": "1DexDataAddr",
"acceptedCurrencies": ["USD","BSV","SOL"],
"skillIds": ["getDexChart"],
"description": "Returns 500‑candle OHLCV JSON."
},
{
"id": "dex-chart-sub-month",
"name": "Unlimited Charts · 30 Days",
"currency": "USD",
"amount": 20,
"address": "1DexDataAddr",
"acceptedCurrencies": ["USD","BSV","SOL"],
"interval": "month",
"skillIds": ["getDexChart"],
"description": "Unlimited OHLCV queries for one month."
}
]
}
```
</details>
---
## 5 Payment Flow<a id="payment-flow"></a>
A2B's payment logic has three variants — *full*, *deposit + final* and *subscription*. The client always signs a raw transaction but **only the server broadcasts** it once the task reaches the corresponding stage.
<details>
<summary>Mermaid · Deposit vs Full Path</summary>
```mermaid
flowchart TD
A[Client signs rawTx] --> B{depositPct set?}
B -- No --> F[stage full]
F --> G[validate & broadcast]
G --> H[run task]
H --> I[done]
B -- Yes --> C[stage deposit]
C --> D[validate]
D --> E[run task]
C --> D[broadcast]
E --> J{needs final?}
J -- Yes --> K[402 AmountInsufficient]
K --> L[Client signs final rawTx]
L --> M[stage final]
M --> N[validate & broadcast]
N --> I
```
</details>
### 5.1 Two‑Stage Deposit Model<a id="two-stage-deposit-model"></a>
| Stage | RawTx ≥ (after FX) | Sender | Broadcast |
|---------|--------------------|--------|-----------|
| deposit | `amount × depositPct` | Client | Server |
| final | `amount − deposit` | Client | Server |
| full | `amount` | Client | Server |
---
### 5.2 FX Conversion & Price Feeds<a id="fx-conversion--price-feeds"></a>
<details>
<summary>Mermaid · FX Math & Slippage</summary>
```mermaid
flowchart LR
A[anchor amount] --> B[spot rate]
B --> C[required = anchor×rate]
C --> D[apply slippage]
D --> E{UTXO ≥ required?}
E -- Yes --> F[valid]
E -- No --> G[402 AmountInsufficient]
```
</details>
* Oracle JSON example: `{ "rates": { "USD": 123.45, "BTC": 0.000014 } }`
* Default slippage tolerance: **±1 %** (override per pricing config).
### 5.3 Error Codes<a id="error-codes"></a>
| HTTP | JSON‑RPC code | Meaning |
|------|---------------|-------------------------------|
| 402 | `-32030` | PaymentMissing |
| 402 | `-32031` | PaymentInvalid (rawTx) |
| 402 | `-32032` | StageMismatch |
| 402 | `-32033` | AmountInsufficient |
| 402 | `-32034` | CurrencyUnsupported / AddressMismatch |
---
### 5.4 Task State Machine
<details>
<summary>Mermaid · Task States</summary>
```mermaid
stateDiagram-v2
[*] --> pending
pending --> running : deposit/full valid
running --> needsFinal : 402
needsFinal --> running : final valid
running --> completed : success
running --> failed : error
needsFinal --> failed : timeout
```
</details>
---
### 5.5 Subscription Renewal Flow
<details>
<summary>Mermaid · Monthly Subscription Sequence</summary>
```mermaid
sequenceDiagram
autonumber
participant C as Client
participant S as Agent
participant B as Blockchain
C->>S: tasks/send (+ x‑payment month 1)
S->>B: broadcast tx₁
S-->>C: data stream (month 1)
Note over C,S: 30 days pass
C->>S: tasks/send (+ x‑payment month 2)
S->>B: broadcast tx₂
S-->>C: data stream (month 2)
```
</details>
---
## 6 On‑Chain Registry<a id="on-chain-registry"></a>
### 6.1 Agent Card + A2B Metadata<a id="1sat-ordinal--map-format"></a>
```
Output 0 (1 sat):
<P2PKH>
OP_FALSE OP_IF
"ord"
OP_1 "application/json"
OP_0 <AgentCard bytes>
OP_ENDIF
OP_RETURN
1PuQa7K62MiKCtssSLKy1kh56WWU7MtUR5 SET
app your-app-name type a2b-agent
```
### 6.1b MCP Tool Registration
```
Output 0 (1 sat):
<P2PKH>
OP_FALSE OP_IF
"ord"
OP_1 "application/json"
OP_0 <MCP Config bytes>
OP_ENDIF
OP_RETURN
1PuQa7K62MiKCtssSLKy1kh56WWU7MtUR5 SET
app your-app-name type a2b-mcp
```
### 6.2 Updating via Re‑inscription<a id="updating-via-re-inscription"></a>
Spend the satoshi, attach a new envelope with updated card and identical MAP; newest wins.
### 6.3 Overlay Search UX<a id="overlay-search-ux"></a>
* Fuzzy search over `name`, `description`, `tags`.
* Filters: `skillId`, `acceptedCurrency`, `interval`, `maxPrice`.
* Result card shows logo, summary, cheapest price, update height, rating.
### 6.4 Cross‑Chain Registry Compatibility
Full 1‑sat‑style inscriptions are effortless on **Bitcoin SV** (design target), but other ledgers impose tighter data caps or higher gas costs. The table ranks practicality for storing *the entire `agent.json`* versus a lightweight pointer.
| Chain | Native data capacity (per tx / script) | Full JSON on‑chain | Pointer variant¹ | Notes |
|-------|----------------------------------------|--------------------|------------------|-------|
| **Bitcoin SV** | OP_FALSE+ord ≈ 100 kB | ✅ | — | Native design. |
| **Bitcoin (Taproot)** | Witness pushes ≤ 520 B, unlimited chunks (Ordinals) | ⚠️ costly | ✅ | Same inscription flow; high fee/weight. |
| **Litecoin (Taproot)** | BTC rules, cheaper fees | ⚠️ | ✅ | LTC‑20 shows viability. |
| **Bitcoin Cash** | OP_RETURN total 223 B | ❌ | ✅ | Store hash + URL only. |
| **Dogecoin** | OP_RETURN 80 B | ❌ | ✅ | Use multi‑output "Stamps" style. |
| **Ethereum / EVM** | Calldata / storage; ~16 k gas / byte | ⚠️ very expensive | ✅ | Emit LOG event with hash + IPFS; full storage = $$. |
| **Solana** | Account data ≤ 10 MB | ✅ | — | Rent‑exempt account holds JSON. |
| **Avalanche / Polygon / BSC** | EVM rules, cheaper gas | ⚠️ | ✅ | Pointer is practical; full JSON still big. |
**Legend** ✅ feasible · ⚠️ feasible but high cost/complexity · ❌ impractical
¹ *Pointer variant* = small MAP record containing a SHA‑256 hash and URI of the card.
#### Pointer Variant Specification
```text
OP_RETURN a2b <domain> <optional-32‑byte‑hex>
```
> **Overlay behaviour** – When the indexer encounters a `hash / uri` record, it **must**:
> 1. Download the referenced `.well-known/agent.json`.
> 2. Verify `SHA‑256(body) == hash`.
> 3. Parse and ingest the JSON exactly as if it were stored on‑chain, so the agent's metadata remains fully searchable.
Using pointers lets *any* UTXO or EVM chain participate in A2B discovery with only ~75 bytes on‑chain, while still providing strong integrity via hash verification.
---
#### Why BSV Comes First
* Supports large ord‑style envelopes in‑script, no size kludges.
* Ultra‑low fees (< $0.001 for 10 kB) enable updates & rich metadata.
* Re‑inscription model = identical to BTC/LTC Taproot flow, easing future multichain parity.
Other chains can join immediately via the pointer method, then migrate to full on‑chain JSON if/when their ecosystems adopt high‑capacity inscription tech (e.g., BTC ordinals, Ethereum blobs / proto‑danksharding [oai_citation_attribution:9‡investopedia.com](https://www.investopedia.com/what-you-need-to-know-ahead-of-ethereum-dencun-update-wednesday-8607518?utm_source=chatgpt.com)).
---
## 7 Protocol Guidelines<a id="protocol-guidelines"></a>
| Scenario | Recommendation |
|------------------|----------------|
| Immediate | `stage:"full"`; server broadcasts on success. |
| Streaming | Deposit then `final`; server withholds last chunk until paid. |
| Interactive | One payment per task; switch to subscription for heavy usage. |
| Long‑Running | Deposit ≥ 50 % or milestone split. |
| Subscription | One config per billing interval (`interval`). |
| MCP Payments | Agent may embed nested A2B hop for downstream tools. |
---
## 8 Security Considerations<a id="security-considerations"></a>
<details>
<summary>Mermaid · Threat Mitigation Flow</summary>
```mermaid
flowchart TD
A[Duplicate txid] --> B[validate]
B -->|seen| X[Reject -32031]
C[Fake oracle JSON] --> D[verify sig]
D -->|bad| X
E[Double spend] --> F[mempool check]
F -->|bad| X
H[Valid path] --> I[broadcast]
```
</details>
* Grace timeout 30 min for missing `final`.
* Refund deposits on task expiry.
* Rate‑limit by satoshi/s & requests.
* Double‑spend detection before broadcast.
* Verify signed oracle feeds.
---
## 9 Payment Verification Algorithm<a id="payment-verification-algorithm"></a>
```pseudo
verifyAndBroadcast(rawTx, stage, cfg, payTicker):
tx = decode(rawTx)
out = findOutput(tx, cfg.address)
if !out -> 32034
anchor = (stage=='deposit') ? cfg.amount*cfg.depositPct
: (stage=='final') ? cfg.amount - cfg.amount*cfg.depositPct
: cfg.amount
rate = (payTicker==cfg.currency) ? 1
: fetchSpot(payTicker, cfg.currency, cfg.priceFeedUrl)
needed = anchor * rate
if out.value < needed*(1-slippage): -> 32033
if txidSeen(tx.id): -> 32031
broadcast(tx)
```
---
## 10 Implementation Guide<a id="implementation-guide"></a>
### Client<a id="client"></a>
1. Search overlay; choose pricing config.
2. Verify AgentCard hash.
3. Sign rawTx.
4. `tasks/send` + `x-payment`.
5. Handle `402` (deposit model) & resend `final`.
6. Watch mempool for server broadcast.
### Server<a id="server"></a>
1. Inscribe AgentCard (`type=a2b`).
2. Validate payment claim.
3. Execute task; call MCP tools if needed.
4. Broadcast rawTx; stream updates.
5. Re‑inscribe satoshi for updates.
### Publisher<a id="publisher"></a>
1. Publish AgentCard to overlay.
2. Can be a local MCP tool like bsv-mcp
3. Either user or platform can pay the network fee.
4. Broadcast transactions.
---
*Specification version 2025‑04‑19.*
```