bigblocks
Version:
Complete Bitcoin UI component library - authentication, social, wallet, market, inscriptions, and blockchain interactions for React
190 lines (189 loc) • 6.63 kB
JavaScript
// 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() },
}),
};