bigblocks
Version:
Complete Bitcoin UI component library - authentication, social, wallet, market, inscriptions, and blockchain interactions for React
179 lines (178 loc) • 6.75 kB
JavaScript
// Broadcast Utilities - Send transactions to Bitcoin network
import { P2PKH, Transaction } from "@bsv/sdk";
import { buildOpReturnScript } from "./protocol.js";
// Default broadcast configuration
const DEFAULT_BROADCAST_CONFIG = {
broadcaster: "whatsonchain",
timeout: 30000,
retries: 3,
};
// Build complete transaction with funding and change
export async function buildCompleteTransaction(signedData, config) {
const { utxos, changeAddress, paymentKey, feePerByte = 0.5 } = config;
// Create OP_RETURN script from signed data
const opReturnScript = buildOpReturnScript(signedData);
// Create transaction
const tx = new Transaction();
// Add OP_RETURN output (0 satoshis)
tx.addOutput({
lockingScript: opReturnScript,
satoshis: 0,
});
// Calculate total input value
let totalInputValue = 0;
for (const utxo of utxos) {
tx.addInput({
sourceTransaction: utxo.tx,
sourceOutputIndex: utxo.vout,
unlockingScriptTemplate: new P2PKH().unlock(paymentKey),
});
totalInputValue += utxo.satoshis;
}
// Calculate estimated fee
const estimatedSize = tx.toHex().length / 2 + 150; // Rough estimation
const fee = Math.ceil(estimatedSize * feePerByte);
// Add change output if necessary
const changeAmount = totalInputValue - fee;
if (changeAmount > 546) {
// Dust limit
tx.addOutput({
lockingScript: new P2PKH().lock(changeAddress),
satoshis: changeAmount,
});
}
return tx;
}
// Broadcast transaction to Bitcoin network
export async function broadcastSocialTransaction(signedData, config) {
const finalConfig = { ...DEFAULT_BROADCAST_CONFIG, ...(config || {}) };
try {
// For now, we'll create a minimal transaction structure
// In a real implementation, you would:
// 1. Get UTXOs for the user
// 2. Build a complete transaction with inputs and change
// 3. Sign all inputs
// 4. Broadcast the raw transaction
// This is a simplified version that assumes external transaction building
const mockTxId = generateMockTxId();
// In practice, you would use a real broadcaster:
const result = await broadcastWithRetry(signedData, finalConfig);
return {
success: true,
txid: result.txid || mockTxId,
rawTx: result.rawTx,
fee: result.fee || 150, // Default fee estimation
};
}
catch (error) {
console.error("Broadcast failed:", error);
return {
success: false,
error: error instanceof Error ? error.message : "Unknown broadcast error",
};
}
}
// Retry logic for broadcasting
async function broadcastWithRetry(signedData, config, attempt = 1) {
try {
// Mock implementation - replace with real broadcaster
if (attempt <= (config.retries || 1)) {
// Simulate network call
await new Promise((resolve) => setTimeout(resolve, 100));
// Mock success
return {
txid: generateMockTxId(),
rawTx: "mock_raw_transaction_hex",
fee: 150,
};
}
throw new Error("Max retries exceeded");
}
catch (error) {
if (attempt < (config.retries || 1)) {
console.warn(`Broadcast attempt ${attempt} failed, retrying...`);
await new Promise((resolve) => setTimeout(resolve, 1000 * attempt));
return broadcastWithRetry(signedData, config, attempt + 1);
}
throw error;
}
}
// Generate mock transaction ID for testing
function generateMockTxId() {
const chars = "0123456789abcdef";
let result = "";
for (let i = 0; i < 64; i++) {
result += chars.charAt(Math.floor(Math.random() * chars.length));
}
return result;
}
// Real broadcaster implementations would go here:
// WhatsOnChain broadcaster (deprecated - use BlockchainService instead)
export async function broadcastWithWhatsOnChain(rawTx) {
console.warn("broadcastWithWhatsOnChain is deprecated. Use BlockchainService instead.");
const response = await fetch("https://api.whatsonchain.com/v1/bsv/main/tx/raw", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ txhex: rawTx }),
});
if (!response.ok) {
throw new Error(`WhatsOnChain broadcast failed: ${response.statusText}`);
}
return await response.text(); // Returns txid
}
// GorillaPool broadcaster (deprecated - use BlockchainService instead)
export async function broadcastWithGorillaPool(rawTx) {
console.warn("broadcastWithGorillaPool is deprecated. Use BlockchainService instead.");
const response = await fetch("https://arc.gorillapool.io/v1/tx", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ rawtx: rawTx }),
});
if (!response.ok) {
throw new Error(`GorillaPool broadcast failed: ${response.statusText}`);
}
const result = await response.json();
return result.txid;
}
// TAAL broadcaster (deprecated - use BlockchainService instead)
export async function broadcastWithTaal(rawTx, apiKey) {
console.warn("broadcastWithTaal is deprecated. Use BlockchainService instead.");
const response = await fetch("https://api.taal.com/arc/v1/tx", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${apiKey || "YOUR_TAAL_API_KEY"}`, // Would need to be configured
},
body: JSON.stringify({ rawtx: rawTx }),
});
if (!response.ok) {
throw new Error(`TAAL broadcast failed: ${response.statusText}`);
}
const result = await response.json();
return result.txid;
}
// Utility to get UTXOs for a given address (deprecated - use BlockchainService instead)
export async function getUtxos(address) {
console.warn("getUtxos is deprecated. Use BlockchainService.getUTXOs() instead.");
try {
const response = await fetch(`https://api.whatsonchain.com/v1/bsv/main/address/${address}/unspent`);
if (!response.ok) {
throw new Error("Failed to fetch UTXOs");
}
const utxos = await response.json();
return utxos.map((utxo) => ({
txid: utxo.tx_hash,
vout: utxo.tx_pos,
satoshis: utxo.value,
script: "", // Would need to fetch from transaction
}));
}
catch (error) {
console.error("Failed to get UTXOs:", error);
return [];
}
}