UNPKG

bigblocks

Version:

Complete Bitcoin UI component library - authentication, social, wallet, market, inscriptions, and blockchain interactions for React

190 lines (189 loc) 6.63 kB
// Protocol Utilities - Bitcoin Social Transaction Builders import { Hash, Script, Utils } from "@bsv/sdk"; import { DEFAULT_FILENAMES, PROTOCOL_IDS, } from "../types/protocol.js"; // Build B Protocol Data (content layer) export function buildBProtocol(builder) { const { content, contentType, encoding = "utf-8", filename } = builder; const fileName = filename || DEFAULT_FILENAMES[contentType] || "content.txt"; return [PROTOCOL_IDS.B, content, contentType, encoding, fileName]; } // Build MAP Protocol Data (metadata layer) export function buildMAPProtocol(builder) { const { app, type, context, metadata = {} } = builder; const mapData = [PROTOCOL_IDS.MAP, "SET", "app", app, "type", type]; // Add context if provided if (context) { mapData.push("context", context.type, context.type, // Key and value type are the same context.value); } // Add additional metadata for (const [key, value] of Object.entries(metadata)) { mapData.push(key, value); } return mapData; } // Build complete social transaction data export function buildSocialTransaction(input) { const { type, content, contentType = "text/plain", context, metadata, config, } = input; const dataArrays = []; // Add B protocol if content provided if (content) { const bData = buildBProtocol({ content, contentType, }); dataArrays.push(bData); } // Add MAP protocol const mapData = buildMAPProtocol({ app: config.app, type, context, metadata, }); dataArrays.push(mapData); // Combine with pipe separator const combined = []; for (let i = 0; i < dataArrays.length; i++) { const dataArray = dataArrays[i]; if (dataArray) { combined.push(...dataArray); if (i < dataArrays.length - 1) { combined.push(PROTOCOL_IDS.PIPE); } } } return combined; } // Sign transaction data with AIP (Authentication & Identity Protocol) export function signWithAIP(builder) { const { dataToSign, signingKey, address } = builder; // Convert data array to number arrays for signing const dataArrays = dataToSign.map((item) => Utils.toArray(item, "utf8")); // Combine all data arrays into one const combinedData = []; for (const arr of dataArrays) { combinedData.push(...arr); } // Sign the hash of the data const hashBytes = Hash.sha256(combinedData); const signatureResult = signingKey.sign(hashBytes).toString("hex"); // Ensure signature is a string const signature = typeof signatureResult === "string" ? signatureResult : Utils.toHex(signatureResult); // Build AIP data const aipData = [ PROTOCOL_IDS.AIP, "BITCOIN_ECDSA", address, signature, ]; // Combine original data with AIP signature const signedData = [...dataToSign, PROTOCOL_IDS.PIPE, ...aipData]; return { data: signedData, signature: signature, address, algorithm: "BITCOIN_ECDSA", }; } // Build OP_RETURN script from signed data export function buildOpReturnScript(signedData) { const dataArrays = signedData.data.map((item) => Utils.toArray(item, "utf8")); const chunks = [ { op: 0 }, // OP_0 (OP_FALSE) { op: 106 }, // OP_RETURN ]; // Add data chunks with proper op codes for (const data of dataArrays) { if (data.length < 76) { // For data less than 76 bytes, the op code is the length chunks.push({ op: data.length, data }); } else { // For larger data, we need OP_PUSHDATA opcodes // This is a simplified version - real implementation would handle OP_PUSHDATA1/2/4 chunks.push({ op: 76, data }); // OP_PUSHDATA1 } } return new Script(chunks); } // Helper function to create specific social action transactions export function createSocialActionTransaction(type, signingKey, options) { const { content, contentType, targetTxid, targetIdKey, emoji, app, metadata = {}, } = options; // Determine context based on action type and targets let context = undefined; if (targetTxid) { context = { type: "tx", value: targetTxid }; } else if (targetIdKey) { context = { type: "bapID", value: targetIdKey }; // Note: keeping 'bapID' for protocol } // Add emoji to metadata if provided (for reactions) if (emoji) { metadata.emoji = emoji; } // Build transaction data const transactionData = buildSocialTransaction({ type, content, contentType, context, metadata, signingKey, config: { app }, }); // Sign with AIP return signWithAIP({ dataToSign: transactionData, signingKey, address: signingKey.toAddress().toString(), }); } // Specific helper functions for common social actions export const SocialActions = { post: (content, signingKey, app, contentType = "text/plain") => createSocialActionTransaction("post", signingKey, { content, contentType, app, }), reply: (content, parentTxid, signingKey, app, contentType = "text/plain") => createSocialActionTransaction("reply", signingKey, { content, contentType, targetTxid: parentTxid, app, }), like: (txid, signingKey, app, emoji = "❤") => createSocialActionTransaction("like", signingKey, { targetTxid: txid, emoji, app, }), unlike: (txid, signingKey, app, emoji = "❤") => createSocialActionTransaction("unlike", signingKey, { targetTxid: txid, emoji, app, }), follow: (idKey, signingKey, app) => createSocialActionTransaction("follow", signingKey, { targetIdKey: idKey, app, }), unfollow: (idKey, signingKey, app) => createSocialActionTransaction("unfollow", signingKey, { targetIdKey: idKey, app, }), friend: (idKey, signingKey, app) => createSocialActionTransaction("friend", signingKey, { targetIdKey: idKey, app, }), unfriend: (idKey, signingKey, app) => createSocialActionTransaction("unfriend", signingKey, { targetIdKey: idKey, app, }), message: (content, recipient, signingKey, app, encrypted = true) => createSocialActionTransaction("message", signingKey, { content, targetIdKey: recipient, app, metadata: { encrypted: encrypted.toString() }, }), };