UNPKG

runes-js

Version:

A typescript library for Runes protocol of Bitcoin

121 lines (104 loc) 2.96 kB
import * as ecc from "@bitcoinerlab/secp256k1" import * as bitcoin from "bitcoinjs-lib" export function buildWitnessScript({ recover = false, ...options }: WitnessScriptOptions) { bitcoin.initEccLib(ecc) if (!options.mediaType || !options.mediaContent || !options.xkey) { throw new OrditSDKError("Failed to build witness script") } if (recover) { return bitcoin.script.compile([ Buffer.from(options.xkey, "hex"), bitcoin.opcodes.OP_CHECKSIG, ]) } const contentChunks = chunkContent( options.mediaContent, !options.mediaType.includes("text") ? "base64" : "utf8", ) const contentStackElements = contentChunks.map(opPush) const metaStackElements: (number | Buffer)[] = [] if (typeof options.meta === "object") { metaStackElements.push( ...[ bitcoin.opcodes.OP_FALSE, bitcoin.opcodes.OP_IF, opPush("ord"), 1, 1, opPush("application/json;charset=utf-8"), bitcoin.opcodes.OP_0, ], ) const metaChunks = chunkContent(JSON.stringify(options.meta)) metaChunks && metaChunks.forEach((chunk) => { metaStackElements.push(opPush(chunk)) }) metaChunks && metaStackElements.push(bitcoin.opcodes.OP_ENDIF) } const baseStackElements = [ Buffer.from(options.xkey, "hex"), bitcoin.opcodes.OP_CHECKSIG, bitcoin.opcodes.OP_FALSE, bitcoin.opcodes.OP_IF, opPush("ord"), 1, 1, opPush(options.mediaType), bitcoin.opcodes.OP_0, ] const commitmentStackElements: (number | Buffer)[] = [] if (options.commitment) { commitmentStackElements.push(options.commitment) } return bitcoin.script.compile([ ...baseStackElements, ...contentStackElements, ...commitmentStackElements, bitcoin.opcodes.OP_ENDIF, ...metaStackElements, ]) } export type WitnessScriptOptions = { xkey: string mediaContent: string mediaType: string meta: any commitment?: Buffer recover?: boolean } export class OrditSDKError extends Error { constructor(message: string) { super(message) this.name = "OrditSDKError" } } export const MAXIMUM_SCRIPT_ELEMENT_SIZE = 520 export const chunkContent = function ( str: string, encoding: BufferEncoding = "utf8", ) { const contentBuffer = Buffer.from(str, encoding) const chunks: Buffer[] = [] let chunkedBytes = 0 while (chunkedBytes < contentBuffer.byteLength) { const chunk = contentBuffer.subarray( chunkedBytes, chunkedBytes + MAXIMUM_SCRIPT_ELEMENT_SIZE, ) chunkedBytes += chunk.byteLength chunks.push(chunk) } return chunks } function opPush(data: string | Buffer) { const buff = Buffer.isBuffer(data) ? data : Buffer.from(data, "utf8") if (buff.byteLength > MAXIMUM_SCRIPT_ELEMENT_SIZE) throw new OrditSDKError( "Data is too large to push. Use chunkContent to split data into smaller chunks", ) return Buffer.concat([buff]) }