diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..789ac2e --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,5 @@ +{ + "printWidth": 100, + "singleQuote": true, + "trailingComma": "es5" +} diff --git a/src/cbor.ts b/src/cbor.ts index 7e2077d..c0e7d4f 100644 --- a/src/cbor.ts +++ b/src/cbor.ts @@ -1,5 +1,5 @@ -import { utils } from "@scure/btc-signer"; -import * as P from "micro-packed"; +import { utils } from '@scure/btc-signer'; +import * as P from 'micro-packed'; type Bytes = Uint8Array; @@ -20,7 +20,7 @@ const F16BE = P.wrap({ if (value === -Infinity) return w.bytes(new Uint8Array([0xfc, 0x00])); if (Number.isNaN(value)) return w.bytes(new Uint8Array([0x7e, 0x00])); if (isNegZero(value)) return w.bytes(new Uint8Array([0x80, 0x00])); - throw w.err("f16: not implemented"); + throw w.err('f16: not implemented'); }, decodeStream: (r) => { // decode_half from RFC 8949 @@ -41,11 +41,7 @@ const U64LEN = P.apply(P.U64BE, P.coders.numberBigint); // Number/lengths limits const CBOR_LIMITS: Record< number, - [ - number | bigint, - P.CoderType | P.CoderType, - P.CoderType, - ] + [number | bigint, P.CoderType | P.CoderType, P.CoderType] > = { 24: [2 ** 8 - 1, P.U8, P.U8], 25: [2 ** 16 - 1, P.U16BE, P.U16BE], @@ -55,11 +51,7 @@ const CBOR_LIMITS: Record< const cborUint = P.wrap({ encodeStream(w, value: number | bigint) { - if (value < 24) - return INFO.encodeStream( - w, - typeof value === "bigint" ? Number(value) : value - ); + if (value < 24) return INFO.encodeStream(w, typeof value === 'bigint' ? Number(value) : value); for (const ai in CBOR_LIMITS) { const [limit, intCoder, _] = CBOR_LIMITS[ai]; if (value > limit) continue; @@ -79,10 +71,10 @@ const cborUint = P.wrap({ const cborNegint = P.wrap({ encodeStream: (w, v: number | bigint) => - cborUint.encodeStream(w, typeof v === "bigint" ? -(v + 1n) : -(v + 1)), + cborUint.encodeStream(w, typeof v === 'bigint' ? -(v + 1n) : -(v + 1)), decodeStream(r) { const v = cborUint.decodeStream(r); - return typeof v === "bigint" ? -1n - v : -1 - v; + return typeof v === 'bigint' ? -1n - v : -1 - v; }, }); @@ -108,8 +100,7 @@ const cborArrLength = (inner: P.CoderType): P.CoderType => const ai = INFO.decodeStream(r); if (ai < 24) return P.array(ai, inner).decodeStream(r); // array can have indefinite-length - if (ai === 31) - return P.array(new Uint8Array([0xff]), inner).decodeStream(r); + if (ai === 31) return P.array(new Uint8Array([0xff]), inner).decodeStream(r); const lenCoder = CBOR_LIMITS[ai][2]; if (!lenCoder) throw r.err(`cbor/lengthArray wrong length=${ai}`); return P.array(lenCoder, inner).decodeStream(r); @@ -125,9 +116,7 @@ const cborLength = ( P.wrap({ encodeStream(w, value: T | T[]) { if (Array.isArray(value)) - throw new Error( - "cbor/length: encoding indefinite-length strings not supported" - ); + throw new Error('cbor/length: encoding indefinite-length strings not supported'); const bytes = fn(null).encode(value); if (bytes.length < 24) { INFO.encodeStream(w, bytes.length); @@ -148,8 +137,7 @@ const cborLength = ( decodeStream(r): T | T[] { const ai = INFO.decodeStream(r); if (ai < 24) return fn(ai).decodeStream(r); - if (ai === 31) - return P.array(new Uint8Array([0xff]), def).decodeStream(r); + if (ai === 31) return P.array(new Uint8Array([0xff]), def).decodeStream(r); const lenCoder = CBOR_LIMITS[ai][2]; if (!lenCoder) throw r.err(`cbor/length wrong length=${ai}`); return fn(lenCoder).decodeStream(r); @@ -162,15 +150,9 @@ const cborSimple: P.CoderType = P.wrap({ if (value === true) return INFO.encodeStream(w, 21); if (value === null) return INFO.encodeStream(w, 22); if (value === undefined) return INFO.encodeStream(w, 23); - if (typeof value !== "number") - throw w.err(`cbor/simple: wrong value type=${typeof value}`); + if (typeof value !== 'number') throw w.err(`cbor/simple: wrong value type=${typeof value}`); // Basic values encoded as f16 - if ( - isNegZero(value) || - Number.isNaN(value) || - value === Infinity || - value === -Infinity - ) { + if (isNegZero(value) || Number.isNaN(value) || value === Infinity || value === -Infinity) { INFO.encodeStream(w, 25); return F16BE.encodeStream(w, value); } @@ -192,19 +174,19 @@ const cborSimple: P.CoderType = P.wrap({ if (ai === 25) return F16BE.decodeStream(r); if (ai === 26) return P.F32BE.decodeStream(r); if (ai === 27) return P.F64BE.decodeStream(r); - throw r.err("cbor/simple: unassigned"); + throw r.err('cbor/simple: unassigned'); }, }); export type CborValue = - | { TAG: "uint"; data: number | bigint } - | { TAG: "negint"; data: number | bigint } - | { TAG: "simple"; data: boolean | null | undefined | number } - | { TAG: "string"; data: string } - | { TAG: "bytes"; data: Bytes } - | { TAG: "array"; data: CborValue[] } - | { TAG: "map"; data: [CborValue][] } - | { TAG: "tag"; data: [CborValue, CborValue] }; + | { TAG: 'uint'; data: number | bigint } + | { TAG: 'negint'; data: number | bigint } + | { TAG: 'simple'; data: boolean | null | undefined | number } + | { TAG: 'string'; data: string } + | { TAG: 'bytes'; data: Bytes } + | { TAG: 'array'; data: CborValue[] } + | { TAG: 'map'; data: [CborValue][] } + | { TAG: 'tag'; data: [CborValue, CborValue] }; const cborValue: P.CoderType = P.mappedTag(P.bits(3), { uint: [0, cborUint], // An unsigned integer in the range 0..264-1 inclusive. @@ -220,72 +202,62 @@ const cborValue: P.CoderType = P.mappedTag(P.bits(3), { export const CBOR: P.CoderType = P.apply(cborValue, { encode(from: CborValue): any { let value = from.data; - if (from.TAG === "bytes") { + if (from.TAG === 'bytes') { if (utils.isBytes(value)) return value; const chunks = []; if (!Array.isArray(value)) throw new Error(`CBOR: wrong indefinite-length bytestring=${value}`); for (const c of value as any) { - if (c.TAG !== "bytes" || !utils.isBytes(c.data)) + if (c.TAG !== 'bytes' || !utils.isBytes(c.data)) throw new Error(`CBOR: wrong indefinite-length bytestring=${c}`); chunks.push(c.data); } return utils.concatBytes(...chunks); } - if (from.TAG === "string") { - if (typeof value === "string") return value; - if (!Array.isArray(value)) - throw new Error(`CBOR: wrong indefinite-length string=${value}`); - let res = ""; + if (from.TAG === 'string') { + if (typeof value === 'string') return value; + if (!Array.isArray(value)) throw new Error(`CBOR: wrong indefinite-length string=${value}`); + let res = ''; for (const c of value as any) { - if (c.TAG !== "string" || typeof c.data !== "string") + if (c.TAG !== 'string' || typeof c.data !== 'string') throw new Error(`CBOR: wrong indefinite-length string=${c}`); res += c.data; } return res; } - if (from.TAG === "array" && Array.isArray(value)) - value = value.map((i: any) => this.encode(i)); - if (from.TAG === "map" && typeof value === "object" && value !== null) { + if (from.TAG === 'array' && Array.isArray(value)) value = value.map((i: any) => this.encode(i)); + if (from.TAG === 'map' && typeof value === 'object' && value !== null) { return Object.fromEntries( - (from.data as any).map(([k, v]: [any, any]) => [ - this.encode(k), - this.encode(v), - ]) + (from.data as any).map(([k, v]: [any, any]) => [this.encode(k), this.encode(v)]) ); } - if (from.TAG === "tag") throw new Error("not implemented"); + if (from.TAG === 'tag') throw new Error('not implemented'); return value; }, decode(data: any): any { - if (typeof data === "bigint") { - return data < 0n ? { TAG: "negint", data } : { TAG: "uint", data }; + if (typeof data === 'bigint') { + return data < 0n ? { TAG: 'negint', data } : { TAG: 'uint', data }; } - if (typeof data === "string") return { TAG: "string", data }; - if (utils.isBytes(data)) return { TAG: "bytes", data }; - if (Array.isArray(data)) - return { TAG: "array", data: data.map((i) => this.decode(i)) }; - if ( - typeof data === "number" && - Number.isSafeInteger(data) && - !isNegZero(data) - ) { - return data < 0 ? { TAG: "negint", data } : { TAG: "uint", data }; + if (typeof data === 'string') return { TAG: 'string', data }; + if (utils.isBytes(data)) return { TAG: 'bytes', data }; + if (Array.isArray(data)) return { TAG: 'array', data: data.map((i) => this.decode(i)) }; + if (typeof data === 'number' && Number.isSafeInteger(data) && !isNegZero(data)) { + return data < 0 ? { TAG: 'negint', data } : { TAG: 'uint', data }; } if ( - typeof data === "boolean" || - typeof data === "number" || + typeof data === 'boolean' || + typeof data === 'number' || data === null || data === undefined ) { - return { TAG: "simple", data: data }; + return { TAG: 'simple', data: data }; } - if (typeof data === "object") { + if (typeof data === 'object') { return { - TAG: "map", + TAG: 'map', data: Object.entries(data).map((kv) => kv.map((i) => this.decode(i))), }; } - throw new Error("unknown type"); + throw new Error('unknown type'); }, }); diff --git a/src/cli.ts b/src/cli.ts index 6cde103..0b8d435 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,12 +1,12 @@ #!/usr/bin/env node -import { lstatSync, readFileSync, realpathSync } from "node:fs"; -import { extname } from "node:path"; -import { brotliCompressSync, constants as zlibc } from "node:zlib"; +import { lstatSync, readFileSync, realpathSync } from 'node:fs'; +import { extname } from 'node:path'; +import { brotliCompressSync, constants as zlibc } from 'node:zlib'; // @ts-ignore -import Input from "enquirer/lib/prompts/input.js"; +import Input from 'enquirer/lib/prompts/input.js'; // @ts-ignore -import { hex } from "@scure/base"; +import { hex } from '@scure/base'; import { Address, Decimal, @@ -16,30 +16,22 @@ import { WIF, p2tr, utils, -} from "@scure/btc-signer"; +} from '@scure/btc-signer'; // @ts-ignore -import Select from "enquirer/lib/prompts/select.js"; -import { - type Inscription, - OutOrdinalReveal, - p2tr_ord_reveal, -} from "./index.js"; +import Select from 'enquirer/lib/prompts/select.js'; +import { type Inscription, OutOrdinalReveal, p2tr_ord_reveal } from './index.js'; /* */ -const { - BROTLI_MODE_GENERIC: B_GENR, - BROTLI_MODE_TEXT: B_TEXT, - BROTLI_MODE_FONT, -} = zlibc; +const { BROTLI_MODE_GENERIC: B_GENR, BROTLI_MODE_TEXT: B_TEXT, BROTLI_MODE_FONT } = zlibc; // Max script limit. // Bitcoin core node won't relay transaction with bigger limit, even if they possible. // https://github.com/bitcoin/bitcoin/blob/d908877c4774c2456eed09167a5f382758e4a8a6/src/policy/policy.h#L26-L27 const MAX_STANDARD_TX_WEIGHT = 400000; // 4 * 100kvb const DUST_RELAY_TX_FEE = 3000n; // won't relay if less than this in fees? const customScripts = [OutOrdinalReveal]; -const ZERO_32B = "00".repeat(32); +const ZERO_32B = '00'.repeat(32); // Utils type Opts = Record; @@ -48,12 +40,10 @@ export function splitArgs(args: string[]): { args: string[]; opts: Opts } { const opts: Opts = {}; for (let i = 0; i < args.length; i++) { const cur = args[i]; - if (cur.startsWith("--")) { - if (i + 1 >= args.length) - throw new Error(`arguments: no value for ${cur}`); + if (cur.startsWith('--')) { + if (i + 1 >= args.length) throw new Error(`arguments: no value for ${cur}`); const next = args[++i]; - if (next.startsWith("--")) - throw new Error(`arguments: no value for ${cur}, got ${next}`); + if (next.startsWith('--')) throw new Error(`arguments: no value for ${cur}, got ${next}`); opts[cur.slice(2)] = next; continue; } @@ -71,8 +61,7 @@ const validateFloat = (s: string) => { const validateTxid = (s: string) => { try { const txid = hex.decode(s); - if (txid.length !== 32) - return `wrong length ${txid.length}, expected 32 bytes`; + if (txid.length !== 32) return `wrong length ${txid.length}, expected 32 bytes`; return true; } catch (e) { return `${e}`; @@ -100,13 +89,13 @@ const validateAmount = (s: string) => { // UI // const underline = '\x1b[4m'; -const bold = "\x1b[1m"; +const bold = '\x1b[1m'; // const gray = '\x1b[90m'; -const reset = "\x1b[0m"; -const red = "\x1b[31m"; -const green = "\x1b[32m"; +const reset = '\x1b[0m'; +const red = '\x1b[31m'; +const green = '\x1b[32m'; // const yellow = '\x1b[33m'; -const magenta = "\x1b[35m"; +const magenta = '\x1b[35m'; const HELP_TEXT = ` - ${bold}net:${reset} bitcoin network @@ -122,14 +111,9 @@ const HELP_TEXT = ` ${bold}Important:${reset} first sat is always inscribed. Batch inscriptions are not supported. `; -type InputValidate = ( - input: string -) => boolean | string | Promise; +type InputValidate = (input: string) => boolean | string | Promise; -export const select = async ( - message: string, - choices: string[] -): Promise => { +export const select = async (message: string, choices: string[]): Promise => { try { return await new Select({ message, choices }).run(); } catch (e) { @@ -137,10 +121,7 @@ export const select = async ( } }; -export async function input( - message: string, - validate?: InputValidate -): Promise { +export async function input(message: string, validate?: InputValidate): Promise { let opts: { message: string; validate?: InputValidate } = { message }; if (validate) opts.validate = validate; try { @@ -151,11 +132,10 @@ export async function input( } declare const navigator: any; -const defaultLang = - typeof navigator === "object" ? navigator.language : undefined; +const defaultLang = typeof navigator === 'object' ? navigator.language : undefined; const bfmt = new Intl.NumberFormat(defaultLang, { - style: "unit", - unit: "byte", + style: 'unit', + unit: 'byte', }); const formatBytes = (n: number) => `${magenta}${bfmt.format(n)}${reset}`; @@ -219,7 +199,7 @@ const usage = (err?: Error | string) => { if (err) console.error(`${red}ERROR${reset}: ${err}`); console.log( `Usage: ${green}ord-cli${reset} [--net ${Object.keys(NETWORKS).join( - "|" + '|' )}] [--priv key] [--recovery key] [--compress=on|off] [--fee 10.1] [--addr address] ` ); console.log(HELP_TEXT); @@ -227,20 +207,18 @@ const usage = (err?: Error | string) => { }; async function getNetwork(opts: Opts) { - if (!opts.net) opts.net = await select("Network", ["testnet", "mainnet"]); + if (!opts.net) opts.net = await select('Network', ['testnet', 'mainnet']); const NET = NETWORKS[opts.net]; - if (typeof opts.net !== "string" || !NET) - return usage( - `wrong network ${opts.net}. Expected: ${Object.keys(NETWORKS).join(", ")}` - ); + if (typeof opts.net !== 'string' || !NET) + return usage(`wrong network ${opts.net}. Expected: ${Object.keys(NETWORKS).join(', ')}`); console.log(`${bold}Network:${reset} ${NET.name}`); return NET; } function getKeys(net: NET, opts: Opts) { const KEYS: Record = { - priv: "Temporary", - recovery: "Recovery", + priv: 'Temporary', + recovery: 'Recovery', }; const res: Record = {}; for (const name in KEYS) { @@ -250,9 +228,7 @@ function getKeys(net: NET, opts: Opts) { if (opts[name]) res[name] = WIF(net).decode(opts.priv); else { res[name] = utils.randomPrivateKeyBytes(); - console.log( - `${KEYS[name]} private key: ${red}${WIF(net).encode(res[name])}${reset}` - ); + console.log(`${KEYS[name]} private key: ${red}${WIF(net).encode(res[name])}${reset}`); } if (res[name].length !== 32) { return usage( @@ -278,7 +254,7 @@ function getInscription(filePath: string, opts: Opts) { let data = Uint8Array.from(readFileSync(filePath, null)); let inscription: Inscription = { tags: { contentType: mime }, body: data }; info.push(`size=${formatBytes(data.length)}`); - if (!opts.compress || opts.compress !== "off") { + if (!opts.compress || opts.compress !== 'off') { const compressed = brotliCompressSync(data, { params: { [zlibc.BROTLI_PARAM_MODE]: brotliMode, @@ -291,16 +267,14 @@ function getInscription(filePath: string, opts: Opts) { data = compressed; info.push(`compressed_size=${formatBytes(data.length)}`); inscription = { - tags: { contentType: mime, contentEncoding: "br" }, + tags: { contentType: mime, contentEncoding: 'br' }, body: data, }; } } else info.push(`${red}uncompressed${reset}`); // notify user that compression disabled if (data.length > MAX_STANDARD_TX_WEIGHT) - return usage( - `File is too big ${data.length}. Limit ${MAX_STANDARD_TX_WEIGHT}` - ); - console.log(`${bold}File:${reset} ${filePath} (${info.join(", ")})`); + return usage(`File is too big ${data.length}. Limit ${MAX_STANDARD_TX_WEIGHT}`); + console.log(`${bold}File:${reset} ${filePath} (${info.join(', ')})`); return inscription; } @@ -322,29 +296,15 @@ async function getAddr(net: NET, opts: Opts) { } }; if (!address) - address = await input( - "Change address (where inscription will be sent on reveal)", - validate - ); + address = await input('Change address (where inscription will be sent on reveal)', validate); if (validate(address) !== true) return usage(`wrong address=${address}`); return address; } -function getPayment( - privKey: Uint8Array, - recovery: Uint8Array, - inscription: Inscription, - net: NET -) { +function getPayment(privKey: Uint8Array, recovery: Uint8Array, inscription: Inscription, net: NET) { const pubKey = utils.pubSchnorr(privKey); const recoveryPub = utils.pubSchnorr(recovery); - return p2tr( - recoveryPub, - p2tr_ord_reveal(pubKey, [inscription]), - net, - false, - customScripts - ); + return p2tr(recoveryPub, p2tr_ord_reveal(pubKey, [inscription]), net, false, customScripts); } function getTransaction( @@ -375,10 +335,9 @@ async function main() { const argv = process.argv; // @ts-ignore if (import.meta.url !== `file://${realpathSync(argv[1])}`) return; // ESM is broken. - if (argv.length < 3) return usage("Wrong argument count"); // node script file + if (argv.length < 3) return usage('Wrong argument count'); // node script file const { args, opts } = splitArgs(argv.slice(2)); - if (args.length !== 1) - return usage(`only single file supported, got ${args.length}`); + if (args.length !== 1) return usage(`only single file supported, got ${args.length}`); const net = await getNetwork(opts); const inscription = getInscription(args[0], opts); const { priv, recovery } = getKeys(net, opts); @@ -387,16 +346,7 @@ async function main() { // Actual logic const payment = getPayment(priv, recovery, inscription, net); // dummy tx to estimate fees and tx size - const dummyTx = getTransaction( - priv, - addr, - payment, - net, - ZERO_32B, - 0, - DUST_RELAY_TX_FEE, - 1n - ); + const dummyTx = getTransaction(priv, addr, payment, net, ZERO_32B, 0, DUST_RELAY_TX_FEE, 1n); if (dummyTx.weight >= MAX_STANDARD_TX_WEIGHT) { return usage( `File is too big: reveal transaction weight (${dummyTx.weight}) is higher than limit (${MAX_STANDARD_TX_WEIGHT})` @@ -415,24 +365,15 @@ async function main() { )}` ); // Ask for UTXO - console.log("Please enter UTXO information for transaction you sent:"); + console.log('Please enter UTXO information for transaction you sent:'); // These fields cannot be known before we send tx, // and to send tx user needs an address of inscription - const txid = await input("Txid", validateTxid); - const index = Number.parseInt(await input("Index", validateIndex)); - const amount = Decimal.decode(await input("Amount", validateAmount)); + const txid = await input('Txid', validateTxid); + const index = Number.parseInt(await input('Index', validateIndex)); + const amount = Decimal.decode(await input('Amount', validateAmount)); // Real reveal transaction - const tx = getTransaction( - priv, - addr, - payment, - net, - txid, - index, - amount, - txFee - ); - console.log("Reveal transaction created."); + const tx = getTransaction(priv, addr, payment, net, txid, index, amount, txFee); + console.log('Reveal transaction created.'); console.log(`${bold}Txid:${reset} ${tx.id}`); console.log(`${bold}Tx:${reset}`); console.log(hex.encode(tx.extract())); diff --git a/src/index.ts b/src/index.ts index e8b7f32..398572f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ -import { type Coder, hex, utf8 } from "@scure/base"; +import { type Coder, hex, utf8 } from '@scure/base'; import { type CustomScript, MAX_SCRIPT_BYTE_LENGTH, @@ -6,12 +6,12 @@ import { Script, type ScriptType, utils, -} from "@scure/btc-signer"; -import * as P from "micro-packed"; -import { CBOR } from "./cbor.js"; +} from '@scure/btc-signer'; +import * as P from 'micro-packed'; +import { CBOR } from './cbor.js'; type Bytes = Uint8Array; -const PROTOCOL_ID = /* @__PURE__ */ utf8.decode("ord"); +const PROTOCOL_ID = /* @__PURE__ */ utf8.decode('ord'); function splitChunks(buf: Bytes): Bytes[] { const res = []; @@ -27,9 +27,8 @@ const RawInscriptionId = /* @__PURE__ */ P.tuple([ export const InscriptionId: P.Coder = { encode(data: string) { - const [txId, index] = data.split("i", 2); - if (`${+index}` !== index) - throw new Error(`InscriptionId wrong index: ${index}`); + const [txId, index] = data.split('i', 2); + if (`${+index}` !== index) throw new Error(`InscriptionId wrong index: ${index}`); return RawInscriptionId.encode([hex.decode(txId), +index]); }, decode(data: Bytes) { @@ -108,40 +107,34 @@ const TagCoder: P.Coder = { const res: Partial = {}; if (unknown.length) res.unknown = unknown; for (const field in tmp) { - if (field === "parent" && tmp[field].length > 1) { - res[field as TagName] = tmp[field].map((i) => - TagCoders.parent.decode(i) - ); + if (field === 'parent' && tmp[field].length > 1) { + res[field as TagName] = tmp[field].map((i) => TagCoders.parent.decode(i)); continue; } - res[field as TagName] = TagCoders[field as TagName].decode( - utils.concatBytes(...tmp[field]) - ); + res[field as TagName] = TagCoders[field as TagName].decode(utils.concatBytes(...tmp[field])); } return res as Tags; }, decode(to: Tags): TagRaw[] { const res: TagRaw[] = []; for (const field in to) { - if (field === "unknown") continue; + if (field === 'unknown') continue; const tagName = TagCoderInternal.encode(field); - if (field === "parent" && Array.isArray(to.parent)) { - for (const p of to.parent) - res.push({ tag: tagName, data: TagCoders.parent.encode(p) }); + if (field === 'parent' && Array.isArray(to.parent)) { + for (const p of to.parent) res.push({ tag: tagName, data: TagCoders.parent.encode(p) }); continue; } let bytes = TagCoders[field as TagName].encode(to[field as TagName]); // Handle pointer = 0: - if (field === "pointer" && bytes.length === 0) { + if (field === 'pointer' && bytes.length === 0) { bytes = new Uint8Array([0]); } for (const data of splitChunks(bytes)) res.push({ tag: tagName, data }); } if (to.unknown) { - if (!Array.isArray(to.unknown)) - throw new Error("ordinals/TagCoder: unknown should be array"); + if (!Array.isArray(to.unknown)) throw new Error('ordinals/TagCoder: unknown should be array'); for (const [tag, data] of to.unknown) res.push({ tag, data }); } return res; @@ -150,21 +143,20 @@ const TagCoder: P.Coder = { export type Inscription = { tags: Tags; body: Bytes; cursed?: boolean }; type OutOrdinalRevealType = { - type: "tr_ord_reveal"; + type: 'tr_ord_reveal'; pubkey: Bytes; inscriptions: Inscription[]; }; const parseEnvelopes = (script: ScriptType, pos = 0) => { - if (!Number.isSafeInteger(pos)) - throw new Error(`parseInscription: wrong pos=${typeof pos}`); + if (!Number.isSafeInteger(pos)) throw new Error(`parseInscription: wrong pos=${typeof pos}`); const envelopes = []; // Inscriptions with broken parsing are called 'cursed' (stutter or pushnum) let stutter = false; main: for (; pos < script.length; pos++) { const instr = script[pos]; if (instr !== 0) continue; - if (script[pos + 1] !== "IF") { + if (script[pos + 1] !== 'IF') { if (script[pos + 1] === 0) stutter = true; continue main; } @@ -180,17 +172,17 @@ const parseEnvelopes = (script: ScriptType, pos = 0) => { for (let j = pos + 3; j < script.length; j++) { const op = script[j]; // done - if (op === "ENDIF") { + if (op === 'ENDIF') { envelopes.push({ start: pos + 3, end: j, pushnum, payload, stutter }); pos = j; break; } - if (op === "1NEGATE") { + if (op === '1NEGATE') { pushnum = true; payload.push(new Uint8Array([0x81])); continue; } - if (typeof op === "number" && 1 <= op && op <= 16) { + if (typeof op === 'number' && 1 <= op && op <= 16) { pushnum = true; payload.push(new Uint8Array([op])); continue; @@ -207,12 +199,9 @@ const parseEnvelopes = (script: ScriptType, pos = 0) => { }; // Additional API for parsing inscriptions -export function parseInscriptions( - script: ScriptType, - strict = false -): Inscription[] | undefined { +export function parseInscriptions(script: ScriptType, strict = false): Inscription[] | undefined { if (strict && (!utils.isBytes(script[0]) || script[0].length !== 32)) return; - if (strict && script[1] !== "CHECKSIG") return; + if (strict && script[1] !== 'CHECKSIG') return; const envelopes = parseEnvelopes(script); const inscriptions: Inscription[] = []; @@ -227,10 +216,8 @@ export function parseInscriptions( for (; i < payload.length && payload[i] !== 0; i += 2) { const tag = payload[i]; const data = payload[i + 1]; - if (!utils.isBytes(tag)) - throw new Error("parseInscription: non-bytes tag"); - if (!utils.isBytes(data)) - throw new Error("parseInscription: non-bytes tag data"); + if (!utils.isBytes(tag)) throw new Error('parseInscription: non-bytes tag'); + if (!utils.isBytes(data)) throw new Error('parseInscription: non-bytes tag data'); tags.push({ tag, data }); } while (payload[i] === 0 && i < payload.length) i++; @@ -255,19 +242,15 @@ export function parseInscriptions( * Parse inscriptions from reveal tx input witness (tx.inputs[0].finalScriptWitness) */ export function parseWitness(witness: Bytes[]): Inscription[] | undefined { - if (witness.length !== 3) throw new Error("Wrong witness"); + if (witness.length !== 3) throw new Error('Wrong witness'); // We don't validate other parts of witness here since we want to parse // as much stuff as possible. When creating inscription, it is done more strictly return parseInscriptions(Script.decode(witness[1])); } -export const OutOrdinalReveal: Coder< - OptScript, - OutOrdinalRevealType | undefined -> & - CustomScript = { +export const OutOrdinalReveal: Coder & CustomScript = { encode(from: ScriptType): OutOrdinalRevealType | undefined { - const res: Partial = { type: "tr_ord_reveal" }; + const res: Partial = { type: 'tr_ord_reveal' }; try { res.inscriptions = parseInscriptions(from, true); res.pubkey = from[0] as Bytes; @@ -277,22 +260,21 @@ export const OutOrdinalReveal: Coder< return res as OutOrdinalRevealType; }, decode: (to: OutOrdinalRevealType): OptScript => { - if (to.type !== "tr_ord_reveal") return; - const out: ScriptType = [to.pubkey, "CHECKSIG"]; + if (to.type !== 'tr_ord_reveal') return; + const out: ScriptType = [to.pubkey, 'CHECKSIG']; for (const { tags, body } of to.inscriptions) { - out.push(0, "IF", PROTOCOL_ID); + out.push(0, 'IF', PROTOCOL_ID); const rawTags = TagCoder.decode(tags); for (const tag of rawTags) out.push(tag.tag, tag.data); // Body out.push(0); for (const c of splitChunks(body)) out.push(c); - out.push("ENDIF"); + out.push('ENDIF'); } return out as any; }, finalizeTaproot: (script: any, parsed: any, signatures: any) => { - if (signatures.length !== 1) - throw new Error("tr_ord_reveal/finalize: wrong signatures array"); + if (signatures.length !== 1) throw new Error('tr_ord_reveal/finalize: wrong signatures array'); const [{ pubKey }, sig] = signatures[0]; if (!P.utils.equalBytes(pubKey, parsed.pubkey)) return; return [sig, script]; @@ -308,9 +290,9 @@ export function p2tr_ord_reveal( inscriptions: Inscription[] ): { type: string; script: Uint8Array } { return { - type: "tr_ord_reveal", + type: 'tr_ord_reveal', script: P.apply(Script, P.coders.match([OutOrdinalReveal])).encode({ - type: "tr_ord_reveal", + type: 'tr_ord_reveal', pubkey, inscriptions, }),