Skip to main content
Glama

Bitcoin SV MCP Server

by b-open-io
decodeTransaction.ts7.84 kB
import { Transaction, Utils } from "@bsv/sdk"; import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import type { RequestHandlerExtra } from "@modelcontextprotocol/sdk/shared/protocol.js"; import { z } from "zod"; // Schema for decode transaction arguments export const decodeTransactionArgsSchema = z.object({ tx: z.string().describe("Transaction data or txid"), encoding: z .enum(["hex", "base64"]) .default("hex") .describe("Encoding of the input data"), }); export type DecodeTransactionArgs = z.infer<typeof decodeTransactionArgsSchema>; // Type for JungleBus API response interface JungleBusTransactionResponse { id: string; transaction: string; block_hash?: string; block_height?: number; block_time?: number; block_index?: number; addresses?: string[]; inputs?: string[]; outputs?: string[]; input_types?: string[]; output_types?: string[]; contexts?: string[]; sub_contexts?: string[]; data?: string[]; merkle_proof?: unknown; } // Network info response type interface NetworkInfoResponse { blocks: number; [key: string]: unknown; } // Transaction input type interface TransactionInputData { txid: string | undefined; vout: number; sequence: number | undefined; script: string; scriptAsm: string; type?: string; value?: number; } // Transaction output type interface TransactionOutputData { n: number; value: number | undefined; scriptPubKey: { hex: string; asm: string; }; type?: string; } // Transaction result type interface TransactionResult { txid: string; version: number; locktime: number; size: number; inputs: TransactionInputData[]; outputs: TransactionOutputData[]; confirmations?: number; block?: { hash: string; height: number; time: number; index: number; } | null; addresses?: string[]; fee?: number | null; feeRate?: number | null; } /** * Fetches transaction data from JungleBus */ async function fetchJungleBusData( txid: string, ): Promise<JungleBusTransactionResponse | null> { try { const response = await fetch( `https://junglebus.gorillapool.io/v1/transaction/get/${txid}`, ); if (!response.ok) { console.error( `JungleBus API error: ${response.status} ${response.statusText}`, ); return null; } return (await response.json()) as JungleBusTransactionResponse; } catch (error) { console.error("Error fetching from JungleBus:", error); return null; } } /** * Determines if a string is likely a txid */ function isTxid(str: string): boolean { // TX IDs are 64 characters in hex (32 bytes) return /^[0-9a-f]{64}$/i.test(str); } /** * Register the BSV transaction decode tool */ export function registerDecodeTransactionTool(server: McpServer): void { server.tool( "bsv_decodeTransaction", "Decodes and analyzes Bitcoin SV transactions to provide detailed insights. This powerful tool accepts either a transaction ID or raw transaction data and returns comprehensive information including inputs, outputs, fee calculations, script details, and blockchain context. Supports both hex and base64 encoded transactions and automatically fetches additional on-chain data when available.", { args: decodeTransactionArgsSchema, }, async ( { args }: { args: DecodeTransactionArgs }, extra: RequestHandlerExtra, ) => { try { const { tx, encoding } = args; let transaction: Transaction; let rawTx: string; let txid: string; let junglebusData: JungleBusTransactionResponse | null = null; // Check if input is txid or raw transaction if (isTxid(tx)) { // It's a txid, fetch from JungleBus txid = tx; junglebusData = await fetchJungleBusData(txid); if (!junglebusData) { throw new Error( `Failed to fetch transaction data for txid: ${txid}`, ); } // JungleBus returns base64, convert if needed rawTx = junglebusData.transaction; // Check if rawTx is in base64 format (common from JungleBus) const isBase64 = /^[A-Za-z0-9+/=]+$/.test(rawTx); if (isBase64) { const txBytes = Utils.toArray(rawTx, "base64"); transaction = Transaction.fromBinary(txBytes); } else { transaction = Transaction.fromHex(rawTx); } } else { // It's a raw transaction let txBytes: number[]; if (encoding === "hex") { txBytes = Utils.toArray(tx, "hex"); } else { txBytes = Utils.toArray(tx, "base64"); } transaction = Transaction.fromBinary(txBytes); txid = transaction.hash("hex") as string; // Optionally fetch additional context from JungleBus junglebusData = await fetchJungleBusData(txid); } // Basic transaction data const result: TransactionResult = { txid, version: transaction.version, locktime: transaction.lockTime, size: transaction.toBinary().length, inputs: transaction.inputs.map((input) => ({ txid: input.sourceTXID, vout: input.sourceOutputIndex, sequence: input.sequence, script: input.unlockingScript ? input.unlockingScript.toHex() : "", scriptAsm: input.unlockingScript ? input.unlockingScript.toASM() : "", })), outputs: transaction.outputs.map((output) => ({ n: transaction.outputs.indexOf(output), value: output.satoshis, scriptPubKey: { hex: output.lockingScript.toHex(), asm: output.lockingScript.toASM(), }, })), }; // Add JungleBus context if available if (junglebusData) { result.confirmations = junglebusData.block_height ? (await getCurrentBlockHeight()) - junglebusData.block_height + 1 : 0; result.block = junglebusData.block_hash ? { hash: junglebusData.block_hash, height: junglebusData.block_height || 0, time: junglebusData.block_time || 0, index: junglebusData.block_index || 0, } : null; // Add script types if ( junglebusData.input_types && junglebusData.input_types.length > 0 ) { result.inputs = result.inputs.map((input, i) => ({ ...input, type: junglebusData.input_types?.[i] || "unknown", })); } if ( junglebusData.output_types && junglebusData.output_types.length > 0 ) { result.outputs = result.outputs.map((output, i) => ({ ...output, type: junglebusData.output_types?.[i] || "unknown", })); } // Add addresses found in transaction if (junglebusData.addresses && junglebusData.addresses.length > 0) { result.addresses = junglebusData.addresses; } } // Calculate additional information const totalInputValue = result.inputs.reduce( (sum, input) => sum + (input.value || 0), 0, ); const totalOutputValue = result.outputs.reduce( (sum, output) => sum + (output.value || 0), 0, ); result.fee = totalInputValue > 0 ? totalInputValue - totalOutputValue : null; result.feeRate = result.fee !== null ? Math.round((result.fee / result.size) * 100000000) / 100000000 : null; return { content: [ { type: "text", text: JSON.stringify(result, null, 2), }, ], }; } catch (error) { return { content: [ { type: "text", text: error instanceof Error ? error.message : String(error), }, ], isError: true, }; } }, ); } /** * Get current block height */ async function getCurrentBlockHeight(): Promise<number> { try { const response = await fetch( "https://junglebus.gorillapool.io/v1/network/info", ); if (!response.ok) { return 0; } const data = (await response.json()) as NetworkInfoResponse; return data.blocks || 0; } catch (error) { console.error("Error fetching current block height:", error); return 0; } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/b-open-io/bsv-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server