#
tokens: 21453/50000 6/59 files (page 2/2)
lines: off (toggle) GitHub
raw markdown copy
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.*
```
Page 2/2FirstPrevNextLast