├── .env.example ├── .gitignore ├── Delegate_inscription.ts ├── Encipher_Decipher_Runestone.ts ├── HTML_Child_Inscription.ts ├── Image_Child_Inscription.ts ├── Parent_Inscription.ts ├── RUNE_airdrop.ts ├── RUNE_etching.ts ├── RUNE_minting.ts ├── RUNE_transfer.ts ├── Recursive_Rune.ts ├── Reinscription.ts ├── Runestone.ts ├── Text_Child_Inscription.ts ├── UTXO_merge.ts ├── UTXO_send.ts ├── UTXO_split.ts ├── config └── network.config.ts ├── controller ├── utxo.merge.controller.ts ├── utxo.send.controller.ts └── utxo.split.controller.ts ├── package-lock.json ├── package.json ├── readme.md ├── tsconfig.json └── utils ├── SeedWallet.ts ├── WIFWallet.ts └── mempool.ts /.env.example: -------------------------------------------------------------------------------- 1 | MNEMONIC = 2 | PRIVATE_KEY = -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env -------------------------------------------------------------------------------- /Delegate_inscription.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Transaction, 3 | script, 4 | Psbt, 5 | initEccLib, 6 | networks, 7 | Signer as BTCSigner, 8 | crypto, 9 | payments, 10 | opcodes, 11 | address as Address 12 | } from "bitcoinjs-lib"; 13 | 14 | import { Taptree } from "bitcoinjs-lib/src/types"; 15 | import { ECPairFactory, ECPairAPI } from "ecpair"; 16 | import ecc from "@bitcoinerlab/secp256k1"; 17 | import axios, { AxiosResponse } from "axios"; 18 | import networkConfig from "config/network.config"; 19 | import { WIFWallet } from 'utils/WIFWallet' 20 | import { SeedWallet } from "utils/SeedWallet"; 21 | import cbor from 'cbor' 22 | //test 23 | const network = networks.testnet; 24 | // const network = networks.bitcoin; 25 | 26 | initEccLib(ecc as any); 27 | const ECPair: ECPairAPI = ECPairFactory(ecc); 28 | 29 | // const seed: string = process.env.MNEMONIC as string; 30 | // const networkType: string = networkConfig.networkType; 31 | // const wallet = new SeedWallet({ networkType: networkType, seed: seed }); 32 | 33 | const privateKey: string = process.env.PRIVATE_KEY as string; 34 | const networkType: string = networkConfig.networkType; 35 | const wallet = new WIFWallet({ networkType: networkType, privateKey: privateKey }); 36 | 37 | const txhash: string = 'dd476bdd2039161c50196d9a8f8412d56be3710de8bda5de4674429e5f8e3649'; 38 | const txidBuffer = Buffer.from(txhash, 'hex'); 39 | const delegateBuffer = txidBuffer.reverse(); 40 | 41 | const receiveAddress: string = "tb1ppx220ln489s5wqu8mqgezm7twwpj0avcvle3vclpdkpqvdg3mwqsvydajn"; 42 | const transaction_fee = 28000; 43 | 44 | 45 | export function createdelegateInscriptionTapScript(): Array { 46 | 47 | const keyPair = wallet.ecPair; 48 | const delegateOrdinalStacks: any = [ 49 | toXOnly(keyPair.publicKey), 50 | opcodes.OP_CHECKSIG, 51 | opcodes.OP_FALSE, 52 | opcodes.OP_IF, 53 | Buffer.from("ord", "utf8"), 54 | 1, 55 | 11, 56 | delegateBuffer, 57 | opcodes.OP_ENDIF, 58 | ]; 59 | return delegateOrdinalStacks; 60 | } 61 | 62 | async function delegateInscribe() { 63 | const keyPair = wallet.ecPair; 64 | const delegateOrdinalStack = createdelegateInscriptionTapScript(); 65 | 66 | const ordinal_script = script.compile(delegateOrdinalStack); 67 | 68 | const scriptTree: Taptree = { 69 | output: ordinal_script, 70 | }; 71 | 72 | const redeem = { 73 | output: ordinal_script, 74 | redeemVersion: 192, 75 | }; 76 | 77 | const ordinal_p2tr = payments.p2tr({ 78 | internalPubkey: toXOnly(keyPair.publicKey), 79 | network, 80 | scriptTree, 81 | redeem, 82 | }); 83 | 84 | const address = ordinal_p2tr.address ?? ""; 85 | console.log("send coin to address", address); 86 | 87 | const utxos = await waitUntilUTXO(address as string); 88 | console.log(`Using UTXO ${utxos[0].txid}:${utxos[0].vout}`); 89 | 90 | const psbt = new Psbt({ network }); 91 | 92 | psbt.addInput({ 93 | hash: utxos[0].txid, 94 | index: utxos[0].vout, 95 | tapInternalKey: toXOnly(keyPair.publicKey), 96 | witnessUtxo: { value: utxos[0].value, script: ordinal_p2tr.output! }, 97 | tapLeafScript: [ 98 | { 99 | leafVersion: redeem.redeemVersion, 100 | script: redeem.output, 101 | controlBlock: ordinal_p2tr.witness![ordinal_p2tr.witness!.length - 1], 102 | }, 103 | ], 104 | }); 105 | 106 | 107 | const change = utxos[0].value - 546 - transaction_fee; 108 | 109 | psbt.addOutput({ 110 | address: receiveAddress, //Destination Address 111 | value: 546, 112 | }); 113 | 114 | psbt.addOutput({ 115 | address: receiveAddress, // Change address 116 | value: change, 117 | }); 118 | 119 | await signAndSend(keyPair, psbt); 120 | } 121 | 122 | delegateInscribe() 123 | 124 | export async function signAndSend( 125 | keypair: BTCSigner, 126 | psbt: Psbt, 127 | ) { 128 | psbt.signInput(0, keypair); 129 | psbt.finalizeAllInputs() 130 | const tx = psbt.extractTransaction(); 131 | 132 | console.log(tx.virtualSize()) 133 | console.log(tx.toHex()) 134 | 135 | const txid = await broadcast(tx.toHex()); 136 | console.log(`Success! Txid is ${txid}`); 137 | } 138 | 139 | export async function waitUntilUTXO(address: string) { 140 | return new Promise((resolve, reject) => { 141 | let intervalId: any; 142 | const checkForUtxo = async () => { 143 | try { 144 | const response: AxiosResponse = await blockstream.get( 145 | `/address/${address}/utxo` 146 | ); 147 | const data: IUTXO[] = response.data 148 | ? JSON.parse(response.data) 149 | : undefined; 150 | console.log(data); 151 | if (data.length > 0) { 152 | resolve(data); 153 | clearInterval(intervalId); 154 | } 155 | } catch (error) { 156 | reject(error); 157 | clearInterval(intervalId); 158 | } 159 | }; 160 | intervalId = setInterval(checkForUtxo, 4000); 161 | }); 162 | } 163 | export async function getTx(id: string): Promise { 164 | const response: AxiosResponse = await blockstream.get( 165 | `/tx/${id}/hex` 166 | ); 167 | return response.data; 168 | } 169 | const blockstream = new axios.Axios({ 170 | baseURL: `https://mempool.space/testnet/api`, 171 | // baseURL: `https://mempool.space/api`, 172 | }); 173 | export async function broadcast(txHex: string) { 174 | const response: AxiosResponse = await blockstream.post("/tx", txHex); 175 | return response.data; 176 | } 177 | function tapTweakHash(pubKey: Buffer, h: Buffer | undefined): Buffer { 178 | return crypto.taggedHash( 179 | "TapTweak", 180 | Buffer.concat(h ? [pubKey, h] : [pubKey]) 181 | ); 182 | } 183 | function toXOnly(pubkey: Buffer): Buffer { 184 | return pubkey.subarray(1, 33); 185 | } 186 | function tweakSigner(signer: any, opts: any = {}) { 187 | let privateKey = signer.privateKey; 188 | if (!privateKey) { 189 | throw new Error('Private key is required for tweaking signer!'); 190 | } 191 | if (signer.publicKey[0] === 3) { 192 | privateKey = ecc.privateNegate(privateKey); 193 | } 194 | const tweakedPrivateKey = ecc.privateAdd(privateKey, tapTweakHash(toXOnly(signer.publicKey), opts.tweakHash)); 195 | if (!tweakedPrivateKey) { 196 | throw new Error('Invalid tweaked private key!'); 197 | } 198 | return ECPair.fromPrivateKey(Buffer.from(tweakedPrivateKey), { 199 | network: opts.network, 200 | }); 201 | } 202 | interface IUTXO { 203 | txid: string; 204 | vout: number; 205 | status: { 206 | confirmed: boolean; 207 | block_height: number; 208 | block_hash: string; 209 | block_time: number; 210 | }; 211 | value: number; 212 | } -------------------------------------------------------------------------------- /Encipher_Decipher_Runestone.ts: -------------------------------------------------------------------------------- 1 | import { Runestone, Transaction } from '@ordjs/runestone'; 2 | 3 | // See https://mempool.space/tx/2bb85f4b004be6da54f766c17c1e855187327112c231ef2ff35ebad0ea67c69e 4 | const tx: any = { 5 | output: [{ 6 | // // OP_RETURN OP_PUSHNUM_13 ... 7 | script_pubkey: '00dbfeab01f711904e020000904e030000904e040000904e05', 8 | value: 0, 9 | }], 10 | }; 11 | 12 | const runestone = Runestone.decipher(tx); 13 | // runestone.divisibility => 2 14 | // runestone.premine => 11000000000 15 | // runestone.symbol => ᚠ 16 | // runestone.terms.amount => 100 17 | console.log(runestone) -------------------------------------------------------------------------------- /HTML_Child_Inscription.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Transaction, 3 | script, 4 | Psbt, 5 | initEccLib, 6 | networks, 7 | Signer as BTCSigner, 8 | crypto, 9 | payments, 10 | opcodes, 11 | address as Address 12 | } from "bitcoinjs-lib"; 13 | import { Taptree } from "bitcoinjs-lib/src/types"; 14 | import { ECPairFactory, ECPairAPI } from "ecpair"; 15 | import ecc from "@bitcoinerlab/secp256k1"; 16 | import axios, { AxiosResponse } from "axios"; 17 | import networkConfig from "config/network.config"; 18 | import { WIFWallet } from 'utils/WIFWallet' 19 | import { SeedWallet } from "utils/SeedWallet"; 20 | import cbor from 'cbor'; 21 | 22 | //test 23 | const network = networks.testnet; 24 | // const network = networks.bitcoin; 25 | 26 | initEccLib(ecc as any); 27 | const ECPair: ECPairAPI = ECPairFactory(ecc); 28 | 29 | export const contentBuffer = (content: string) => { 30 | return Buffer.from(content, 'utf8') 31 | } 32 | // const seed: string = process.env.MNEMONIC as string; 33 | // const networkType: string = networkConfig.networkType; 34 | // const wallet = new SeedWallet({ networkType: networkType, seed: seed }); 35 | 36 | const privateKey: string = process.env.PRIVATE_KEY as string; 37 | const networkType: string = networkConfig.networkType; 38 | const wallet = new WIFWallet({ networkType: networkType, privateKey: privateKey }); 39 | 40 | // input data 41 | const txhash: string = '66505a1520d9b9a1b309ecef17a68adb5de166e0a7108a2be6d9b46d85fd951f'; 42 | const memeType: string = 'text/html;charset=utf-8'; 43 | const metaProtocol: Buffer = Buffer.concat([Buffer.from("parcel.bitmap", "utf8")]); 44 | const receiveAddress: string = 'tb1ppx220ln489s5wqu8mqgezm7twwpj0avcvle3vclpdkpqvdg3mwqsvydajn'; 45 | const metadata = { 46 | 'type': 'Bitmap', 47 | 'description': 'Bitmap Community Parent Ordinal' 48 | } 49 | const fee = 500000; 50 | const parentInscriptionTXID: string = 'd9b95d549219eebcd1be0360f41c7164c4ad040b716475630154f08263ab2fdf'; 51 | const contentBufferData: Buffer = contentBuffer(` 52 | 53 | 54 | Welcome to Bitmap Community! 55 | 144 | 145 | 146 | 147 |
148 |

Bitmap Community

149 |
150 | 151 |
152 |
153 |
154 |

Introduction Bitmap Community

155 |
156 |
157 |
158 | Profile Image 159 |

Introduction

160 |

Hello, Bitmap Community Users! It's just our test for our next projects related Recursive.

161 |

The start of a new season is upon us, and what better time to usher in the age of Bitmap Community than with the introduction of a brand new way to interact with your Bitmap on-chain? That's right, we're about to dive into functionality!

162 |

I'm thrilled to introduce the Bitmap Communityand the Bitmap Community Metaprotocol. These two novel concepts form the bedrock of content management upon Bitmap land. These additions empower creators to carve out their own vision of Bitmap entirely on-chain. But let's not put the cart before the horse! What exactly are we talking about here?

163 |

This guide will cover the essentials of creating, linking, and managing your content library. You'll discover a novel approach to indexing your content through parent/child inscriptions and learn how to create an index.html page for your bitmap similar to traditional websites, through the use of the Bitmap Community Metaprotocol.

164 |

It should be noted that these additions do not directly tie in with the Bitmap's block data, however it provides a method to inscribe data unto your Bitmap that can be universally read by anyone applying the standard.

165 |
166 | 167 |
168 |

Understanding The Bitmap Community Projects

169 |

The Bitmap Community Projects encompasses all child inscriptions associated with your Bitmap. This can be expanded to the children of those children, to form a full family tree file system with your bitmap at the root. It stands as a straightforward, decentralized method to manage content stemming from your Bitmap, with provenance and immutability at its core. Any file can be a child inscription. To compile this content, and any content accessible through Ordinals, we craft an index page for your Bitmap by invoking the Metaprotocol index.bitmap.

170 |
171 | 172 |
173 |

Creating Your Bitmap Parcel

174 |

Mirroring the role of the index.html file in traditional web development, the index.bitmap Metaprotocol anchors itself as the welcoming front page of your Bitmap. This section details the process of earmarking your HTML document as your Bitmap's primary index: forge a child inscription to your Bitmap and label it with the Metaprotocol tag Bitmap Community. This act ensures that explorers can effortlessly locate the most recent HTML index file connected as a child to your Bitmap.

175 |
176 | 177 |
178 |

Our Bitmap Community History

179 |

And there you have it, friends! A canvas waiting for your brush, a stage eager for your performance. As we close this guide, remember that each step you take in inscribing content to your Bitmap is a stroke in the grand mural of Bitcoin and into the future. So go ahead, make your mark with confidence, knowing that your contributions are etched in the annals of the blockchain, immutable and celebrated. May your creations inspire and your Bitmap flourish. Until next time, you know where to find me

180 | Bitoshi's Signature 181 |

Everything here is strictly experimental, of course.

182 |
183 |
184 |
185 |
186 | 187 | 188 | 189 | `); 190 | 191 | const revealtxIDBuffer = Buffer.from(parentInscriptionTXID, 'hex'); 192 | const inscriptionBuffer = revealtxIDBuffer.reverse(); 193 | const pointer1: number = 546 * 1; 194 | const pointer2: number = 546 * 2; 195 | const pointer3: number = 546 * 3; 196 | const pointerBuffer1: Buffer = Buffer.from(pointer1.toString(16).padStart(4, '0'), 'hex').reverse(); 197 | const pointerBuffer2: Buffer = Buffer.from(pointer2.toString(16).padStart(4, '0'), 'hex').reverse(); 198 | const pointerBuffer3: Buffer = Buffer.from(pointer3.toString(16).padStart(4, '0'), 'hex').reverse(); 199 | const metadataBuffer = cbor.encode(metadata); 200 | 201 | const splitBuffer = (buffer: Buffer, chunkSize: number) => { 202 | let chunks = []; 203 | for (let i = 0; i < buffer.length; i += chunkSize) { 204 | const chunk = buffer.subarray(i, i + chunkSize); 205 | chunks.push(chunk); 206 | } 207 | return chunks; 208 | }; 209 | const contentBufferArray: Array = splitBuffer(contentBufferData, 400) 210 | 211 | export function createChildInscriptionTapScript(): Array { 212 | 213 | const keyPair = wallet.ecPair; 214 | let childOrdinalStacks: any = [ 215 | toXOnly(keyPair.publicKey), 216 | opcodes.OP_CHECKSIG, 217 | opcodes.OP_FALSE, 218 | opcodes.OP_IF, 219 | Buffer.from("ord", "utf8"), 220 | 1, 221 | 1, 222 | Buffer.concat([Buffer.from(memeType, "utf8")]), 223 | 1, 224 | 2, 225 | pointerBuffer1, 226 | 1, 227 | 3, 228 | inscriptionBuffer, 229 | 1, 230 | 5, 231 | metadataBuffer, 232 | 1, 233 | 7, 234 | metaProtocol, 235 | opcodes.OP_0 236 | ]; 237 | contentBufferArray.forEach((item: Buffer) => { 238 | childOrdinalStacks.push(item) 239 | }) 240 | childOrdinalStacks.push(opcodes.OP_ENDIF) 241 | 242 | return childOrdinalStacks; 243 | } 244 | 245 | async function childInscribe() { 246 | const keyPair = wallet.ecPair; 247 | const childOrdinalStack = createChildInscriptionTapScript(); 248 | 249 | const ordinal_script = script.compile(childOrdinalStack); 250 | 251 | const scriptTree: Taptree = { 252 | output: ordinal_script, 253 | }; 254 | 255 | const redeem = { 256 | output: ordinal_script, 257 | redeemVersion: 192, 258 | }; 259 | 260 | const ordinal_p2tr = payments.p2tr({ 261 | internalPubkey: toXOnly(keyPair.publicKey), 262 | network, 263 | scriptTree, 264 | redeem, 265 | }); 266 | 267 | const address = ordinal_p2tr.address ?? ""; 268 | console.log("send coin to address", address); 269 | 270 | const utxos = await waitUntilUTXO(address as string); 271 | 272 | const psbt = new Psbt({ network }); 273 | const parentInscriptionUTXO = { 274 | txid: txhash, 275 | vout: 0, 276 | value: 546 277 | } 278 | psbt.addInput({ 279 | hash: parentInscriptionUTXO.txid, 280 | index: parentInscriptionUTXO.vout, 281 | witnessUtxo: { 282 | value: parentInscriptionUTXO.value, 283 | script: wallet.output, 284 | }, 285 | tapInternalKey: toXOnly(keyPair.publicKey), 286 | }); 287 | 288 | psbt.addInput({ 289 | hash: utxos[0].txid, 290 | index: utxos[0].vout, 291 | tapInternalKey: toXOnly(keyPair.publicKey), 292 | witnessUtxo: { value: utxos[0].value, script: ordinal_p2tr.output! }, 293 | tapLeafScript: [ 294 | { 295 | leafVersion: redeem.redeemVersion, 296 | script: redeem.output, 297 | controlBlock: ordinal_p2tr.witness![ordinal_p2tr.witness!.length - 1], 298 | }, 299 | ], 300 | }); 301 | 302 | 303 | const change = utxos[0].value - 546 * 2 - fee; 304 | 305 | psbt.addOutput({ 306 | address: receiveAddress, //Destination Address 307 | value: 546, 308 | }); 309 | 310 | psbt.addOutput({ 311 | address: receiveAddress, //Destination Address 312 | value: 546, 313 | }); 314 | 315 | psbt.addOutput({ 316 | address: receiveAddress, // Change address 317 | value: change, 318 | }); 319 | 320 | await signAndSend(keyPair, psbt); 321 | } 322 | 323 | childInscribe() 324 | 325 | export async function signAndSend( 326 | keypair: BTCSigner, 327 | psbt: Psbt, 328 | ) { 329 | const signer = tweakSigner(keypair, { network }) 330 | psbt.signInput(0, signer); 331 | psbt.signInput(1, keypair); 332 | psbt.finalizeAllInputs() 333 | const tx = psbt.extractTransaction(); 334 | 335 | console.log(tx.virtualSize()) 336 | console.log(tx.toHex()); 337 | 338 | // const txid = await broadcast(tx.toHex()); 339 | // console.log(`Success! Txid is ${txid}`); 340 | } 341 | 342 | export async function waitUntilUTXO(address: string) { 343 | return new Promise((resolve, reject) => { 344 | let intervalId: any; 345 | const checkForUtxo = async () => { 346 | try { 347 | const response: AxiosResponse = await blockstream.get( 348 | `/address/${address}/utxo` 349 | ); 350 | const data: IUTXO[] = response.data 351 | ? JSON.parse(response.data) 352 | : undefined; 353 | console.log(data); 354 | if (data.length > 0) { 355 | resolve(data); 356 | clearInterval(intervalId); 357 | } 358 | } catch (error) { 359 | reject(error); 360 | clearInterval(intervalId); 361 | } 362 | }; 363 | intervalId = setInterval(checkForUtxo, 4000); 364 | }); 365 | } 366 | 367 | export async function getTx(id: string): Promise { 368 | const response: AxiosResponse = await blockstream.get( 369 | `/tx/${id}/hex` 370 | ); 371 | return response.data; 372 | } 373 | 374 | const blockstream = new axios.Axios({ 375 | baseURL: `https://mempool.space/testnet/api`, 376 | // baseURL: `https://mempool.space/api`, 377 | }); 378 | 379 | export async function broadcast(txHex: string) { 380 | const response: AxiosResponse = await blockstream.post("/tx", txHex); 381 | return response.data; 382 | } 383 | 384 | function tapTweakHash(pubKey: Buffer, h: Buffer | undefined): Buffer { 385 | return crypto.taggedHash( 386 | "TapTweak", 387 | Buffer.concat(h ? [pubKey, h] : [pubKey]) 388 | ); 389 | } 390 | 391 | function toXOnly(pubkey: Buffer): Buffer { 392 | return pubkey.subarray(1, 33); 393 | } 394 | 395 | function tweakSigner(signer: any, opts: any = {}) { 396 | let privateKey = signer.privateKey; 397 | if (!privateKey) { 398 | throw new Error('Private key is required for tweaking signer!'); 399 | } 400 | if (signer.publicKey[0] === 3) { 401 | privateKey = ecc.privateNegate(privateKey); 402 | } 403 | const tweakedPrivateKey = ecc.privateAdd(privateKey, tapTweakHash(toXOnly(signer.publicKey), opts.tweakHash)); 404 | if (!tweakedPrivateKey) { 405 | throw new Error('Invalid tweaked private key!'); 406 | } 407 | return ECPair.fromPrivateKey(Buffer.from(tweakedPrivateKey), { 408 | network: opts.network, 409 | }); 410 | } 411 | 412 | interface IUTXO { 413 | txid: string; 414 | vout: number; 415 | status: { 416 | confirmed: boolean; 417 | block_height: number; 418 | block_hash: string; 419 | block_time: number; 420 | }; 421 | value: number; 422 | } 423 | 424 | -------------------------------------------------------------------------------- /Image_Child_Inscription.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Transaction, 3 | script, 4 | Psbt, 5 | initEccLib, 6 | networks, 7 | Signer as BTCSigner, 8 | crypto, 9 | payments, 10 | opcodes, 11 | address as Address 12 | } from "bitcoinjs-lib"; 13 | import { Taptree } from "bitcoinjs-lib/src/types"; 14 | import { ECPairFactory, ECPairAPI } from "ecpair"; 15 | import ecc from "@bitcoinerlab/secp256k1"; 16 | import axios, { AxiosResponse } from "axios"; 17 | import networkConfig from "config/network.config"; 18 | import { WIFWallet } from 'utils/WIFWallet' 19 | import { SeedWallet } from "utils/SeedWallet"; 20 | import cbor from 'cbor'; 21 | 22 | //test 23 | const network = networks.testnet; 24 | // const network = networks.bitcoin; 25 | 26 | initEccLib(ecc as any); 27 | const ECPair: ECPairAPI = ECPairFactory(ecc); 28 | 29 | // const seed: string = process.env.MNEMONIC as string; 30 | // const networkType: string = networkConfig.networkType; 31 | // const wallet = new SeedWallet({ networkType: networkType, seed: seed }); 32 | 33 | const privateKey: string = process.env.PRIVATE_KEY as string; 34 | const networkType: string = networkConfig.networkType; 35 | const wallet = new WIFWallet({ networkType: networkType, privateKey: privateKey }); 36 | 37 | const txhash: string = 'd9b95d549219eebcd1be0360f41c7164c4ad040b716475630154f08263ab2fdf'; 38 | const txidBuffer = Buffer.from(txhash, 'hex'); 39 | const inscriptionBuffer = txidBuffer.reverse(); 40 | const memeType: string = 'image/png'; 41 | const pointer1: number = 546 * 1; 42 | const pointer2: number = 546 * 2; 43 | const pointer3: number = 546 * 3; 44 | const pointerBuffer1: Buffer = Buffer.from(pointer1.toString(16).padStart(4, '0'), 'hex').reverse(); 45 | const pointerBuffer2: Buffer = Buffer.from(pointer2.toString(16).padStart(4, '0'), 'hex').reverse(); 46 | const pointerBuffer3: Buffer = Buffer.from(pointer3.toString(16).padStart(4, '0'), 'hex').reverse(); 47 | const metaProtocol: Buffer = Buffer.concat([Buffer.from("parcel.bitmap", "utf8")]); 48 | const contentBufferData1 = Buffer.from('0.364972.bitmap', 'utf8') 49 | const receiveAddress: string = 'tb1ppx220ln489s5wqu8mqgezm7twwpj0avcvle3vclpdkpqvdg3mwqsvydajn'; 50 | const metadata = { 51 | 'type': 'Bitmap', 52 | 'description': 'Bitmap Community Parent Ordinal' 53 | } 54 | const metadataBuffer = cbor.encode(metadata); 55 | const fee = 1200000; 56 | 57 | const splitBuffer = (buffer: Buffer, chunkSize: number) => { 58 | let chunks = []; 59 | for (let i = 0; i < buffer.length; i += chunkSize) { 60 | const chunk = buffer.subarray(i, i + chunkSize); 61 | chunks.push(chunk); 62 | } 63 | return chunks; 64 | }; 65 | 66 | export const contentBuffer = (content: string) => { 67 | return Buffer.from('89504E470D0A1A0A0000000D494844520000057C0000019408060000008629EAA70000000467414D410000B18F0BFC610500000A4969434350735247422049454336313936362D322E31000048899D53775893F7163EDFF7650F5642D8F0B1976C81002223AC08C81059A21092006184101240C585880A561415119C4855C482D50A489D88E2A028B867418A885A8B555C38EE1FDCA7B57D7AEFEDEDFBD7FBBCE79CE7FCCE79CF0F8011122691E6A26A003952853C3AD81F8F4F48C4C9BD80021548E0042010E6CBC26705C50000F00379787E74B03FFC01AF6F00020070D52E2412C7E1FF83BA50265700209100E02212E70B01905200C82E54C81400C81800B053B3640A009400006C797C422200AA0D00ECF4493E0500D8A993DC1700D8A21CA908008D0100992847240240BB00605581522C02C0C200A0AC40222E04C0AE018059B632470280BD0500768E58900F4060008099422CCC0020380200431E13CD03204C03A030D2BFE0A95F7085B8480100C0CB95CD974BD23314B895D01A77F2F0E0E221E2C26CB142611729106609E4229C979B231348E7034CCE0C00001AF9D1C1FE383F90E7E6E4E1E666E76CEFF4C5A2FE6BF06F223E21F1DFFEBC8C020400104ECFEFDA5FE5E5D60370C701B075BF6BA95B00DA560068DFF95D33DB09A05A0AD07AF98B7938FC401E9EA150C83C1D1C0A0B0BED2562A1BD30E38B3EFF33E16FE08B7EF6FC401EFEDB7AF000719A4099ADC0A383FD71616E76AE528EE7CB0442316EF7E723FEC7857FFD8E29D1E234B15C2C158AF15889B850224DC779B952914421C995E212E97F32F11F96FD0993770D00AC864FC04EB607B5CB6CC07EEE01028B0E58D27600407EF32D8C1A0B91001067343279F7000093BFF98F402B0100CD97A4E30000BCE8185CA894174CC608000044A0812AB041070CC114ACC00E9CC11DBCC01702610644400C24C03C104206E4801C0AA11896411954C03AD804B5B0031AA0119AE110B4C131380DE7E0125C81EB70170660189EC218BC86090441C8081361213A8811628ED822CE0817998E04226148349280A420E988145122C5C872A402A9426A915D4823F22D7214398D5C40FA90DBC820328AFC8ABC47319481B25103D4027540B9A81F1A8AC6A073D174340F5D8096A26BD11AB41E3D80B6A2A7D14BE87574007D8A8E6380D1310E668CD9615C8C87456089581A26C71663E55835568F35631D583776151BC09E61EF0824028B8013EC085E8410C26C82909047584C5843A825EC23B412BA085709838431C2272293A84FB4257A12F9C478623AB1905846AC26EE211E219E255E270E135F9348240EC992E44E0A21259032490B496B48DB482DA453A43ED210699C4C26EB906DC9DEE408B280AC209791B7900F904F92FBC9C3E4B7143AC588E24C09A22452A494124A35653FE504A59F324299A0AA51CDA99ED408AA883A9F5A496DA076502F5387A91334759A25CD9B1643CBA42DA3D5D09A696769F7682FE974BA09DD831E4597D097D26BE807E9E7E983F4770C0D860D83C7486228196B197B19A718B7192F994CA605D39799C85430D7321B9967980F986F55582AF62A7C1591CA12953A9556957E95E7AA545573553FD579AA0B54AB550FAB5E567DA64655B350E3A909D416ABD5A91D55BBA936AECE5277528F50CF515FA3BE5FFD82FA630DB2868546A08648A35463B7C6198D2116C63265F15842D6725603EB2C6B984D625BB2F9EC4C7605FB1B762F7B4C534373AA66AC6691669DE671CD010EC6B1E0F039D99C4ACE21CE0DCE7B2D032D3F2DB1D66AAD66AD7EAD37DA7ADABEDA62ED72ED16EDEBDAEF75709D409D2C9DF53A6D3AF77509BA36BA51BA85BADB75CFEA3ED363EB79E909F5CAF50EE9DDD147F56DF4A3F517EAEFD6EFD11F373034083690196C313863F0CC9063E86B9869B8D1F084E1A811CB68BA91C468A3D149A327B826EE8767E33578173E66AC6F1C62AC34DE65DC6B3C61626932DBA4C4A4C5E4BE29CD946B9A66BAD1B4D374CCCCC82CDCACD8ACC9EC8E39D59C6B9E61BED9BCDBFC8D85A5459CC54A8B368BC796DA967CCB05964D96F7AC98563E567956F556D7AC49D65CEB2CEB6DD6576C501B579B0C9B3A9BCBB6A8AD9BADC4769B6DDF14E2148F29D229F5536EDA31ECFCEC0AEC9AEC06ED39F661F625F66DF6CF1DCC1C121DD63B743B7C727475CC766C70BCEBA4E134C3A9C4A9C3E957671B67A1739DF33517A64B90CB1297769717536DA78AA76E9F7ACB95E51AEEBAD2B5D3F5A39BBB9BDCADD96DD4DDCC3DC57DABFB4D2E9B1BC95DC33DEF41F4F0F758E271CCE39DA79BA7C2F390E72F5E765E595EFBBD1E4FB39C269ED6306DC8DBC45BE0BDCB7B603A3E3D65FACEE9033EC63E029F7A9F87BEA6BE22DF3DBE237ED67E997E07FC9EFB3BFACBFD8FF8BFE179F216F14E056001C101E501BD811A81B3036B031F049904A50735058D05BB062F0C3E15420C090D591F72936FC017F21BF96333DC672C9AD115CA089D155A1BFA30CC264C1ED6118E86CF08DF107E6FA6F94CE9CCB60888E0476C88B81F69199917F97D14292A32AA2EEA51B453747174F72CD6ACE459FB67BD8EF18FA98CB93BDB6AB6727667AC6A6C526C63EC9BB880B8AAB8817887F845F1971274132409ED89E4C4D8C43D89E37302E76C9A339CE49A54967463AEE5DCA2B917E6E9CECB9E773C593559907C3885981297B23FE5832042502F184FE5A76E4D1D13F2849B854F45BEA28DA251B1B7B84A3C92E69D5695F638DD3B7D43FA68864F4675C633094F522B79911992B923F34D5644D6DEACCFD971D92D39949C949CA3520D6996B42BD730B728B74F662B2B930DE479E66DCA1B9387CAF7E423F973F3DB156C854CD1A3B452AE500E164C2FA82B785B185B78B848BD485AD433DF66FEEAF9230B82167CBD90B050B8B0B3D8B87859F1E022BF45BB16238B5317772E315D52BA647869F0D27DCB68CBB296FD50E2585255F26A79DCF28E5283D2A5A5432B82573495A994C9CB6EAEF45AB9631561956455EF6A97D55B567F2A17955FAC70ACA8AEF8B046B8E6E2574E5FD57CF5796DDADADE4AB7CAEDEB48EBA4EB6EACF759BFAF4ABD6A41D5D086F00DAD1BF18DE51B5F6D4ADE74A17A6AF58ECDB4CDCACD03356135ED5BCCB6ACDBF2A136A3F67A9D7F5DCB56FDADABB7BED926DAD6BFDD777BF30E831D153BDEEF94ECBCB52B78576BBD457DF56ED2EE82DD8F1A621BBABFE67EDDB847774FC59E8F7BA57B07F645EFEB6A746F6CDCAFBFBFB2096D52368D1E483A70E59B806FDA9BED9A77B5705A2A0EC241E5C127DFA67C7BE350E8A1CEC3DCC3CDDF997FB7F508EB48792BD23ABF75AC2DA36DA03DA1BDEFE88CA39D1D5E1D47BEB7FF7EEF31E36375C7358F579EA09D283DF1F9E48293E3A764A79E9D4E3F3DD499DC79F74CFC996B5D515DBD6743CF9E3F1774EE4CB75FF7C9F3DEE78F5DF0BC70F422F762DB25B74BAD3DAE3D477E70FDE148AF5B6FEB65F7CBED573CAE74F44DEB3BD1EFD37FFA6AC0D573D7F8D72E5D9F79BDEFC6EC1BB76E26DD1CB825BAF5F876F6ED17770AEE4CDC5D7A8F78AFFCBEDAFDEA07FA0FEA7FB4FEB165C06DE0F860C060CFC3590FEF0E09879EFE94FFD387E1D247CC47D52346238D8F9D1F1F1B0D1ABDF264CE93E1A7B2A713CFCA7E56FF79EB73ABE7DFFDE2FB4BCF58FCD8F00BF98BCFBFAE79A9F372EFABA9AF3AC723C71FBCCE793DF1A6FCADCEDB7DEFB8EFBADFC7BD1F9928FC40FE50F3D1FA63C7A7D04FF73EE77CFEFC2FF784F3FB2D4738CF000000206348524D00007A26000080840000FA00000080E8000075300000EA6000003A98000017709CBA513C000000097048597300000EC400000EC401952B0E1B0000479A49444154789CEDDD49D2DE38BA1E50BAA2E69EA456E3F578555E8F57234DBC82F2202FAF28247BA279019C13A19032A5FF6B4810CD4310F81FFFF9CF7F160000000000FAF7AFD61F0000000000803C04BE00000000008310F802000000000C42E00B000000003008812F00000000C02004BE00000000008310F802000000000C42E00B000000003008812F00000000C02004BE00000000008310F802000000000C42E00B000000003008812F00000000C02004BE00000000008310F802000000000C42E00B000000003008812F00000000C02004BE00000000008310F802000000000C42E00B000000003008812F00000000C02004BE00000000008310F802000000000C42E00B000000003008812F00000000C02004BE00000000008310F802000000000C42E00B000000003008812F00000000C02004BE00000000008310F802000000000C42E00B000000003008812F00000000C02004BE00000000008310F802000000000C42E00B000000003008812F00000000C02004BE00000000008310F802000000000C42E00B000000003008812F00000000C02004BE00000000008310F802000000000C42E00B000000003008812F00000000C02004BE00000000008310F802000000000C42E00B000000003008812F00000000C02004BE00000000008310F802000000000C42E00B000000003008812F00000000C02004BE00000000008310F802000000000C42E00B000000003008812F00000000C02004BE00000000008310F802000000000C42E00B000000003008812F00000000C02004BE00000000008310F802000000000C42E00B000000003008812F00000000C02004BE00000000008310F802000000000C42E00B000000003008812F00000000C02004BE00000000008310F802000000000C42E00B000000003008812F00000000C02004BE00000000008310F802000000000C42E00B000000003008812F00000000C02004BE00000000008310F802000000000C42E00B000000003008812F00000000C02004BE00000000008310F802000000000C42E00B000000003008812F00000000C02004BE00000000008310F802000000000C42E00B000000003008812F00000000C02004BE00000000008310F802000000000C42E00B000000003008812F00000000C02004BE00000000008310F802000000000C42E00B000000003008812F00000000C02004BE000000000083F877EB0FC03BFFF77FFFCFD61FE1CACFFFFAFD47D34F010000004068FFEBFFFCBFD61F6128025F7E26FF9D23A0FD99FC59E80B000000001558D2616E6B30FBD77FFD5AFF5F1A02E7784D00000000A03081EFBCB6C1ECB2F9F39790F6E83501000000800A04BE733B0A63BF84B4473F6B962F000000001426F0E54CAE90D62C5F00000000A840E0CB911221AD59BE00C0887E2EDFF7410000802C04BED462962F00309A6DC8BBB701AE00180080EAFEDDFA03D0D4AFE53A88FDB92CCB8F1BAF6540030044B5D74FF9B1DCEFE79CBDE6DE66B5BF0EDE1300008A33C3775E7706374F67E5DEF9F7063F00400B7F6D7E2DCBB7B077FB9A67EF050000D5097CA9C9C00700A86DEF66F35F277FF7C4AF97EF0F0000C5087C010018DDDE4DE7AF37A24B3C2D0500009F097CB9C3CC1400A0677766E2467C6D0000784CE0CB953B8F3C0A840180A8BEAED3DBEAB50100E015812F3918EC0000D11DCDC4AD71E3DACD710000AA11F872D759A86B1003004476D48FC9B5C6EED9B20ED6F10500A02A812F779D85BA4F67F80A88018048BEF44DAEFA41D6F80500A02A812F77062157EBF83E192499E502004452A36F62F92B0000AA11F8CEEDC9E0E36A3024C8050066767613DDD34D00005423F025178F2B0200D195EAAF9CDD44BF7A520A0000B212F89283C7140180E85AF7575ABF3F000093F877EB0F00005040CECD46010000BA21F09DD7CFE5DD8077EFE73CA208D0871942D0ED77DC5B5FFE57F26F46F9DE7CF7B66FB4F56BB1AF0100008D59D2615E6F0634470318836580D87E2EBFC3ACF5D7B2F3FBFAEF7AB57EF6BF96E336EBAFE4EF7BFEBEBCB3B78E6F8E90567F0800801004BE7C65A0CC887E26BF43CFB641EFF6FF6D7F5FADFFAEB7E0771B683F21F49D4FCB5056398BEDE7C12F0080EE58D2811C7486E9DD5150940664666FD193B56EFE91FCF76A0D3B8F96385883DF9ECAFD9BF6E8AF657FC627E4A49CC575B60CCCB67EECA92EA4BEDEDA4B000627F09DDBDA817DBADE5CDAA1594301E84D3AF3F167F27747FF1EA24BCBEF76A983D4F6FFADE1468FF5FAD747F25DDF2C8B72309BB3BA71FBFFB77523AC8EDADA65515600684CE0CBD3417DAE192A3ACDB476F448FB59686476163DD8D6AFDB4792EF04A26B1D9FBE46E4FA3A47306DF625CBA21CCCE46A73C7D45EDDC8BC9E6C0EAABC00D084357C693183CBEED5B4966EEC946EE274F5B3BDCD7C644E773630DB93AE6B1B75B6EFD3C006B64A06BB42E33E7CAD1B99D393CD41D77FAFCC00509DC0776EEB1DE72F036577ADE9D1DB729B76E0219A743986B7F57B2F03D5DC9F2DF277259F927D17FDA2F8BE5CE76E2EB12CF7CB817E2300CD087C792B7A08007B72EDBAADF34E0FBE0613DB816AD459BE4F67E85DBD162C4BCCB24E5E39D6FC663E6FCFBBF60580EAACE1CBB23CDFB46DBB81C597D7D94AD7447BDAA132A386277274BCADE747445183D95C4A7E378FE1B32CE5D7F1D566B465CD6FDEFAFAE4CCFA1AAE7F00AA30C3975C9D8E37AF936E9A95CEBC4CD755FDB1FCB90CC5DEFA587BBF6095BB936DC606D1E418909EBD6EEB3A757B0D97B8FE0CC4E7523BB4D366C4E03CF0D697B2A3DC01509519BE7C1DBC7F79B4E957F2DFCBF2E7AEB677DFEBAC0395BE9EC1FCDC5A8755505AC919BE11EACF748DE21CCCD49BD3553932138F2BCA08001096C0972F03E76D48FBF675D29F39DBEDF68DEDCF6DC35F1DF4F9949AF9B8BEB632C5C82294EF1261EF2AC2F7238E1C8FEC7F59E68A32DCF4E52BD73500DDB0A403B9BCE944A7CB329466A7DC79950E648545CCA065BD992E2961C04D2E2566786B1362CA7D5E9CE7B938DF0074C50C5F565FEE58F736F0DE6EB6B52C3A70B3281956294B4451AA9C47B84996BBADB19403967500EE8AD00E02C06D025F9665FC5DDDF7A46B061BD08DAFE48D09E587484A6D6636623BE1DAE5C8D5B20E5FAE873B3FAB6CE637621D463D5FDA41371801A84EE0CBD68CEB52E558A78F3E942CDF6E1A30AAB57E6C59C685349476D63E9C95BF376DCA9D9F39DBC076A5CD7967B67E2E71B86601A84AE0CB6AD4D95B7709ECC656B27CBB69C0E85AB60FA5D6EC75CDB23A2BDFADC2C1ABF7BD0A84F567FE69E63E2E31186BC0D8D276C6F54E73025F96452778BBA6AF8A794C25CBB8E0E81E9DA0FE6CCB76CBF3552A745306E9D5D9357114062BEF66F7D2966B10C6954E50D86B8BD5015427F0C58EE77F334B932F34E0FBD28ECEB613441FD6D98F239571E58FADD16E7AEFF5E7D281E748D7F313B997761AAD6E04E0BDBF0EFEBC2C2697D1C8BF5A7F009A12F6320B8D6B5D3F973FEB97F557FA6FC82BD731FDB5C458BB779533A05D5FABF577A2BDB49E1A595A0FFF5CFEFCFE33C87DCD8F5E6638F6B44D729311C6B26D3FEFB6A5DBF617AA31C3773EE9E05D8715C8E96E8022702BE36B9D1E2D10CDB97E70B4EF461BDBF234631F68FB9DB7337F47BF2E0CB2C9E16D9B34FAF505337A73D3585D405502DFB9FC4C7E875928F3E5DD0D51CC74892F526734F7B51BE9BB51D7EC41EF9EBDB50647BE469C7700BEF8FA64508427E79888C0771E6BE5F273D1E1653E396709AE0497BF3DEDFCE8E8E497A37C475AC6612BD7AC65E624E8BD365BF0FB953A655E2610006FFB12368AA73A6BF8CEC1CC5EC86BFB68F8EC0DF693B0D720B9AC1C6156C4329DA3DC44FC5E9427BC7C666F9D5FF629537372DE812FDC78A62A337CC7B6BD7BA4723927881A5B8941EBEC9DFEA7B3E6AC9F5A4ECED9BDD1ACB3F3D7CFB79D8998DA2B8751BF1765A5B3670497CFA4337E7BAFB79D7F725196605EAE7FBA23F01D9F8AE9BEDE07341C2BB1A4C3CCDEAE5FE51A2B2357F98E7A7ED6CFB50D7EB7FF7FFDBBAB9F670EE9534D6E78BF37D2E3A7B9CBC108C78477D429302FD73F5D11F88ECB40E73E33C0C627ECCDE74DDDE21A2B6B96F27D14AE1C7D7FE56E2E6B0067BF82BCB6A1EFB20839578E03C03C72F7B5B5A95421F01D530F61EFD540BCF66757D98E2F57999A35447ABBF191A51CEA885CDFD7907E7FE56E2EF62A28ABE7251E4A9689DE8E05ED282BF0DE5E3DDEE27ACAD5D75E6FA4AA13284EE03B96483B51DF09C59ECED62A61D6F08E6F666BA0DFDE4412BA9537F2FABD5F2977E3DBCE9031ABB7BC5E67FB9628176E2ECCE7ED395FAF1BE099A36CA3C79B8F4746F91E0425F01D4FABC1CE5E47E649E5B55676B53AD047EB40321E83B2F772DC44727D95F7A5DE1F319437B09EC3CF833F5356CFB37D73313B6B5E6E2A417957E38F91D69787A204BEE36835D8F91A9CA6B37396A57C676AC48083733AE8CF7C0D7A056E75E4AAF77BAE0BD363A07E9FC3F626B1FABD0DB316E11ED709DC77370FA8D906E5CE594C3CA31A816FFF5A2DE390A3A2DA0ED86A84BD2A57BE9AA9D3FEF65A14B8D525ECFAE73150F6C666464F2CBD2DEF002DB83EE05CA4A529F7E4FE4C6B9DA04F435102DFBED5DE9C2D0DBBBE544EE9062B66F552428999EFA396A19C6BC18E7A8C22F108FB3FCD7443666635977F7AEAC986B4E9BF3D9BAD1471F0BB2C7F2EEF1051D472C27C843AB02F7AD05B927A81E204BEFDAB1594AEBE564A2D2B7515EA9C66EB3C3CB5ED6C580BB62F5FCB76D490E6AE28BB365357ED9BDDA9ABEB269DB573B647C1F6DF1ED5A1DBBFDB13A58D337065645F6E1E58FE04FEE92C13F875F277BD53175095C0B75F35662DE40E70F6D6EB2DED69A57AF468623A70633E2336D0E94CFBB784BDFDEAFD9C459F61485E2DC2DEA34D69AFFA033F92DFD7D7DAFBEC775E67B57DDFF578EC7DC69AC7286AA055AABF19F1BB52DED7A5AE80BF1DB5E5E9F28BA33DA5918E977AEF83D301816FDF4A74E69FCCE87DBB6E5BEDCA7BDB60EC05B77B83B6379F71FB332AF0318D725E73CDB4B72E3611B8E130879A61EF9DBED0D3F2966BF07A14F66E3FCFDEACE01AC72DDA4DF192FDCD48DF93F89417F8DB5E5BFE663CD1EB8D1413C8A84AE0CBEA4D457BE7DFA58F32B6782C239DC5B837ABF12A00FB75F0735B2AF05846BB2BFC557A3C725D8BCA3B2D097BC7577B29A8D265EA6896EF534F6705AFDFABD4315C67F946DBC4ADD4F7D5DFE309E585D9EDB5E567F9C39D719C6B0A2E087C59966F839BBD651A8EFEDC22ECCDF59E57AFB317086B84DACB75FE7BBD8B9C96C5D98FC728726EB0D73375ECF8B6378D6BEE5950AA6CADDFA5F6F597AE155C32F45D96DFDF4FC80571973C815ACE66F59EB51111D6EFCD3581C853913421F0EDDBD74EFBD78AE76886ECDE9F2354D825EDAD41943610E9E396BD54F6570D5DC4EF5162766FC4EF79242D5F398F87199531D8608FD1E55A63FC4CEE8D69EF6871DDAD6D421A3897EC9BB5EEE7947ECA47FD0970EEE8099DAB7E68A4A73473AE25ACDDA03A816FBFBE563E6F07FCA51E0B1FCD5900BC1D742D9B3F476B04EE3C461BF1F1CDD5EC65B3C4CD1641E1389C437A50B21E9FA93E4BD7FD2DBD194E94198D35F6BA8065F9B35CCCDEFF8465B9DE98ED2AEC3DBB8E6AD6C39EAAA36B02DFFE9DCDD2D8EB7C7C99D53BCB6CDD12B6C73F0D48F736926BEDEEB94EBF5794CF9F534F8D74A9B52E3D861447A4590F5042E9323E737D967EDF1A21D5887D83D1BE0FE7EED649A56FA4400FAE66F52ECB751D7AA73DAA590F7BAA8E6E097CFB96AEC976F46FD2BFFF32AB57D8FBCD51F01B29F47D13EC6F376B89F2F973E8A591DE1EF7DCD7682FC78067225CAB902A796379E6A0F7483AF37714A5BF8FFA733E77EAA4D1AE23FAB357066BD6556F67F5A63F7FA6C5449CAFCB686A2F6846E03B86A3B5617F2EF93AA582DEBCF666C64618407C196C4779847359F296D7D6E7E44AA9752E852331E59A41E49C1255C9FE8672FF5B1AF69658D7B765BFE6CEF7F0183E3999E14B4B471BA3D5AA87BF86BDAB68B37BBF5CD751C6C54CEC5FAD3F00D9A5CB0418DCC4B6366ADBD0B7B5AF839E08DF21A71EBECF5F9B5F396C3B67EA90B1E87C1255A9BAF6D7A2DC9FF991FC9EFBB5A3B6A15F9E5ED12EB275B77E897A2DD0BFA3B0B5D68DAC5C61EF8866FEEE046086EFB872552E3A27E56D97435896763362729CEBD6B37C675ACEA16438B22C71BF37DF39B74465EDF1364A1D9F084F2E9D89FCD9E8CB9D3549DD78A2A4164F29E458AF77EFB58ED4BE8646CF41A2B7D16420F0E58C4DDAEA396A246BEBF95C9728AF911AC1BD4E87F57AE7337AE79339955A92465D765FEE73D02AE0AA111A182473E6EB7A9FF044AB7E61CE59BD4FC6702D9673E8FD7ADEDB303EFDBBF4EFB5718310F872A5F70A8E7B7277165A3514A396D7D2375FCC84FBDBD57510E5D8E4587625CA7781DCF59BB0F79DC8CB2F3C553A3450B6E6F1F49A18E93A22BEABF6B3D44DB7124B385CD5DB516F204690EEE174F6EF567BE7EE2808DEA31DEC84C017626AB1915BAEC1B6C7D6F2B26B7D3977664DFF5AE21F9BBB1B0F09C2882A47FD367B7DF6552F03DBAFF44F786AD4C9045B6E04F7EBAA7CE63EAFB9C3DE276D8FC9447FDBDBFBE72CCCBDE3C9CFA4E1F0967A2418812FEC4B0704352BFBED9ABE3D569A23DC818D3020FCDA701F49BF5B8F65EC8BB4ACDC39B6E9A35011DD9D5514F93B309F1C75B7A0379F8803DB27EE9627E58427CE966888D05F7CEAE83A49FFBFEB24B6ABFAAE44D92C15F6469CDD1BC9DEB96EB9F4E6597D78562ED5290D087CE1B811D93E1A71A7A1C959E16E67C9F618FC8E700736C2C679B91F715EF5569E72781BA0AFFF76DB896975FCD2F2317B0798FEAD372AAED6BC3C9AC56ED67A1EA3CCEEF5383DB9DD29537BF54FD4757C9F046CADFB3C1CBB7B1E4B6CE29E731987BDD73B325339ACB16F4B0957FDB8BDF582294CE0CBE8EE06226795CE9D0A692F14CE5131D7087B0D8EE228B1398040E4B72FC775BD09D3FA386E03E865F95D665CC7F46A1BFADEF977CB62566F6E2582D28837ADDD2423A7B30923D1DAE4A737BDD39BDD51AE617EAB1D00EEBDDF973146B46BA4A4AB6B682F08ED21E07D22BD59EF865225025F8EF45C09EFCD662CDD59495F7B3B787D5B61D79AE59BB3633AC2720EAD945C9662F6C634E7B16D35F0D9FB0EE9E718AD73C83C8EAEA9BD01819B5879956843D31B52B57C59D626EA8C4CDACABD71518B3EC49747BF7B5F666E44BDD4A9775EF74E991CE146DD551F6759FEBCD646974E5EA120812F47EE3E6619C15E659186BCB53B29E972105F8E61E98A3FF7EB8FB09C432DA59770D039CFBBC655AB814FFA1D8E6E6A9DD1A9A247E975A64ECBABE41A80ADFA5F6FD65A8D3823931872968DDA9B1AE7DA0B42E81B4FE9356FAFCA4E8E72FC24ECEDB9DCA5D7CDD9D82FEA78F6EEE6D00424F0E54CC40EF0D57ABB57FFAFA6ED0E9A6F43DF288F914797F3C6448DCEF8B6F12FB521DBEC65A6D4A677B5076C5B5FCFEFEC6502F8A719066F57A141B4BE2E71F45A3672DFCC11FAC6F0A43CBE3D4F5765E76B08FBF49AEABDBCEDE5293DB6BB1173216E10F87247CB59BE47B3777BB09D651C79B674AE0ABCD5720E251AA05265ECE7C19FBF12F4FEA9C6CEB5B5063D6919777E811C4A0DDC5ADD10FBF2E8BD812C67DECE706CD5EF2FD5076A79C39BDF4AF76DEFBCC7D7BE68C4F1F0915C4FEAF6F49DF7945C7A908204BE5CA9D509BE7AD4AE57DB471A2387BEB9B4583AA397415A89065FD0FB4F353A5635073D3F0EFE0CF056A97AB2F5E3B76F83B95EFA11B471D4778F781361A41BDEFCE96E797ADB3FBD53766A86741102C1AFD771D431FFD1B13DFABCA3F515A622F0A5B65183DD3BBE341A9167F7456890A336A8ABDC1D7041EFBE1A039D56E5DD790672285D4F46ACABAE3E534F378FA92B2D1B11FABC4746BBE1CD6F4FCFED937AF8EE12683942BAA7A175EBF664B476616FFC58EA69D9ABCFD0FADC4E43E0CB5D3967A6BAC09FD9AEE35BE2AE7A8E0ADE60695F8975643594E76A84FFD18EBD0DDB80274AD49311EB99BB9F49FF8527A2F501B6A24F80E0BD279B9CDD753748CE39F628115A73EE6CA290B07770025FEED82E47B02CEF3A131107023D2A5541E6E8208E5079E72AA7B983DEF473BD39D66F1BF35ECE6BCD017BC4C7194BAFB706F4AD641DD97A10977EB7A74FC1B869CD911ECA450F9F91F74A6D72563BEC2DBD24450923B40D77372D2D7DB3A8753F615A025F9E7853E979F43CB6DE1BB1129F3F578726D28CDEB79F69DD917915F51A2ED95989D4F10478A3C6802E4AFB6050490939FB02B96F1A6F1FCD664C39EBEE27935272D7A766F7D67396C1A47545ADA5F09CD70604BEDCB5BD40D7E51DB617705A51B8B8F38B38AB70ABC5E78B74E7B5E4ACDE1C337ADF7CA6EDCF440C7F4B2C99B152870123281DF646B829963E89A6CE26976D3FF34DB94A97C42BB5066E94BE3079953AAF4F9687C8519FF638BBB72747C7EDEADC99D53B01812F4FED75AAB7FF9DFE5BBE2B5959E60C4C5B9CEFD61DDC74A1FBD6B37A4BDEB1DD0B7F5B5EE3DB639FF37B8E16F4EA3CC3BC6A3DFD10A1AE8CF01918D3DBB2556B5242AD47B2A92BF7797D52164B84BD66F7E6F576B9BFDA9BB4399F8D097C79636FB1EFADD641D0884A1DCFD681690EAD3AB83F93DF5BCEEA2D39D375CF3A43A5D5B55EA2B3122DBCC869B4EF035CAB11F66EEB9611FA7E5FF6AA801684BD65ECF52F5BD46F4FCEEBD90DFEA7EBF52E4BBEEF1BE969CC11E43847CEC74404BE94D07B87BF942F956B8981548E4E6284D9832D0768256696BE9DD55BFBFBA7A16FEDC17E89EFBD5D074F3D06F4AAC59ABD23D49982097A24ECFDE6E89A6FF9645BAE8D965BAED79BBEFF95AF63CA12E39108ED41CEA7A85B8C198DA91A13F8425D5F2AD8129B3CE468C85A56E22D36AA28F15EBD85BDAB6DE8BB2C751BF5DCCB672CCBB775FA5A89D01905E29861CD5E40FBFFC5D325D0D2FE6E8D7EE2D731E393D729B97CE093EF9163E3ECAFEB6E47B13793F7CB38ABC598B1C5754342E00B7D2851418ED6514C37C638FB777BAE7EB6C4B2095F1ECB691DF6AE6ACE80C85D666D26008CA2C6D23EA3D799A3F58B882B575FA9751FB0177BD7F6D363B7FEFBE87DDDA7E38308F57A8E1B897B9380BECEFAADF9D4C79D99BC5FCF51CBA76123ECFF3225812FD4F1B6B1582BFFDC156494B03097A70D727AF7F768E3C165293788FED2C18A7CFE4A2DEF106563BC68667F0479FBDDF78E45EFE717EEA8D1268C52679E99BD3EA58CBB13129E504EEF29D1872FB98FC5D7BAFCEDCF4798589473CDE0EDFBB7780AF18974F24FD4CFF995D9BE8D087CA19EB78D77890A71D4414DDAA94E43DCF5EFD2BBBFAB74039AF4E77278DBB03F7DFCAC9592E54AD8FB4F67C77BD447AFB79DC5B42E4BAF611D4B46566BC3CE91EACC23237F37DA39EA6F9BDD5B56E9BA710DAF4A78FB799FFE5CC93EE293E0B9D4E758AFBD2F334C4B2EE3B7F5E3E0CFA3D99E0B2A11F8421F4ACDF01DC9D14CDDF44EEF51E7235DCB35ED28E4B037683E3BAFE95A54D13BF7A51EDBC9595ED37330C29DF4B372D1FB77DB73F49D8E66F7AEC1EF88C78279D56A1766087B97451D415969DF54792BA3D64DB0EDFB45ECEB9EF9B29CDC13471370F6CE4BC9CFB14E062A3933FBC967591ABC7FA9B27534B1EA0E756025025F282FEAC668D1C3C3379E86787B7718D347807278D3B9EA2DECDDCA39F3A1C4F71F29EC9DC9F67C5DCD78FF95FC9B5136F1606E359FF4A8150A4431EA934FC4B0B794D85BCAE9BEDAFDE512B37C47B88177543EF7CA7DE9999EDBC9406F42DF5CED42CBF6B4D475B13781E6AE08E1FB3404BE50D6974AB6542398ABA318F9718C278DC7B6312FD1288EBA56EF1DE9E0A6E5C6747736431849E4EBF3ADBBB3F5D3BFB36618BDAB3D6B6D35E3F5D26B7BDBD2D78D916690ABEFDD7BBFB08456F5E3F6FDBF96FB1A217ECDD9A57BE7E12C082EE96BE8FB45941BA7A5C3DE374A2E8BC286C017CAFB52C9966A1C7255FC2374EC6B749E5B069DADE45ADEA1C4CCDE5994DA4CA4D5EC84B733DED74EA599DDF4A4D5BAEDB30FC04A6CB035B2F46924E1EFBEB44F94E3B566D73AE85DDF37577858EA3BD40C1D8FCAF6DE77ABD9D61C85BE35DF7B14B96F1C98905198C017CAF952B9EFAD274B7EB53A884FCEE108616FAA75F91D35C0A8D1814C07F32D96467832B3F74CBA5963EB7209A9D69B73CEB25EEF91D106E625ED6D7E9BEEC2AE9EFD2D0D9ABE3CF535BB08416F4E25CF6B8B3A3DCADABD7BEF951EEB5A75548B7AB0E48673B9BECF97FA909B04BE50D6D7D9BDD13BCBD13F5F0B39960E18A103BB35E3060935D40827F636184C67CA963CBFB9D6B2DECEAE12EA50DAD58D92F4EFB75AD5FF23D79577A917AE5D856D7BCBE9CC5CA696256F9BD3BA7FD8FA067AD44911D166F746594A607DFFDA6BF79E69B1B443AB7AB0C47552E27B98E55B90C017F2CBB94E57A947B2735131EFFBBA66ED48222CCC3F6A39ADD181DE9BC5B5DA06C0A5CEEFF63573775C0511E4B65787EFDD28D9FE1E21B810F6FE16E17C44F5A6CC2A53FB9E061CB99E1ACC51BE5BCF569CE91A4DFB7577BE7BABFAFCA88CB65ABBF7E83D5B2CED10E55C7C51EA386D27645080C017F2D986075F3A23A51BEA9C1DA55E02935A41EA7AEE5A6F5076E6AA412DF1FE1142DF11EDCD9AC83593E86A7095AE43587A862FF462EFB1F6BD7F1385B0F79A81E86F77CBAE63F64FE931A9F1F4D7F6FAEE755241A49B63B9DD3927E9CDC233ADEBF3F41CB55EBB774F8BA51D5AF4656B6E40FE73F3EFBEBCAF3E7F01025FC82B5767AA97CAAE87CF79F6E86C4E5F3A59253BB3779698D81EA3F5DFE7FE2C4FEFA2F73A30A969EF18E50C7BCFCAC0D11A6DB93B6B25AEDD5A7502F368BDF6EE5BADC3819EEC6D4C369337F5E56CC7E8CADB35F0DF1CFBF491FE1EDBBB12B37A8FFAA16F5FFF4B7879D5D7DA7BEDB33E7ACBFAFC69F98A5037D49AE5DBE3B5B77537ECFD5ACFD4DC486F2A025FF8AE4487A4C45A36BD37384F9D3D8A9E5B8E4E56C9B0F7EA73ED3D721C61A7F29CD75384CE6509B967ECBF7DDDD2EB6FE5FA9E3A94E4D6FBECB351EBC6DC6A3CD110D5D332AE8EDDF7350C79EA681DF11EE4AE57CFD6B4FD7A5C4AF4FDF7FAEF691F3DF767F9AA761BB8D7B7BFDB177D3ACBF7ED98A8459FB3C4757E763CD763D953FD32957FB5FE0030801F4BDE06766D504A34DABD0E489FDA7614B7BF72FBB57C0F7B6B6EBAF5F4674AAF097B25C7FBCF52E65769787F577ACD3CB1FD9912B3714BD24125879EEB999F3BBF9665FFDAA87923359292FD88E8DE066FB385E257722CF976D7DEF2623F93BFFBF2BAA5950A7BD3F1DAD7902AC7ECDE3347D7D08F935F2DD4EED7A7ED54DA763DFD4CCB725ED6721CD79A375C5AAD8DBC2C79AED999FA1655087CE19BB38111F5959E65B875D4817CA3E400E06D87B654E87BF75845DFB07044B93673C97DAC4B5C1F35EB0AC6356ABD7236983EFBF32C66FAAE5F6F66CF74ACEED886B177BC3D7E7BE72D577055CAB63EC9D1EE5F4DCAC811527DE9F33C59CA21BA5A1B391E4DE8D99BE0136DD669ADFE66CEEF7CB72CE67C4FFDF2CC2CE900DFE5EA9CF4B47E50C4CE48CDC145AE75B24A7FD6AF3B9FA6BBD9E6D06AE382ED264A237726DE94A99CE530FAF21911EB2EFAD6FBACCFB59E5F7FDFB61BDBBFDBDA9B3978B4B1D0DB754BA39A6D599837ED43DAE6F2DB9B80F0491D73B67970E4F3913BE85D9DD539B98EC7DB27AA72BE662BB566F73E5D726CDB8E7D6D7F225F374772F64BEE1EB7DEFB42C312F8C23B25D6ED5D96E3C1D517A5D6178CD21929752EF6DCED443E51F333BF9173C6E67610D86AB7DA5106EA7BE7E4CD77CC593F943ABFB9D793EE6D2D43621AA50CDD59C2E1E9F20EE9607B6F8DBF287D08AEBD5DBB7259DAB5F95195AC378E2623ECDDD089A4C4C499D221D5D7BEE408B37BDF9CB7DA1B5BE7B84197A3CF58EBA667CBD9BDB9FAEADA8BCC04BEF05E898D044A75044B3D121DA542AEB9265AAEEF5C332C88729E564F3E4F89CDE346091DB6C7E54D87B6879B4125C3D99ECF3D6D95BA766A3BFBFCB96F026D5F736FF6A7EB714C2D6FF44655F2981CBD5EA41B546737ACDF283119E38E52EFD5D375527A23C7AF6DEDD3B15B896BB2E68493DC7DE63BC7E2E8E99E37667B82A60A6BF8C233A5D603EAA9715FB5FECC2DD6662AD5092861BB76D9B27C3F562D1AE012EB08475FE3EB89F4B83C299FBD055639CB40AFE79B587AB9765ADBDBF0EC6CBD45D7671C399FEE595F6FE6B57DD33ECD559B9DE371F95C4FC1E56A838FD6607D2BE77E1AA58D54BF7DD91BE4A9AF6DEDDEFBBEB9E9906BC339A84AE00BCF94DA65B7C719872D1BADED1DD89AB37B73A975ECB61DE02FE5AAE5066A25AF879E775DBFDB813DD3CB77CF55065ACD0202CE8D74236E34B9DA89F4FCCEEC49FB73E7F85F05C8B9DABB484B995D6DC876E54B193C1B139CD55D57E396DE6636D69ADD5B4AAD8DE6DEBCDF53B96FA4F556163921F085674A343EDB06A09710A27550546B0DCEAF1DCA33B58E61AE75A75A2BDDF9186176D9DD70BFB7EF68F75F9887E0775C6EBAFDED4E997E5AEE4B6E4E967B76EF57B966F57EE987EFAD937CB6AEF9555D566AAC11C5DB8DED4A2D4958E3676ADA1EDF5CC7ECCD53830424F0857B6A2CB65EF2B547BB5357F371F4DC8F89D50AAA575F3F7BEECDB7DEBC6689A51DB67A7CB4F8ED67AB75ED440D69239F53E037334263C8D5EE8E1E683D7177CDCB27B37BF7E47C1AEEEB79CBBD31DB97CF93BB3ED95BA662CFD55365BD5C1BB5EAE392FDD4A7AFDDCBB969C5F1094CE00BF7ECED309D4BE93076B44AB8566055F2BC945EBB7759E29EF7B7B3B34B87BEAB1C6BCAD5927EC6ABE35AFADA29357B2BF73A9251AF0DE09FA284BEBF963F9FFA9941890DC56CDE76ED6E59BFD3DF6B7DDD6C45087B73FCFC9EB3EF16E91C7CF5B41FF96543E1AFF6DEFBCE2CEB3D239D432623F0856BE9860725C292929DDFF5F3E71CA4B41EF0D40A7B7B1994A403D1966BEEDEF1F6F36D43DF5A03EF1E66FBAEEE0CFA4A5F3B25965BE9E53A04CA8812FAAA8BC8E1A81C6FFB1A7737903A2A93E9ACD3B75AF7F797A56CFFF6A9F5733C5D33F5EC1C4438C64F949C1D9BBBAFBA7DEF3BAF5D7A1DECEDE7802A04BE706E9D8950AA72AED1C897DAB9B665A7ABD4718BD4A9BC2B5DCBACD4E78E32E3355DA7B6F44CEC911E2BAE710E4B1EA368EB0832B7DE06E9BDAB550F9BE5558ED9BDFF5C6221BDA9FC24A0BD3A962DD6F2CC2DD77ABD39A59F25C7ECDE28DFED4CE9FA2F67D89BD6E33597023C5363E3B69EE9571520F08573DBB0377725DD2258FC75F0AB1725070B353A95A51AE31A33C423491F0BAD51865B7712EF389B355443A92513840444A33CB651E30985BB7F1FB16D8CAAB71BE9B5FCB5F3EB8EAB3E4FC947E26BBEDE28E566D6D9BD4FBF5B892564B67AE8C77F91EBF86DCFDBDDBA24EABE1D2C025FB83252D8FBE3E0D75B2D063BA5665BD73C1F39CBD29B46F98D9C77DB737DCEBD99BE359778E84594590D5FE4BEEE7526C9A5B7C1FA284AD5C135D641EFA9FDC849BDFBB7DC7DA023D166F73EB941DFE3D3767B469ADDFBD6DDEF56A26FFD64D98DBB6D79E436BF443FF949D9CC55E7CCDA461623F08563A58392911BF8129EAC6DF646AFE7E34DA37C572F6BB16E6F5E940C7EA32DEF907ED7D68F3C46EE08436E359F30E0B79275F059FDF97543CFF4F3A68FF2B7BE99D8D37AF53DBAEAC3DE7DF2AED7D9BDCB72DD3F4983DE68FDF227C7E44E7D3172DBF1749247BAD4C997F7DD96A127E3C6274F7844D46A9D616D4670025FD85732581CA581AFDDF0E5EA0CAC6A2F6991BB41ACF5B9735F0325CB4D1AFC96D23AF45DDF37FDAE7B8142ABEBB497D7852F84BE6D447962E1EE8CE0743F88B48FF973E7675A28D9B6CD1A0ADC599FF769C879F66F72B695259750DBF6BF6B07BD6FCA62A919C73DF46DDEDE087A722E4BCC4E8DFC845BE4FA707BBC6A3CF592EAE19AE88AC017FEA946D8AB327B6E3D2F3906D6ADCE434FE169CECE48EEE51CAED49AEDBB2CED9636D9FB7F7BEB4BD6FE7C913BB15042A4D0B7C7B5F9BF685DDF1CB5C1E9CCDDEDEFE9CCBFBD355C5BCEB42DD1B6450C5C4ABBBB11DB93FEE89DD9BDADAF892B471BEFAE7D98DA7B0E3C91B3CFDD531DFD74C2CDD33E7FA971F7D327322297BD3B6A5EFBDBF7CA39118B02FEDDFA034030C2DEB8B6776C7F2DEFCE51FAF8FBDE9F7BD0E3ECDE56334DD7F2B2CAF99DFEFAAFD76EB5A9D8D5266DB507D9EBF180D9AC41C55AFE6B5F7B69DB96D67B47D26BF6E935DC32C88B50DF9CD5FD6F8FCDFA732DDB9608C7B6576F8290279B015ECDEEFDF9E07D8FD4DA0F616FD67B6FFDF12F7AF8AE6F43C4A7E17DCEB6E4CB67EE59EE1B2657638CDACB2AF192C0177ECB1D92EC75985464DF7D6DD0D69F6F3590CAA5B7D9BDADA403A9DC814C8B81F19DF3D3327082191DDD64CAE14E3DB36DDBD2FFB76D37F73E671A126F7FEEC8D5F79C615667C976B8F50DC565893F5B3482BD6374A7ECA79BEE6ECFF1D9B20F4F3FCB533526A7F43CE162EBEDF1EEA9BFF4F626C2DD7A2B771DF37532D008DE4E8ADA4A6F3C1EFD3D1D10F8C2DF4ACD88DB1B648D62A4C0B4F477E929401D793DA6BDA023E7AEB2B5BEE75E88B32CED07E7351FC78488F6EA81A3C0F5EAE7B67F772784495F237DBFF467F7FE7BFB1A5F36B029F964C5DE7B45696396256FE8D69201FDB537FDA5BDD9F8779DD511AB1CE72DD2F534AA1E8EF197B58E9F8850D7B408A74BC8DD0FAF756E22B479C312F842D9C79F470D7B97A5FD774A1B87BF4EFE2EFDFBDA7237C0AD8FFD5D511BF0DCC16FA4C75F5B779C4B97CD568FCCC31D4781EB5528996313A6AB40F7CD6BBC75F464C5B2945956278A1CFDC91197FFDAAEE9D9FBF74AFB726FC2DEBDD9F85FE458CA21D275D48B377DFB9E8EF3DB72D562A3B6B79E9C8F1E263544FF7C677A6F1BC2B2691BB32B19F6AEAFA902CBEF47F26BB5B7D3EF9DE35FFA1CF5D20097188C452EFFDBF29163B3A35AE739EA9A77A5BF7F7ABEDEEAE57AA46FDBF2BA37EB77F472B8AD9FEEB6C55FDFABA59CFDC928757A4EBD7FA7BD4DD89E6C62B517E4E73A265FCB7FDA6FEE55ED7A75F4B562DF86D9ADD6EE7DEAEDCDB5C8A17D2F656B2BF2F11C8219BECCAAC4EE922915585DD167C5449F91987B11FE9ECAFFDE6CB4D63B38DF7DBF929B273C557B498B37A2CD08644E51DBA99CF642EE65C9B3BEE056A46B3A7A3BDF528436EAAEA3CFFA657DD0A32554EEBCEF51F9CE3996B9F324425447C7F168F99C9CDE2EE7D18B37B37B9F1CE3DCEDC1534FCAFBDE526A5FF47ABD95E0381424F06566B91B189BB4B5F3B4C1E64F25D7B0EE4DBA79D1936352BBF39686BDB3850DAD070AC033251E896D1DA218B45F4B37008A7CBC72B5A7E95ABD5FDFF7E8064AEDCF19CDD171D86E36957EAFA7C72C471D137D524A2E4FAEEF1E9648D8CA79EE22DDA86470025F6653E391E3688F599712BDD37EA5A74E460D3982B3513A2FE91ABF778E4DEDCEDBEC41E7DB81C22865147A53B2BF90FBB5EFD6FBB9677C8D6E6D27A3F41F8FCE5FAE10F5EE926267E5E8ACCDCAD50F88702E9EB873BEB6371972380ADCBFBC46646FBEDFD37E70CB7A73F4BE600F1322463F076158C39799A46B6F957C8F9E1AF5B77AF98E698762BBA6622FDFA19412EB9DDDDDE1BD076FBE43AD0EECD7F586673642D904CAB85B3FACFDC812757EA4A57A72DB1EB716DF2F3DB66FD7E53D526226678EB071CF9B3557A3C875BEBE7E863B7AEBABD57C6AAC97E51C7A11E566DA9551D60DEF8219BECCA254E335FB320E3D342C66DFEC2BB58E750F65E2A92777CA7B58C77676239651886EE4EB2E677DBC9D2537EAF14A97785895F8BE7BE7A5C498E0EBD20877CB4FEEA51C7A2B6311FA3DA3CFEE5D966FEB554737F2A6BFE946E69167F9F6764D744BE0CB0C4ADFA99C691987544FDFB7E7990CA5E41AA4F6BE06DC19C1EA58AC9B066DF4D036BC09A5B511EF6D83DFBDE3B8B7DCC1D9E662A59668B8EB4B19DFFB8C695B957B09911EAEC9D597490A256E368D3ABBF7AB5ECAD4DBCFD9437DBFAD27A285BEB35D0F2158D281D1D5782C65D6B0377A83B7A7C5398A769CB68F51E6FC6CB3957F009E196DC3B665293773B966DF21423F25EDAFA7C7F447F2EFD63F6F7FA5AFD3FA91FF3BAE8E7D89E51C225C374F7C5992AFD412224F8E614FFDE308754149BD95FD37CE6E8AB564198746CCF06554A51E57DF1A7956E3A85A6DB0126DC051E2F344EC5CB450EB38940C4EA295D7336F672F28AF5057A91BF025D74ABDFB3325FA14B5EBA7686BA1A6C1EEF6DF6E978268F9B9BF8647DBB273D696E51CD3F4BA94C397EF5DA2CE89387B32979ACB39D43C8639CA7E2F81F8DDBAA5B6DEEA9D21087C1951CD4EE0CC15572FDF3D6D9CB7B3406A7D87088D6DE94E4A2FE5A1B45AC7A14479EAE151B5ADB79FD7B20E5057E9355323C85D27F77AF33087AB63D9BA3F9573C2C7DECCE5AD9CD74E8F616FEBF27954CFDC097DA3D55157BE1EEBC8CBE1E42CFBADEB9FBBAEEA965A7ABB0E8663490746533AECFDB5F9453FF61E11ACB9B9566B5F1E87BBE25AE89F73089456EA26556B2506D3DBC7D06BE825C08820F763C967E738E7B2143D87BDADCBE7D1313B3B963D1EEF656973AC4BF7417B3D1739B5CA2E2CE3108019BE8CA2F4120E232EDF30E323CDADEF72D6566369936599AF1C8DC83904722BD5E6961CB83E790CB664BB9A2E5DB02CEAE9967A1E07F4187845097BEF389B05CCB9D2B37C4B94FDF5357B289BCBF2E74CDF5A4F9CF65C5F0E47E0CB086ACCEA5D96FE1E77E6B7B4811B6546E3D94ED5AB920DFB28C7F14CB46B3EE2A647339403E099527DB3D2E15594C76097E59FFD96F433BD3D0611BE5B0FD2B6AD44999BE969B3BB7A0A7B7B3AAE919508224BB515BDE501EB58B1F4DAD335EA4B5EB0A4033DDBEEB85AAAE24A1B0B95577F463C67479B996CFFBBF4EED43DCE18F92252A899EBBC1E9DC337DFB5C772D053871D7A54AA0D2A59DF6CFB9691EC2D4DF533F9B52CFBFD83BDEFB3BE4E0FA15A6D69FFBFF4E3C8356638F6A2A7B077245FCA608ECD0B73BCCED96B477DBD1AB601F5F658E73ADEB5EB4B1E32C3975E99D5DBDE8C4B4244B07DCCF3682652E98EF26C61EF9D7AA0C6A0AAC64EF03DD6796F662BD8B80DCAE971298755F43AF0A8FFBBD72EA7E1F0DEEBF05BCBC7904BCEB8EBA5AF26EC6DEBCB71CFB17961AED9A7FA76FFB41D2FA64B3CAC9E1C77B3793B22F0A547B5C3DE912BB1193AFC51BE63CE4768F6D6F6ABD541EE6D00F1C5D9ECA83D358E49E9F3FCF67A695557460F676036BD2EE5B05AEBB288F5CAD531BDDA008C7F8A125CE47ECCBAB7BE9AB0B76F39FA805F42DF28D771747BC1EFFADF4F8372C7B813025F7A5223DC9A6D0987AF77737B09C45B77204B0C1E5BEEA4DBC339CFA575D929E56C93913765B5B732610608E455B28F56B3EDF9F21E6F674BE5306A5B554AA44D854ADD6068FDBD9E5286FB95ABAC6D43DF569FE1AE5A1BA0959006BFBDD5153C20F0A5073583DE6551E9DD1571F6CB1521CF77B35C1F4FCAF728CB39CC66F6EF0FB994DE4F6159FAB95EA3CE0EE66F11FBFB39CB4B4FFD5CD7097B8EAECBF4E98B96D7EFD77A3ECA759A1EC374C9872875241F087C89665B796E2BD31A77D0546AF7AD4B0AF478CCB6E54A67F39E281D93DA9ED43B3D2DE730EBF94CE9CCC2772386BD39FA063DCFFE7AAAE5CCE6BB2206BDCB52E6FA89F4FD52B5F79C601C919EBE5DCBF1977A3EC2F7481DAD014FC7FED5FA03C046DAE9A911F6E6DCA592B8B6E738BDA9D0EA73F422DD79750623DF08E86DB65C290699F0DD8861EFEAED779AED46527A9EA2F5ABF776908F22F7F513E9B8A77E2E7F7EDFF517ED8D3053B5B66D5DF2F418CC7ACC68C40C5FA2483B3D353B01913A7FE4B737537C6D6C6B067B3D3FE639E33572B70EEA71398711CFE74CB3E9A0B5D24B6DB50E7BD7CFF0F4BBAD4F3F7D798D1EA533EFDE6EBC944B4F1B38E50E7B237D57B379FBF1754F97593DDD682EE275CAE004BE44A223D09FDE67B2B408617B1A00CE78173AEA8665BD949916BE5CC7BDD761D04AE93AA9E7F519B7AFD1539BFFD4D5E69FB5967AD8FB1CD1EBF512EBF646F8CE425E6673B7BD88749D3211812F18F07FD1C3718B34ABB69759BE51D7BA2BEDE9E395BD05E2DB99ED339DD723E96C3CE05CC9F66BA46B71BBF3F9E8A1EF515BB2FDFFDBF037C771E831E0DD1A6DDD5E212F9C13F6D28CC097D65A875F06FCDFF4121C45EB7C461EFCCDDA29791BF6F6769C727F5EF527CCA1C6BE0A6B401AA55ECDB1AC433ADB356ADBFFD493BA3F5DEAE1C8D9B1395BA6A1F558E2899ED7ED5D3FFBDEE48551CA35E4D6EB788141087C69A9C6E081724608CB5B0C1222CFF29DB553F2B62EAA719C72979512614A94F212F9460AF4AAD67ABDAB28F5C9D7B67A5BD74659DB3697B77D85BD7FBF1EA7AB30F8ECFDA294992BA5C2DE92DF7FAFCF60FC368EA8E39151CC3AAE2210812FB5B9234C14DB41460B91067DB32EE1B02C7D0C5C227FB628ACE30BF995AE1F471E0CEF7DA7DE977828D1574843F151F512F6EECDE22D7DD3A794F406424F9FBDB46DBFC771C96FE67115C1087C6941C342041136848930E81B79C07DE5ED00ACC799EDB39CE7A7D7D4084F2A406E35029ED1EBA4A31B49BD2FF130EAF9AA21F7B9FE7A2ECE6E92E60AA85B85AE69FD12A5CF3D2237CDFF347ADB466704BEAC4A57D6D11F1999ADB1CA793EDE1CBBEDFBD738EE113B7911425F9D92F7C7BEE631CB5546463FCF91974B81DE940E7A97A58F3A29C73ABEA9BD356DA3F55196C5CDB01C4AB649395EBBE6755EA37D3EAA5FF40FFE94ABEE75D37CBF5CF5D0B63109812F3F933F977A34685962766657B355CCB93A3E6F1AFAEDAC815F4BF9B0FDE8BB4608F95B2F29B17E86D9BC3DE62D3AB539CB4884325F92C11CBC57338CE9A51E2ADD469F6D66D6FA46F0AA97731559897319714CD5BAECF456BF30866D3B317BF84D40025F96E577A7216725553BE8DD7EF637EF377A1092AA1D8C1CADDDDCEACE70B43BD23567F9F636BB2AB7AF8F29B63A661167A9AF22D59F518F11F4A0E4F5D37318F3B6FEDDAE877A661BFCEE6D60F6E4BDF7FA36E9CFBFDD1C8DFB66BA0119A15FD973FDD25AE4FE654F94414212F8CEADC4EEEF5B2DD6697AF39DA2857F35E49EB57216F844DE142B4287BCE67A7E3377467A79DA604FAEEBB5C4798F567F7E096666BC2E985BAD36B0E7B6A766DF726F03B3BDD9BF67EFB9FEFC5178BCECFC1BF28ADCEFCD25CA6CDEF43328D3CF58EA220F658EB004BEF37AD219B96A3C5B052911EE28B33FA86915FEEFE9A123537A5389D9AF951C83AF96A1E6D7321C29902DC94D3FB8AF76283563DBB32CDFBFF7D9CDF4A360781BF62ECB711036EB392969F4B0F76B7F32671FF76853C4DC84C8D71C23084AE03B9FAB70765B61AF7F4E3B947B6A776CCE668B98E1D54EC459947B21D0D759E1A5E4DCC8ADF5EC8B087295C79E67A7F5FCD981B2A2B4D33C7727BC4D37ADA28ED1AEAB5CFDC95C7DEEDA37685D3BE7DC3487C004BE733AEA886C37D15AED35CC513A32479DDB2F33BC84BEEF459DD570541EA29EE7A34730EF1E57B379FF547226492D5F6F0494FCEC113BF96EFAC1B148373967A05E9947EE6BEBEBFE2439DE7715AD0CD7FC3CAE61A05B02DFB9DCE988440BEB6AD3A0BFD363B989DC814B3FD7F6DABD13B045FD5E25959A5D1E25D07C13FAD6FAEC91CA9B651DE058CB1BB391DBDC3BDEDC4852AFCCA3D4B5F57513BFBB6A3E15F665CF8ADAD7D32CD7B08DDB605002DF79449D7D598A868BD55E00D463072E9DB97B77F7EFD16D438452D77C9463FC26CCACF5D9A385394FDB80DEEA0378A2F5724B3DB6B95B39975B625C35FA2039C2DFA36BB1461BFE76CF8AD99F5E2B29DAF276404602DF398C1AF61E050C5F1AAE68A105DF9D95855E3A38B536A6E8C9D5D2335FF5184EB4FCCC91CAE3DB3620D277805C46ED03D6A66FC9919A7DC9B3F077CF7ADD1FCDE06D5536EFDE44C91DF4BA69F32775130C4EE03B8FD11AB712B345665AC777ED9CCED2F1D9FB8E479D60E2AB156044AD07CEAEDB169F7994D9E633D4FDCCA3F5ACDE513DED37F53EBB997325FA23DBF272D62EA5E1EFDE726057816994FD09AEFE5DCEF77B63D4FEC1F63BCD322684A9087C1999F5D68E6DEFECCF283DC7A376E4465423EC8D5C07A40396289FB5F7EB6796BA9FF1D50A7ADF6C2CDA7B5BEBD167F6E4BACE8E96EEBA235A987BD7D1672C5157BCBD7667E81FE458B6667B7C7B287B30857FB5FE00F0D151E3ADA1B9B69DE53BBAA31D8795937EFC5CEA86BD3D948DED676D599EA31EAB37759B30875EA57564AD7AF2EEF53FD2CCB119FA4D5C2BD15ECC3E216355AA5F31523D94DB7ACCDFD46FDB36670D8F672FC31082C0777CBD847ABF36BFEE2AD968CFD248450D6A723A9B3D30CAA3E8A3FAB9D40B31B6F54F4FE5A1D4677D521747ECD8BF392E0682F4225DC3BC66D0DB633D99D3ACDF9B3FE5BE01BD37635E9B446DDBD0F76D7610AD3F085313F8CE2172E7B4F4E0E1ED5DCA19442E17399DCD02379322969FCB71C85BE3BA34EBFB6F4F8E814129D497CEA0AA711D5ECDEA8D3EB120B7A781887EC6787287BDE97AAAC432CB35BCADE3DF94C31FC9EF4043D6F01D5FE4F5C68E82DEA7EB071DADF314F9BB47D17AF3B6961D5A6B4DC5905EA3B5CBA14115D093DA9BB15D6DFAB4FEFFBBFDADDED7F15D963F6F16DFE93FCDB00628DFFC48FE6CFC52C6DBF1CE8CD7F0D9A67A571B5FF75EC7C33004BEB47034785807014F3A39771AE0A78DFBAC0D7ACBF72FFDFA67A1E2AF658C01684F5A87BCAB111E4D8EB0B1C96C7526D4B4D737AA55673EAD23AFFA5BA38526ADFB4FC07DAED7E7F6EAFE3404EEB90F0DC3B3A4C31CA254C4E9F20DE9E7FAD2105F6DDEF6F4D1BB28C7ACB4969D9F28833E8FA397952ED3D062A9863D2384BDCB52E6F33F79CDE8C7D1C66DE454B36C6CEBCB9A4B37A4EE5EDB4FEA80D1AEB128FD19C634DAF5D233E7E2CF8D82A3F6FD80FF22F09D4BCB0EE9D31D9D737FD62F6B11915FF49088F7AEC2DDD66BBEDA74E8DAD3014DD4E368E336724AC3D7D2EFB32C31EACDDCDF77B46BEC49FF525834869AE771BD5E949DF646ABBB8009087CE7D122F0FCB53C0F579EDE2DBCF3DAEB2C56A1EFBEDA618DB06D2C7702DE68CC4A38F7E6D8188C32B26DDDB6FDEF9CAFFF33F9EF0822D6DF11DDE95F3A9663C97D3EF7AE798FCDC714A57E06B824F09D4BCDC0339DD1FBA6A372676DDEF53DAEA43B86A66134758F45CD8EAB8E59193D05BCCB32EEF51EA17C47F80C50DA5FC9EF39ACF5E87613B0F53DA2D6A5EC33A9602E39CEF3D9A418016F395FCE9D7A19E88AC0773EA53AA4BF9677337AF75C6D42F4F53DD2107AD420E8891A039556C779EFBB09FCDF399AC91BDDD325657A11E5D8473FA6AE735A38BA11B2AD437B097A9FDCD4B97BBD8D78A348E83B879C6DDE51BF24BD3E46BC5E5A708D0253F977EB0F4013EB12076B63F76670B1D750E6EA005D85BD25DE2BDD717446DB72917BC0D97A1987BDCDE9ECD67B5F8BDDE173F038641D5737E95A7A7A9DCFDE0E702C6D1BD3D076D9F9BBBDB2B796C996F5EAB69C5FBDF75FCBFDEBE2C9F516B5CEF82A471F9B791CD523DB6B69BD0623B7B53DD1FF07A621F09DD759D0B9ED9C9E75F2B7AF51AA035232584E5FF36CF0368B74A0B22CDF062B5102B7BD8E9DCEDE3F9D1D939E06AD51CADD0C461C888EF23DC8270D08D6FAF0D7725C6F6EF70FF8B9FC79AD6C5FA3A6B46E6CD90E8E5467A4D23E764FED27F7E538B747D7407A6D3EB9F1C2B91CF5DEC8F5173010812F6963B51700A78F1C6E67A7947C447AEF7D4BD270FF2D1DA4BEE9D0EE056EAD3B47E977D80BB7E97F60DA7A36F9ACA21F6FA10B251C95A96DB01BE10989B39B6077AF8D9C6DF82CE1D55E3FA3755F883C72DD30392A0FAD6FC88CECEB138DB3D45FC00004BEA4CE3AA13F0EFE5C9259B76DEC85BECB72DD313A0A7AB7FF1D8D4EF59F7A0DC6CCEAE5886B9CDAA2D4A157F5E2DD6BE3E94CFE5EDB9112D227C8D44563A95DD6DD30C823C7B5E85C00E1D9B48DE846DB64A9277BCB5CA49B9D1D6DD6576B16F817E9EC2B83B03E37B3D82B7BD4E7FA81384AD48B7743AD27ED887A839EE5E833DD5DD26159DC4429E1EDB9732E802E087C8133DBF076FBDFE9BFD9FE7EF66FA3D8EE841EF533B69286FB51097AE3186DE02384A2576FEBC5DC75FD9DF71DADDE3893F68F18C7D7BEC7D91AE04F7F866772F41B9D0B20344B3A0077EC0D567EEDFCBF1E43379DB57F8A3C38B574433C916F0C6CDD7DF456B92215B12E4CBD5DBFFCE9A3CD771E63EEE178D5A65E19DB9BA51DDE6C7A6AFDD8FC2C41030C4BE00B3C31DA8065DBC1D381DE7777FDE61A9F61355A391C41F473F2743D72E8418BBAF1CE7B58AB9699EC6D02B6BD36CFDA9BA701AEBE6A5E5FEBAAE87D1F6072025F008E6C67FAD60CCAF606343AD5B1F5B07949E499EBC4F67557F712DECEE83D7BBD3BDFAD876B1D6A4B6F2A1EFDBFD49BFA64BBB1327944AADB01B211F802FCA603BD2F0D7E4B748ACDE2ED578F8F981ADCF15494D0B7C4B2367767B9BD998D7875ACB4BB8C62AF1C5FADC5FB2BF9EFF4DFEF5D43AE99BC3C01040CCBA66D007FD3C1BB966EE8F635E44B5FE7C7E61794A27CF1D6B60EACE9A8AE6CE54E387CF758B91E99D5B6ECEF5D537BD7C6DA57F5A44A5EDB3AF549FDEE3C00A199E10BCCEAEEFA6AFC291DA01C758CEFAE8F6CB0FF5DA9D93E4F6732F636EBA8F54C4DFA547B7DDADC4B377CF56496AFB57CE1DC9BEBBAC7A76A7AB0F661EE3EC9E13C00E1097C8199198C7E930E54D2CEF2D9BF258F52038E37D7464FE778EFFB19B8F144C91B06EA4F80BA7E6C7E7FD2FFE9ED663730114B3A003313F6E6B5ED2CA7BFE8CB9B6BA3C7EB49B0C61BE9F236B98CBAC48D9B29F0DED1F5D3639BDB933BF596A78480D004BEC02CF63A6E7B03691D6878AEC7418FEB9F2FD2351FDF869A91D6E72DF5B400F0CED1F5D3639BDB9356EBB5036425F0056670D4614EC31D1D68F8DBAC21CDACDF9BF7DE6EF6136D36EFDDF7FE126C0379B94959CE93FAD8790042B2862F302BE12E00B91CAD5F7EF533D19CAD4DFC7603B9271B2101F7D834AC8E3BF556C4BA1C40E00BB0C3060C442738C9C7B12497DEDB8D34B4DEBB2EDE7E479BA4C27BDAA936DCAC02BA664907803FE9D0115DE450A9B74027F2B184168E96A8C83593D08C4478463BD5D69DB5DA7BEBFB009310F802405FF6061651061BBD0E4C8550F0A7BD6BF9EBF56DA344C8CF3554DEDE269D674F42008420F00580BE451A6CF43CF0EC35AC86924ADC0CD9BEA6EB0EDE8BD4FECF20DD60D3CD622034812F00F425724012F9B31DD90EDE80DF4A841A82126004695DD6F30D6F6050025F807D3A6EF09CEB06C692CE68CBF59A403EDADE36D2BACC79004211F802FC9347E4E06F4F66E1B96E80BBCCAC87678EDA636D6F5B3F0EFE0CD09CC01700D8F364E072B67B3500F0DE9DF6D8ECD276DCC0024212F8021CD37966566FCABEC10E00D467962F00FF20F005D8A7F3CCCC84B700D017131500F86F025F80733ACFF4A275596DFDFE0030B2B3A593D6890ADA6200966511F8027379BAC6683ACB57279AA85ACF48FF2BC067008051DD79F2463B0CC07F13F802B3F8F288FACF45D8CBBCEEDE28B1691B00B4A7CF0A80C017E0C23A5BC29AA6CCE869B9779D00403B66F902B02C8BC017E0AE9FC9EFD09A19B500C01EFD5580C9097C81D9BC09C9FE5AFE5CA354279AD6CCA40580F9DCE9C7EAAF0220F005A6922324F3A81C111C0DE20CEE00604C4FFAB1FAAB009313F802BC2358A3A5BD415FC9C19DE52300A03FFAAB009312F8023CE75139222A15CA5A3E0200E2B8DBDEEBAF024C4CE00BCC284730E651392212CE02C0B89EB6F3425F8049097C81D908C41889A51600603E4FDA7F9314002624F00566952B28336382566ADFBCB873CDB81E00A0ACB5FD77D3178043025F6046B982328FC9114DA9B278E79A31830800EAF0C41A00A704BEC06C720762425FA210B802C05C7E2DF767FAEAAB024C44E00BCCA6C48C08411B2DFD5A9E0DF8BEBE1700D0DE8FE5FEF20EFAAA009311F80233CB1D5E9939416D3F36BF977EBCD3E3A300108FF619807F10F802B3CADD39B6B4032DD52C7766F902403C967600E0BF097C01F21082D1CA8F833F977E2F002086BBEDB3B0176012025F607639835A6118ADD42E7B7BD78D9B1E00D0D6595BFCD7622D5F8069087C8199DDDDE8E2AE9FC9EF30A2B370D94D0F00682377BF16808E097C81D9E50AA8D619133536CF82080C28012016A12F00CBB2087C0172CFC635BB9719B8A9010031097D0110F802D3CBD929B62E1A0000ADB9310B3039812FC09FA1AFD910709FEB0500E2D2B7059894C017E0B7AFB32174A89989D9430010D7765F895FC9EF000C4EE00BF0B76D78F5A5332C040300208A34F4D557059880C017E0B79FCBF7E51D6CDA060040243F963F67FC023038812FC06F3F36BFBFE910AF9BB6097D010000802604BE00FFF473F91DDA3E9DE52BF4050000009A11F802FC53FAD89B1D8EE1D8F6DA70A3030000A0B17FB7FE0000C1ADA1EFCFE5CF60EBAFCD9FF7C2606BA431831FCB9F21AF720F0000D098C017E09E6D909586BFE9DF030000003421F005784EB80B7FB2E40900004010D6F00500BE70030400002090FFF19FFFFCA7F56700000000002003337C01000000000621F005000000001884C017000000006010025F000000008041087C01000000000621F005000000001884C017000000006010025F000000008041087C01000000000621F005000000001884C017000000006010025F000000008041087C01000000000621F005000000001884C017000000006010025F000000008041087C01000000000621F005000000001884C017000000006010025F000000008041087C01000000000621F005000000001884C017000000006010025F000000008041087C01000000000621F005000000001884C017000000006010025F000000008041087C01000000000621F005000000001884C017000000006010025F000000008041087C01000000000621F005000000001884C017000000006010025F000000008041087C01000000000621F005000000001884C017000000006010025F000000008041087C01000000000621F005000000001884C017000000006010025F000000008041087C01000000000621F005000000001884C017000000006010025F000000008041087C01000000000621F005000000001884C017000000006010025F000000008041087C01000000000621F005000000001884C017000000006010025F000000008041087C01000000000621F005000000001884C017000000006010025F000000008041087C01000000000621F005000000001884C017000000006010025F000000008041087C01000000000621F005000000001884C017000000006010025F000000008041087C01000000000621F005000000001884C017000000006010025F000000008041FC7FCCC0741CE089BE130000000049454E44AE426082', 'hex') 68 | } 69 | 70 | const contentBufferData: Buffer = contentBuffer('test') 71 | const contentBufferArray: Array = splitBuffer(contentBufferData, 400) 72 | 73 | export function createChildInscriptionTapScript(): Array { 74 | 75 | const keyPair = wallet.ecPair; 76 | let childOrdinalStacks: any = [ 77 | toXOnly(keyPair.publicKey), 78 | opcodes.OP_CHECKSIG, 79 | opcodes.OP_FALSE, 80 | opcodes.OP_IF, 81 | Buffer.from("ord", "utf8"), 82 | 1, 83 | 1, 84 | Buffer.concat([Buffer.from(memeType, "utf8")]), 85 | 1, 86 | 2, 87 | pointerBuffer1, 88 | 1, 89 | 3, 90 | inscriptionBuffer, 91 | 1, 92 | 5, 93 | metadataBuffer, 94 | 1, 95 | 7, 96 | metaProtocol, 97 | opcodes.OP_0 98 | ]; 99 | contentBufferArray.forEach((item: Buffer) => { 100 | childOrdinalStacks.push(item) 101 | }) 102 | childOrdinalStacks.push(opcodes.OP_ENDIF) 103 | 104 | console.log(childOrdinalStacks) 105 | 106 | return childOrdinalStacks; 107 | } 108 | 109 | async function childInscribe() { 110 | const keyPair = wallet.ecPair; 111 | const childOrdinalStack = createChildInscriptionTapScript(); 112 | 113 | const ordinal_script = script.compile(childOrdinalStack); 114 | 115 | const scriptTree: Taptree = { 116 | output: ordinal_script, 117 | }; 118 | 119 | const redeem = { 120 | output: ordinal_script, 121 | redeemVersion: 192, 122 | }; 123 | 124 | const ordinal_p2tr = payments.p2tr({ 125 | internalPubkey: toXOnly(keyPair.publicKey), 126 | network, 127 | scriptTree, 128 | redeem, 129 | }); 130 | 131 | const address = ordinal_p2tr.address ?? ""; 132 | console.log("send coin to address", address); 133 | 134 | const utxos = await waitUntilUTXO(address as string); 135 | 136 | const psbt = new Psbt({ network }); 137 | const parentInscriptionUTXO = { 138 | txid: txhash, 139 | vout: 0, 140 | value: 546 141 | } 142 | psbt.addInput({ 143 | hash: parentInscriptionUTXO.txid, 144 | index: parentInscriptionUTXO.vout, 145 | witnessUtxo: { 146 | value: parentInscriptionUTXO.value, 147 | script: wallet.output, 148 | }, 149 | tapInternalKey: toXOnly(keyPair.publicKey), 150 | }); 151 | 152 | psbt.addInput({ 153 | hash: utxos[0].txid, 154 | index: utxos[0].vout, 155 | tapInternalKey: toXOnly(keyPair.publicKey), 156 | witnessUtxo: { value: utxos[0].value, script: ordinal_p2tr.output! }, 157 | tapLeafScript: [ 158 | { 159 | leafVersion: redeem.redeemVersion, 160 | script: redeem.output, 161 | controlBlock: ordinal_p2tr.witness![ordinal_p2tr.witness!.length - 1], 162 | }, 163 | ], 164 | }); 165 | 166 | 167 | const change = utxos[0].value - 546 * 2 - fee; 168 | 169 | psbt.addOutput({ 170 | address: receiveAddress, //Destination Address 171 | value: 546, 172 | }); 173 | 174 | psbt.addOutput({ 175 | address: receiveAddress, //Destination Address 176 | value: 546, 177 | }); 178 | 179 | psbt.addOutput({ 180 | address: receiveAddress, // Change address 181 | value: change, 182 | }); 183 | 184 | await signAndSend(keyPair, psbt); 185 | } 186 | 187 | childInscribe() 188 | 189 | export async function signAndSend( 190 | keypair: BTCSigner, 191 | psbt: Psbt, 192 | ) { 193 | const signer = tweakSigner(keypair, { network }) 194 | psbt.signInput(0, signer); 195 | psbt.signInput(1, keypair); 196 | psbt.finalizeAllInputs() 197 | const tx = psbt.extractTransaction(); 198 | console.log(tx.virtualSize()) 199 | console.log(tx.toHex()); 200 | 201 | // const txid = await broadcast(tx.toHex()); 202 | // console.log(`Success! Txid is ${txid}`); 203 | } 204 | 205 | export async function waitUntilUTXO(address: string) { 206 | return new Promise((resolve, reject) => { 207 | let intervalId: any; 208 | const checkForUtxo = async () => { 209 | try { 210 | const response: AxiosResponse = await blockstream.get( 211 | `/address/${address}/utxo` 212 | ); 213 | const data: IUTXO[] = response.data 214 | ? JSON.parse(response.data) 215 | : undefined; 216 | console.log(data); 217 | if (data.length > 0) { 218 | resolve(data); 219 | clearInterval(intervalId); 220 | } 221 | } catch (error) { 222 | reject(error); 223 | clearInterval(intervalId); 224 | } 225 | }; 226 | intervalId = setInterval(checkForUtxo, 4000); 227 | }); 228 | } 229 | 230 | export async function getTx(id: string): Promise { 231 | const response: AxiosResponse = await blockstream.get( 232 | `/tx/${id}/hex` 233 | ); 234 | return response.data; 235 | } 236 | 237 | const blockstream = new axios.Axios({ 238 | baseURL: `https://mempool.space/testnet/api`, 239 | // baseURL: `https://mempool.space/api`, 240 | }); 241 | 242 | export async function broadcast(txHex: string) { 243 | const response: AxiosResponse = await blockstream.post("/tx", txHex); 244 | return response.data; 245 | } 246 | 247 | function tapTweakHash(pubKey: Buffer, h: Buffer | undefined): Buffer { 248 | return crypto.taggedHash( 249 | "TapTweak", 250 | Buffer.concat(h ? [pubKey, h] : [pubKey]) 251 | ); 252 | } 253 | 254 | function toXOnly(pubkey: Buffer): Buffer { 255 | return pubkey.subarray(1, 33); 256 | } 257 | 258 | function tweakSigner(signer: any, opts: any = {}) { 259 | let privateKey = signer.privateKey; 260 | if (!privateKey) { 261 | throw new Error('Private key is required for tweaking signer!'); 262 | } 263 | if (signer.publicKey[0] === 3) { 264 | privateKey = ecc.privateNegate(privateKey); 265 | } 266 | const tweakedPrivateKey = ecc.privateAdd(privateKey, tapTweakHash(toXOnly(signer.publicKey), opts.tweakHash)); 267 | if (!tweakedPrivateKey) { 268 | throw new Error('Invalid tweaked private key!'); 269 | } 270 | return ECPair.fromPrivateKey(Buffer.from(tweakedPrivateKey), { 271 | network: opts.network, 272 | }); 273 | } 274 | 275 | interface IUTXO { 276 | txid: string; 277 | vout: number; 278 | status: { 279 | confirmed: boolean; 280 | block_height: number; 281 | block_hash: string; 282 | block_time: number; 283 | }; 284 | value: number; 285 | } 286 | 287 | -------------------------------------------------------------------------------- /Parent_Inscription.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Transaction, 3 | script, 4 | Psbt, 5 | initEccLib, 6 | networks, 7 | Signer as BTCSigner, 8 | crypto, 9 | payments, 10 | opcodes, 11 | address as Address 12 | } from "bitcoinjs-lib"; 13 | 14 | import { Taptree } from "bitcoinjs-lib/src/types"; 15 | import { ECPairFactory, ECPairAPI } from "ecpair"; 16 | import ecc from "@bitcoinerlab/secp256k1"; 17 | import axios, { AxiosResponse } from "axios"; 18 | import networkConfig from "config/network.config"; 19 | import { WIFWallet } from 'utils/WIFWallet' 20 | import { SeedWallet } from "utils/SeedWallet"; 21 | import cbor from 'cbor' 22 | //test 23 | const network = networks.testnet; 24 | // const network = networks.bitcoin; 25 | 26 | initEccLib(ecc as any); 27 | const ECPair: ECPairAPI = ECPairFactory(ecc); 28 | 29 | // const seed: string = process.env.MNEMONIC as string; 30 | // const networkType: string = networkConfig.networkType; 31 | // const wallet = new SeedWallet({ networkType: networkType, seed: seed }); 32 | 33 | const privateKey: string = process.env.PRIVATE_KEY as string; 34 | const networkType: string = networkConfig.networkType; 35 | const wallet = new WIFWallet({ networkType: networkType, privateKey: privateKey }); 36 | 37 | const receiveAddress: string = "tb1ppx220ln489s5wqu8mqgezm7twwpj0avcvle3vclpdkpqvdg3mwqsvydajn"; 38 | const metadata = { 39 | 'type': 'Bitmap', 40 | 'description': 'Bitmap Community Parent Ordinal' 41 | } 42 | const metadataBuffer = cbor.encode(metadata); 43 | const transaction_fee = 35000; 44 | 45 | 46 | export function createparentInscriptionTapScript(): Array { 47 | 48 | const keyPair = wallet.ecPair; 49 | const parentOrdinalStacks: any = [ 50 | toXOnly(keyPair.publicKey), 51 | opcodes.OP_CHECKSIG, 52 | opcodes.OP_FALSE, 53 | opcodes.OP_IF, 54 | Buffer.from("ord", "utf8"), 55 | 1, 56 | 1, 57 | Buffer.concat([Buffer.from("text/plain;charset=utf-8", "utf8")]), 58 | 1, 59 | 5, 60 | metadataBuffer, 61 | opcodes.OP_0, 62 | Buffer.concat([Buffer.from("364972.bitmap", "utf8")]), 63 | opcodes.OP_ENDIF, 64 | ]; 65 | return parentOrdinalStacks; 66 | } 67 | 68 | async function parentInscribe() { 69 | const keyPair = wallet.ecPair; 70 | const parentOrdinalStack = createparentInscriptionTapScript(); 71 | 72 | const ordinal_script = script.compile(parentOrdinalStack); 73 | 74 | const scriptTree: Taptree = { 75 | output: ordinal_script, 76 | }; 77 | 78 | const redeem = { 79 | output: ordinal_script, 80 | redeemVersion: 192, 81 | }; 82 | 83 | const ordinal_p2tr = payments.p2tr({ 84 | internalPubkey: toXOnly(keyPair.publicKey), 85 | network, 86 | scriptTree, 87 | redeem, 88 | }); 89 | 90 | const address = ordinal_p2tr.address ?? ""; 91 | console.log("send coin to address", address); 92 | 93 | const utxos = await waitUntilUTXO(address as string); 94 | console.log(`Using UTXO ${utxos[0].txid}:${utxos[0].vout}`); 95 | 96 | const psbt = new Psbt({ network }); 97 | 98 | psbt.addInput({ 99 | hash: utxos[0].txid, 100 | index: utxos[0].vout, 101 | tapInternalKey: toXOnly(keyPair.publicKey), 102 | witnessUtxo: { value: utxos[0].value, script: ordinal_p2tr.output! }, 103 | tapLeafScript: [ 104 | { 105 | leafVersion: redeem.redeemVersion, 106 | script: redeem.output, 107 | controlBlock: ordinal_p2tr.witness![ordinal_p2tr.witness!.length - 1], 108 | }, 109 | ], 110 | }); 111 | 112 | 113 | const change = utxos[0].value - 546 - transaction_fee; 114 | 115 | psbt.addOutput({ 116 | address: receiveAddress, //Destination Address 117 | value: 546, 118 | }); 119 | 120 | psbt.addOutput({ 121 | address: receiveAddress, // Change address 122 | value: change, 123 | }); 124 | 125 | await signAndSend(keyPair, psbt); 126 | } 127 | 128 | parentInscribe() 129 | 130 | export async function signAndSend( 131 | keypair: BTCSigner, 132 | psbt: Psbt, 133 | ) { 134 | psbt.signInput(0, keypair); 135 | psbt.finalizeAllInputs() 136 | const tx = psbt.extractTransaction(); 137 | 138 | console.log(tx.virtualSize()) 139 | console.log(tx.toHex()) 140 | 141 | // const txid = await broadcast(tx.toHex()); 142 | // console.log(`Success! Txid is ${txid}`); 143 | } 144 | 145 | export async function waitUntilUTXO(address: string) { 146 | return new Promise((resolve, reject) => { 147 | let intervalId: any; 148 | const checkForUtxo = async () => { 149 | try { 150 | const response: AxiosResponse = await blockstream.get( 151 | `/address/${address}/utxo` 152 | ); 153 | const data: IUTXO[] = response.data 154 | ? JSON.parse(response.data) 155 | : undefined; 156 | console.log(data); 157 | if (data.length > 0) { 158 | resolve(data); 159 | clearInterval(intervalId); 160 | } 161 | } catch (error) { 162 | reject(error); 163 | clearInterval(intervalId); 164 | } 165 | }; 166 | intervalId = setInterval(checkForUtxo, 4000); 167 | }); 168 | } 169 | export async function getTx(id: string): Promise { 170 | const response: AxiosResponse = await blockstream.get( 171 | `/tx/${id}/hex` 172 | ); 173 | return response.data; 174 | } 175 | const blockstream = new axios.Axios({ 176 | baseURL: `https://mempool.space/testnet/api`, 177 | // baseURL: `https://mempool.space/api`, 178 | }); 179 | export async function broadcast(txHex: string) { 180 | const response: AxiosResponse = await blockstream.post("/tx", txHex); 181 | return response.data; 182 | } 183 | function tapTweakHash(pubKey: Buffer, h: Buffer | undefined): Buffer { 184 | return crypto.taggedHash( 185 | "TapTweak", 186 | Buffer.concat(h ? [pubKey, h] : [pubKey]) 187 | ); 188 | } 189 | function toXOnly(pubkey: Buffer): Buffer { 190 | return pubkey.subarray(1, 33); 191 | } 192 | function tweakSigner(signer: any, opts: any = {}) { 193 | let privateKey = signer.privateKey; 194 | if (!privateKey) { 195 | throw new Error('Private key is required for tweaking signer!'); 196 | } 197 | if (signer.publicKey[0] === 3) { 198 | privateKey = ecc.privateNegate(privateKey); 199 | } 200 | const tweakedPrivateKey = ecc.privateAdd(privateKey, tapTweakHash(toXOnly(signer.publicKey), opts.tweakHash)); 201 | if (!tweakedPrivateKey) { 202 | throw new Error('Invalid tweaked private key!'); 203 | } 204 | return ECPair.fromPrivateKey(Buffer.from(tweakedPrivateKey), { 205 | network: opts.network, 206 | }); 207 | } 208 | interface IUTXO { 209 | txid: string; 210 | vout: number; 211 | status: { 212 | confirmed: boolean; 213 | block_height: number; 214 | block_hash: string; 215 | block_time: number; 216 | }; 217 | value: number; 218 | } -------------------------------------------------------------------------------- /RUNE_airdrop.ts: -------------------------------------------------------------------------------- 1 | import * as Bitcoin from "bitcoinjs-lib"; 2 | import ecc from "@bitcoinerlab/secp256k1"; 3 | import { 4 | SEED, 5 | STANDARD_RUNE_UTXO_VALUE, 6 | TESTNET, 7 | networkType, 8 | } from "../../config/config"; 9 | import { IUtxo } from "../../utils/types"; 10 | import { RuneId, Runestone, none } from "runelib"; 11 | import initializeWallet from "../wallet/initializeWallet"; 12 | import { SeedWallet } from "../wallet/SeedWallet"; 13 | import app from "../.."; 14 | Bitcoin.initEccLib(ecc); 15 | 16 | // Create dummy psbt for buyer offer 17 | export const SameRuneTransferTx = ( 18 | addressList: Array, 19 | amount: number, 20 | rune_id: string, 21 | networkType: string, 22 | runeUtxo: IUtxo 23 | ): string => { 24 | // Initialize seed Wallet 25 | const wallet: SeedWallet = initializeWallet( 26 | networkType, 27 | SEED, 28 | app.locals.walletIndex 29 | ); 30 | 31 | // Create psbt instance 32 | const psbt = new Bitcoin.Psbt({ 33 | network: 34 | networkType == TESTNET 35 | ? Bitcoin.networks.testnet 36 | : Bitcoin.networks.bitcoin, 37 | }); 38 | 39 | // Input all buyer Rune UTXOs for rune token 40 | psbt.addInput({ 41 | hash: runeUtxo.txid, 42 | index: runeUtxo.vout, 43 | witnessUtxo: { 44 | value: runeUtxo.value, 45 | script: wallet.output, 46 | }, 47 | tapInternalKey: Buffer.from(wallet.publicKey, "hex").subarray(1, 33), 48 | }); 49 | 50 | // Create Runestone 51 | const edicts: any = []; 52 | edicts.push({ 53 | id: new RuneId(+rune_id.split(":")[0], +rune_id.split(":")[1]), 54 | amount: 0, 55 | output: addressList.length + 1, 56 | }); 57 | const mintstone = new Runestone(edicts, none(), none(), none()); 58 | 59 | // Add output runestone 60 | psbt.addOutput({ 61 | script: mintstone.encipher(), 62 | value: 0, 63 | }); 64 | 65 | // Add output for rune airdrop 66 | for (let i = 0; i < addressList.length; i++) { 67 | psbt.addOutput({ 68 | address: addressList[i], 69 | value: STANDARD_RUNE_UTXO_VALUE, 70 | }); 71 | } 72 | 73 | // Sign psbt using admin wallet 74 | const signedPsbt: Bitcoin.Psbt = wallet.signPsbt(psbt, wallet.ecPair); 75 | 76 | // return Virtual Size of Runestone Transaction 77 | return signedPsbt.extractTransaction(true).toHex(); 78 | }; -------------------------------------------------------------------------------- /RUNE_etching.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Transaction, 3 | script, 4 | Psbt, 5 | address as Address, 6 | initEccLib, 7 | networks, 8 | Signer as BTCSigner, 9 | crypto, 10 | payments, 11 | } from "bitcoinjs-lib"; 12 | import { Taptree } from "bitcoinjs-lib/src/types"; 13 | import { ECPairFactory, ECPairAPI } from "ecpair"; 14 | import ecc from "@bitcoinerlab/secp256k1"; 15 | import axios, { AxiosResponse } from "axios"; 16 | import { 17 | Rune, 18 | RuneId, 19 | Runestone, 20 | EtchInscription, 21 | none, 22 | some, 23 | Terms, 24 | Range, 25 | Etching, 26 | } from "runelib"; 27 | import networkConfig from "config/network.config"; 28 | 29 | import { SeedWallet } from "utils/SeedWallet"; 30 | import { WIFWallet } from 'utils/WIFWallet' 31 | 32 | initEccLib(ecc as any); 33 | declare const window: any; 34 | const ECPair: ECPairAPI = ECPairFactory(ecc); 35 | const network = networks.testnet; 36 | const networkType: string = networkConfig.networkType; 37 | 38 | // const seed: string = process.env.MNEMONIC as string; 39 | // const wallet = new SeedWallet({ networkType: networkType, seed: seed }); 40 | 41 | const privateKey: string = process.env.PRIVATE_KEY as string; 42 | const wallet = new WIFWallet({ networkType: networkType, privateKey: privateKey }); 43 | 44 | async function etching() { 45 | 46 | const name = "HARMONITECH•RESURSIVE•RUNE"; 47 | 48 | const keyPair = wallet.ecPair; 49 | 50 | const ins = new EtchInscription(); 51 | 52 | const fee = 70000; 53 | 54 | const HTMLContent = ` 55 | 56 | 57 | 58 | 59 | Build Your Own Recursive Ordinal 60 | 61 | 62 |
63 | 64 |
65 | 66 | `; 67 | 68 | ins.setContent("text/html;charset=utf-8", Buffer.from(HTMLContent, "utf8")); 69 | ins.setRune(name); 70 | 71 | const etching_script_asm = `${toXOnly(keyPair.publicKey).toString( 72 | "hex" 73 | )} OP_CHECKSIG`; 74 | const etching_script = Buffer.concat([ 75 | script.fromASM(etching_script_asm), 76 | ins.encipher(), 77 | ]); 78 | 79 | const scriptTree: Taptree = { 80 | output: etching_script, 81 | }; 82 | 83 | const script_p2tr = payments.p2tr({ 84 | internalPubkey: toXOnly(keyPair.publicKey), 85 | scriptTree, 86 | network, 87 | }); 88 | 89 | const etching_redeem = { 90 | output: etching_script, 91 | redeemVersion: 192, 92 | }; 93 | 94 | const etching_p2tr = payments.p2tr({ 95 | internalPubkey: toXOnly(keyPair.publicKey), 96 | scriptTree, 97 | redeem: etching_redeem, 98 | network, 99 | }); 100 | 101 | const address = script_p2tr.address ?? ""; 102 | console.log("send coin to address", address); 103 | 104 | const utxos = await waitUntilUTXO(address as string); 105 | console.log(`Using UTXO ${utxos[0].txid}:${utxos[0].vout}`); 106 | 107 | const psbt = new Psbt({ network }); 108 | 109 | psbt.addInput({ 110 | hash: utxos[0].txid, 111 | index: utxos[0].vout, 112 | witnessUtxo: { value: utxos[0].value, script: script_p2tr.output! }, 113 | tapLeafScript: [ 114 | { 115 | leafVersion: etching_redeem.redeemVersion, 116 | script: etching_redeem.output, 117 | controlBlock: etching_p2tr.witness![etching_p2tr.witness!.length - 1], 118 | }, 119 | ], 120 | }); 121 | 122 | const rune = Rune.fromName(name); 123 | 124 | const terms = new Terms( 125 | 1000, 126 | 10000, 127 | new Range(none(), none()), 128 | new Range(none(), none()) 129 | ); 130 | 131 | const etching = new Etching( 132 | some(1), 133 | some(1000000), 134 | some(rune), 135 | none(), 136 | some("$"), 137 | some(terms), 138 | true 139 | ); 140 | 141 | const stone = new Runestone([], some(etching), none(), none()); 142 | 143 | psbt.addOutput({ 144 | script: stone.encipher(), 145 | value: 0, 146 | }); 147 | 148 | const change = utxos[0].value - 546 - fee; 149 | 150 | psbt.addOutput({ 151 | address: "tb1ppx220ln489s5wqu8mqgezm7twwpj0avcvle3vclpdkpqvdg3mwqsvydajn", // change address 152 | value: 546, 153 | }); 154 | 155 | psbt.addOutput({ 156 | address: "tb1ppx220ln489s5wqu8mqgezm7twwpj0avcvle3vclpdkpqvdg3mwqsvydajn", // change address 157 | value: change, 158 | }); 159 | 160 | await signAndSend(keyPair, psbt, address as string); 161 | } 162 | 163 | // main 164 | etching(); 165 | 166 | const blockstream = new axios.Axios({ 167 | baseURL: `https://mempool.space/testnet/api`, 168 | }); 169 | 170 | export async function waitUntilUTXO(address: string) { 171 | return new Promise((resolve, reject) => { 172 | let intervalId: any; 173 | const checkForUtxo = async () => { 174 | try { 175 | const response: AxiosResponse = await blockstream.get( 176 | `/address/${address}/utxo` 177 | ); 178 | const data: IUTXO[] = response.data 179 | ? JSON.parse(response.data) 180 | : undefined; 181 | console.log(data); 182 | if (data.length > 0) { 183 | resolve(data); 184 | clearInterval(intervalId); 185 | } 186 | } catch (error) { 187 | reject(error); 188 | clearInterval(intervalId); 189 | } 190 | }; 191 | intervalId = setInterval(checkForUtxo, 5000); 192 | }); 193 | } 194 | 195 | export async function getTx(id: string): Promise { 196 | const response: AxiosResponse = await blockstream.get( 197 | `/tx/${id}/hex` 198 | ); 199 | return response.data; 200 | } 201 | 202 | export async function signAndSend( 203 | keyPair: BTCSigner, 204 | psbt: Psbt, 205 | address: string 206 | ) { 207 | if (process.env.NODE) { 208 | psbt.signInput(0, keyPair); 209 | psbt.finalizeAllInputs(); 210 | 211 | const tx = psbt.extractTransaction(); 212 | console.log(`Broadcasting Transaction Hex: ${tx.toHex()}`); 213 | console.log(tx.virtualSize()) 214 | // const txid = await broadcast(tx.toHex()); 215 | // console.log(`Success! Txid is ${txid}`); 216 | } else { 217 | // in browser 218 | 219 | try { 220 | let res = await window.unisat.signPsbt(psbt.toHex(), { 221 | toSignInputs: [ 222 | { 223 | index: 0, 224 | address: address, 225 | }, 226 | ], 227 | }); 228 | 229 | console.log("signed psbt", res); 230 | 231 | res = await window.unisat.pushPsbt(res); 232 | 233 | console.log("txid", res); 234 | } catch (e) { 235 | console.log(e); 236 | } 237 | } 238 | } 239 | 240 | export async function broadcast(txHex: string) { 241 | const response: AxiosResponse = await blockstream.post("/tx", txHex); 242 | return response.data; 243 | } 244 | 245 | function tapTweakHash(pubKey: Buffer, h: Buffer | undefined): Buffer { 246 | return crypto.taggedHash( 247 | "TapTweak", 248 | Buffer.concat(h ? [pubKey, h] : [pubKey]) 249 | ); 250 | } 251 | 252 | function toXOnly(pubkey: Buffer): Buffer { 253 | return pubkey.subarray(1, 33); 254 | } 255 | 256 | function tweakSigner(signer: BTCSigner, opts: any = {}): BTCSigner { 257 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 258 | // @ts-ignore 259 | let privateKey: Uint8Array | undefined = signer.privateKey!; 260 | if (!privateKey) { 261 | throw new Error("Private key is required for tweaking signer!"); 262 | } 263 | if (signer.publicKey[0] === 3) { 264 | privateKey = ecc.privateNegate(privateKey); 265 | } 266 | 267 | const tweakedPrivateKey = ecc.privateAdd( 268 | privateKey, 269 | tapTweakHash(toXOnly(signer.publicKey), opts.tweakHash) 270 | ); 271 | if (!tweakedPrivateKey) { 272 | throw new Error("Invalid tweaked private key!"); 273 | } 274 | 275 | return ECPair.fromPrivateKey(Buffer.from(tweakedPrivateKey), { 276 | network: opts.network, 277 | }); 278 | } 279 | 280 | interface IUTXO { 281 | txid: string; 282 | vout: number; 283 | status: { 284 | confirmed: boolean; 285 | block_height: number; 286 | block_hash: string; 287 | block_time: number; 288 | }; 289 | value: number; 290 | } 291 | -------------------------------------------------------------------------------- /RUNE_minting.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Transaction, 3 | script, 4 | Psbt, 5 | address as Address, 6 | initEccLib, 7 | networks, 8 | Signer as BTCSigner, 9 | crypto, 10 | payments, 11 | } from "bitcoinjs-lib"; 12 | import { ECPairFactory, ECPairAPI } from "ecpair"; 13 | import ecc from "@bitcoinerlab/secp256k1"; 14 | import axios, { AxiosResponse } from "axios"; 15 | import { Rune, RuneId, Runestone, EtchInscription, none, some, Terms, Range, Etching } from "runelib"; 16 | import networkConfig from "config/network.config"; 17 | import { SeedWallet } from "utils/SeedWallet"; 18 | import { WIFWallet } from 'utils/WIFWallet' 19 | 20 | 21 | initEccLib(ecc as any); 22 | declare const window: any; 23 | const ECPair: ECPairAPI = ECPairFactory(ecc); 24 | const network = networks.testnet; 25 | const networkType: string = networkConfig.networkType; 26 | 27 | const privateKey: string = process.env.PRIVATE_KEY as string; 28 | const wallet = new WIFWallet({ networkType: networkType, privateKey: privateKey }); 29 | 30 | // const seed: string = process.env.MNEMONIC as string; 31 | // const wallet = new SeedWallet({ networkType: networkType, seed: seed }); 32 | 33 | async function mintWithTaproot() { 34 | 35 | const keyPair = wallet.ecPair; 36 | const mintstone = new Runestone([], none(), some(new RuneId(2817883, 2295)), some(1)); 37 | 38 | const tweakedSigner = tweakSigner(keyPair, { network }); 39 | // Generate an address from the tweaked public key 40 | const p2pktr = payments.p2tr({ 41 | pubkey: toXOnly(tweakedSigner.publicKey), 42 | network 43 | }); 44 | const address = p2pktr.address ?? ""; 45 | console.log(`Waiting till UTXO is detected at this Address: ${address}`); 46 | 47 | const utxos = await waitUntilUTXO(address as string); 48 | const filteredUtxos = utxos.filter((utxo) => utxo.value > 60000); 49 | 50 | // console.log(`Using UTXO ${utxos[0].txid}:${utxos[0].vout}`); 51 | 52 | const psbt = new Psbt({ network }); 53 | psbt.addInput({ 54 | hash: filteredUtxos[1].txid, 55 | index: filteredUtxos[1].vout, 56 | witnessUtxo: { value: filteredUtxos[1].value, script: p2pktr.output! }, 57 | tapInternalKey: toXOnly(keyPair.publicKey) 58 | }); 59 | 60 | psbt.addOutput({ 61 | script: mintstone.encipher(), 62 | value: 0 63 | }); 64 | 65 | psbt.addOutput({ 66 | address: "tb1ppx220ln489s5wqu8mqgezm7twwpj0avcvle3vclpdkpqvdg3mwqsvydajn", // rune receive address 67 | value: 546 68 | }); 69 | 70 | const fee = 20000; 71 | 72 | const change = filteredUtxos[1].value - fee - 546; 73 | 74 | psbt.addOutput({ 75 | address: "tb1ppx220ln489s5wqu8mqgezm7twwpj0avcvle3vclpdkpqvdg3mwqsvydajn", // change address 76 | value: change 77 | }); 78 | 79 | await signAndSend(tweakedSigner, psbt, address as string); 80 | 81 | } 82 | 83 | // main 84 | mintWithTaproot(); 85 | 86 | 87 | 88 | const blockstream = new axios.Axios({ 89 | baseURL: `https://mempool.space/testnet/api` 90 | }); 91 | 92 | export async function waitUntilUTXO(address: string) { 93 | return new Promise((resolve, reject) => { 94 | let intervalId: any; 95 | const checkForUtxo = async () => { 96 | try { 97 | const response: AxiosResponse = await blockstream.get(`/address/${address}/utxo`); 98 | const data: IUTXO[] = response.data ? JSON.parse(response.data) : undefined; 99 | if (data.length > 0) { 100 | resolve(data); 101 | clearInterval(intervalId); 102 | } 103 | } catch (error) { 104 | reject(error); 105 | clearInterval(intervalId); 106 | } 107 | }; 108 | intervalId = setInterval(checkForUtxo, 3000); 109 | }); 110 | } 111 | 112 | export async function getTx(id: string): Promise { 113 | const response: AxiosResponse = await blockstream.get(`/tx/${id}/hex`); 114 | return response.data; 115 | } 116 | 117 | export async function signAndSend(keyPair: BTCSigner, psbt: Psbt, address: string) { 118 | if (process.env.NODE) { 119 | 120 | psbt.signInput(0, keyPair); 121 | psbt.finalizeAllInputs(); 122 | 123 | const tx = psbt.extractTransaction(); 124 | console.log(tx.virtualSize()) 125 | console.log(`Broadcasting Transaction Hex: ${tx.toHex()}`); 126 | // const txid = await broadcast(tx.toHex()); 127 | // console.log(`Success! Txid is ${txid}`); 128 | 129 | } else { // in browser 130 | 131 | try { 132 | let res = await window.unisat.signPsbt(psbt.toHex(), { 133 | toSignInputs: [ 134 | { 135 | index: 0, 136 | address: address, 137 | } 138 | ] 139 | }); 140 | 141 | console.log("signed psbt", res) 142 | 143 | res = await window.unisat.pushPsbt(res); 144 | 145 | console.log("txid", res) 146 | } catch (e) { 147 | console.log(e); 148 | } 149 | } 150 | 151 | } 152 | 153 | export async function broadcast(txHex: string) { 154 | const response: AxiosResponse = await blockstream.post('/tx', txHex); 155 | return response.data; 156 | } 157 | 158 | function tapTweakHash(pubKey: Buffer, h: Buffer | undefined): Buffer { 159 | return crypto.taggedHash( 160 | "TapTweak", 161 | Buffer.concat(h ? [pubKey, h] : [pubKey]) 162 | ); 163 | } 164 | 165 | function toXOnly(pubkey: Buffer): Buffer { 166 | return pubkey.subarray(1, 33); 167 | } 168 | 169 | function tweakSigner(signer: BTCSigner, opts: any = {}): BTCSigner { 170 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 171 | // @ts-ignore 172 | let privateKey: Uint8Array | undefined = signer.privateKey!; 173 | if (!privateKey) { 174 | throw new Error("Private key is required for tweaking signer!"); 175 | } 176 | if (signer.publicKey[0] === 3) { 177 | privateKey = ecc.privateNegate(privateKey); 178 | } 179 | 180 | const tweakedPrivateKey = ecc.privateAdd( 181 | privateKey, 182 | tapTweakHash(toXOnly(signer.publicKey), opts.tweakHash) 183 | ); 184 | if (!tweakedPrivateKey) { 185 | throw new Error("Invalid tweaked private key!"); 186 | } 187 | 188 | return ECPair.fromPrivateKey(Buffer.from(tweakedPrivateKey), { 189 | network: opts.network, 190 | }); 191 | } 192 | 193 | interface IUTXO { 194 | txid: string; 195 | vout: number; 196 | status: { 197 | confirmed: boolean; 198 | block_height: number; 199 | block_hash: string; 200 | block_time: number; 201 | }; 202 | value: number; 203 | } 204 | -------------------------------------------------------------------------------- /RUNE_transfer.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Transaction, 3 | script, 4 | Psbt, 5 | address as Address, 6 | initEccLib, 7 | networks, 8 | Signer as BTCSigner, 9 | crypto, 10 | payments, 11 | } from "bitcoinjs-lib"; 12 | import { ECPairFactory, ECPairAPI } from "ecpair"; 13 | import ecc from "@bitcoinerlab/secp256k1"; 14 | import axios, { AxiosResponse } from "axios"; 15 | import { 16 | Rune, 17 | RuneId, 18 | Runestone, 19 | EtchInscription, 20 | none, 21 | some, 22 | Terms, 23 | Range, 24 | Etching, 25 | } from "runelib"; 26 | import networkConfig from "config/network.config"; 27 | 28 | import { SeedWallet } from "utils/SeedWallet"; 29 | import { WIFWallet } from 'utils/WIFWallet' 30 | 31 | initEccLib(ecc as any); 32 | declare const window: any; 33 | const ECPair: ECPairAPI = ECPairFactory(ecc); 34 | const network = networks.testnet; 35 | const networkType: string = networkConfig.networkType; 36 | 37 | // const seed: string = process.env.MNEMONIC as string; 38 | // const wallet = new SeedWallet({ networkType: networkType, seed: seed }); 39 | 40 | const privateKey: string = process.env.PRIVATE_KEY as string; 41 | const wallet = new WIFWallet({ networkType: networkType, privateKey: privateKey }); 42 | 43 | async function mintWithTaproot() { 44 | 45 | const keyPair = wallet.ecPair; 46 | const edicts: any = []; 47 | edicts.push({ 48 | id: new RuneId(2817883, 2295), 49 | amount: 1000, 50 | output: 2, 51 | }); 52 | edicts.push({ 53 | id: new RuneId(2817883, 2295), 54 | amount: 1000, 55 | output: 3, 56 | }); 57 | edicts.push({ 58 | id: new RuneId(2817883, 2295), 59 | amount: 1000, 60 | output: 50, 61 | }); 62 | const mintstone = new Runestone( 63 | edicts, 64 | none(), 65 | none(), 66 | none() 67 | ); 68 | 69 | const tweakedSigner = tweakSigner(keyPair, { network }); 70 | // Generate an address from the tweaked public key 71 | const p2pktr = payments.p2tr({ 72 | pubkey: toXOnly(tweakedSigner.publicKey), 73 | network, 74 | }); 75 | const address = p2pktr.address ?? ""; 76 | console.log(`Waiting till UTXO is detected at this Address: ${address}`); 77 | 78 | const runeUTXO = { 79 | txid: '000d62d718f9f958b9c69580e3146ef3d69a3c57005e955cf49da1fe8ba04503', 80 | vout: 1, 81 | value: 546 82 | }; 83 | const btcUTXO = { 84 | txid: '4e364992dc0c91eb6f997bd1e6a562e97600c4b1427f31f0517d18924e493a24', 85 | vout: 1, 86 | value: 9905934, 87 | } 88 | 89 | const psbt = new Psbt({ network }); 90 | psbt.addInput({ 91 | hash: runeUTXO.txid, 92 | index: runeUTXO.vout, 93 | witnessUtxo: { value: runeUTXO.value, script: p2pktr.output! }, 94 | tapInternalKey: toXOnly(keyPair.publicKey), 95 | }); 96 | 97 | psbt.addInput({ 98 | hash: btcUTXO.txid, 99 | index: btcUTXO.vout, 100 | witnessUtxo: { value: btcUTXO.value, script: p2pktr.output! }, 101 | tapInternalKey: toXOnly(keyPair.publicKey), 102 | }); 103 | 104 | psbt.addOutput({ 105 | script: mintstone.encipher(), 106 | value: 0, 107 | }); 108 | 109 | psbt.addOutput({ 110 | address: "tb1ppx220ln489s5wqu8mqgezm7twwpj0avcvle3vclpdkpqvdg3mwqsvydajn", // rune receive address 111 | value: 546, 112 | }); 113 | 114 | psbt.addOutput({ 115 | address: "tb1ppx220ln489s5wqu8mqgezm7twwpj0avcvle3vclpdkpqvdg3mwqsvydajn", // rune receive address 116 | value: 546, 117 | }); 118 | 119 | psbt.addOutput({ 120 | address: "tb1ppx220ln489s5wqu8mqgezm7twwpj0avcvle3vclpdkpqvdg3mwqsvydajn", // rune receive address 121 | value: 546, 122 | }); 123 | 124 | psbt.addOutput({ 125 | address: "tb1ppx220ln489s5wqu8mqgezm7twwpj0avcvle3vclpdkpqvdg3mwqsvydajn", // rune receive address 126 | value: 546, 127 | }); 128 | 129 | const fee = 100000; 130 | 131 | const change = btcUTXO.value - fee - 2200; 132 | 133 | 134 | psbt.addOutput({ 135 | address: "tb1ppx220ln489s5wqu8mqgezm7twwpj0avcvle3vclpdkpqvdg3mwqsvydajn", // change address 136 | value: change, 137 | }); 138 | 139 | await signAndSend(tweakedSigner, psbt, address as string); 140 | } 141 | 142 | // main 143 | mintWithTaproot(); 144 | 145 | export const blockstream = new axios.Axios({ 146 | baseURL: `https://mempool.space/testnet/api`, 147 | }); 148 | 149 | export async function waitUntilUTXO(address: string) { 150 | return new Promise((resolve, reject) => { 151 | let intervalId: any; 152 | const checkForUtxo = async () => { 153 | try { 154 | const response: AxiosResponse = await blockstream.get( 155 | `/address/${address}/utxo` 156 | ); 157 | const data: IUTXO[] = response.data 158 | ? JSON.parse(response.data) 159 | : undefined; 160 | console.log(data); 161 | if (data.length > 0) { 162 | resolve(data); 163 | clearInterval(intervalId); 164 | } 165 | } catch (error) { 166 | reject(error); 167 | clearInterval(intervalId); 168 | } 169 | }; 170 | intervalId = setInterval(checkForUtxo, 10000); 171 | }); 172 | } 173 | 174 | export async function getTx(id: string): Promise { 175 | const response: AxiosResponse = await blockstream.get( 176 | `/tx/${id}/hex` 177 | ); 178 | return response.data; 179 | } 180 | 181 | export async function signAndSend( 182 | keyPair: BTCSigner, 183 | psbt: Psbt, 184 | address: string 185 | ) { 186 | if (process.env.NODE) { 187 | 188 | psbt.signInput(0, keyPair); 189 | psbt.signInput(1, keyPair); 190 | 191 | psbt.finalizeAllInputs(); 192 | 193 | const tx = psbt.extractTransaction(); 194 | console.log(`Broadcasting Transaction Hex: ${tx.toHex()}`); 195 | const txid = await broadcast(tx.toHex()); 196 | console.log(`Success! Txid is ${txid}`); 197 | } else { 198 | // in browser 199 | 200 | try { 201 | let res = await window.unisat.signPsbt(psbt.toHex(), { 202 | toSignInputs: [ 203 | { 204 | index: 0, 205 | address: address, 206 | }, 207 | ], 208 | }); 209 | 210 | console.log("signed psbt", res); 211 | 212 | res = await window.unisat.pushPsbt(res); 213 | 214 | console.log("txid", res); 215 | } catch (e) { 216 | console.log(e); 217 | } 218 | } 219 | } 220 | 221 | export async function broadcast(txHex: string) { 222 | const blockstream = new axios.Axios({ 223 | baseURL: `https://mempool.space/testnet/api`, 224 | }); 225 | 226 | const response: AxiosResponse = await blockstream.post("/tx", txHex); 227 | return response.data; 228 | } 229 | 230 | function tapTweakHash(pubKey: Buffer, h: Buffer | undefined): Buffer { 231 | return crypto.taggedHash( 232 | "TapTweak", 233 | Buffer.concat(h ? [pubKey, h] : [pubKey]) 234 | ); 235 | } 236 | 237 | function toXOnly(pubkey: Buffer): Buffer { 238 | return pubkey.subarray(1, 33); 239 | } 240 | 241 | function tweakSigner(signer: BTCSigner, opts: any = {}): BTCSigner { 242 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 243 | // @ts-ignore 244 | let privateKey: Uint8Array | undefined = signer.privateKey!; 245 | if (!privateKey) { 246 | throw new Error("Private key is required for tweaking signer!"); 247 | } 248 | if (signer.publicKey[0] === 3) { 249 | privateKey = ecc.privateNegate(privateKey); 250 | } 251 | 252 | const tweakedPrivateKey = ecc.privateAdd( 253 | privateKey, 254 | tapTweakHash(toXOnly(signer.publicKey), opts.tweakHash) 255 | ); 256 | if (!tweakedPrivateKey) { 257 | throw new Error("Invalid tweaked private key!"); 258 | } 259 | 260 | return ECPair.fromPrivateKey(Buffer.from(tweakedPrivateKey), { 261 | network: opts.network, 262 | }); 263 | } 264 | 265 | interface IUTXO { 266 | txid: string; 267 | vout: number; 268 | status: { 269 | confirmed: boolean; 270 | block_height: number; 271 | block_hash: string; 272 | block_time: number; 273 | }; 274 | value: number; 275 | } 276 | -------------------------------------------------------------------------------- /Recursive_Rune.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Transaction, 3 | script, 4 | Psbt, 5 | initEccLib, 6 | networks, 7 | Signer as BTCSigner, 8 | crypto, 9 | payments, 10 | opcodes, 11 | address as Address 12 | } from "bitcoinjs-lib"; 13 | import { Taptree } from "bitcoinjs-lib/src/types"; 14 | import { ECPairFactory, ECPairAPI } from "ecpair"; 15 | import ecc from "@bitcoinerlab/secp256k1"; 16 | import axios, { AxiosResponse } from "axios"; 17 | import networkConfig from "config/network.config"; 18 | import { WIFWallet } from 'utils/WIFWallet' 19 | import { SeedWallet } from "utils/SeedWallet"; 20 | import cbor from 'cbor'; 21 | import { 22 | Rune, 23 | RuneId, 24 | Runestone, 25 | EtchInscription, 26 | none, 27 | some, 28 | Terms, 29 | Range, 30 | Etching, 31 | } from "runelib"; 32 | //test 33 | const network = networks.testnet; 34 | // const network = networks.bitcoin; 35 | 36 | initEccLib(ecc as any); 37 | const ECPair: ECPairAPI = ECPairFactory(ecc); 38 | 39 | export const contentBuffer = (content: string) => { 40 | return Buffer.from(content, 'utf8') 41 | } 42 | // const seed: string = process.env.MNEMONIC as string; 43 | // const networkType: string = networkConfig.networkType; 44 | // const wallet = new SeedWallet({ networkType: networkType, seed: seed }); 45 | 46 | const privateKey: string = process.env.PRIVATE_KEY as string; 47 | const networkType: string = networkConfig.networkType; 48 | const wallet = new WIFWallet({ networkType: networkType, privateKey: privateKey }); 49 | 50 | // input data 51 | const txhash: string = 'ea4303aaa2c7939931a2ba129c9fc915d1905d441f2a74b6cd694c71665c7682'; 52 | const memeType: string = 'text/html;charset=utf-8'; 53 | const metaProtocol: Buffer = Buffer.concat([Buffer.from("harmonitech.team", "utf8")]); 54 | const receiveAddress: string = 'tb1ppx220ln489s5wqu8mqgezm7twwpj0avcvle3vclpdkpqvdg3mwqsvydajn'; 55 | const metadata = { 56 | 'type': 'HarmoniTech', 57 | 'description': 'HarmoniTech team Recursive Rune' 58 | } 59 | const fee = 130000; 60 | const parentInscriptionTXID: string = 'ea4303aaa2c7939931a2ba129c9fc915d1905d441f2a74b6cd694c71665c7682'; 61 | const contentBufferData: Buffer = contentBuffer(` 62 | 63 | 64 | 65 | 66 | Build Your Own Recursive Ordinal 67 | 68 | 69 |
70 | 71 |
72 | 73 | 74 | `); 75 | 76 | const revealtxIDBuffer = Buffer.from(parentInscriptionTXID, 'hex'); 77 | const inscriptionBuffer = revealtxIDBuffer.reverse(); 78 | const pointer1: number = 546 * 1; 79 | const pointer2: number = 546 * 2; 80 | const pointer3: number = 546 * 3; 81 | const pointer4: number = 546 * 4; 82 | let pointerBuffer : Array = []; 83 | pointerBuffer.push(Buffer.from(pointer1.toString(16).padStart(4, '0'), 'hex').reverse()); 84 | pointerBuffer.push(Buffer.from(pointer2.toString(16).padStart(4, '0'), 'hex').reverse()); 85 | pointerBuffer.push(Buffer.from(pointer3.toString(16).padStart(4, '0'), 'hex').reverse()); 86 | pointerBuffer.push(Buffer.from(pointer4.toString(16).padStart(4, '0'), 'hex').reverse()); 87 | const metadataBuffer = cbor.encode(metadata); 88 | 89 | const splitBuffer = (buffer: Buffer, chunkSize: number) => { 90 | let chunks = []; 91 | for (let i = 0; i < buffer.length; i += chunkSize) { 92 | const chunk = buffer.subarray(i, i + chunkSize); 93 | chunks.push(chunk); 94 | } 95 | return chunks; 96 | }; 97 | const contentBufferArray: Array = splitBuffer(contentBufferData, 450) 98 | 99 | export function createChildInscriptionTapScript(): Array { 100 | 101 | const keyPair = wallet.ecPair; 102 | let childOrdinalStacks: any = [ 103 | toXOnly(keyPair.publicKey), 104 | opcodes.OP_CHECKSIG, 105 | ]; 106 | for (let i = 0; i < 4; i++) { 107 | childOrdinalStacks.push( 108 | opcodes.OP_FALSE, 109 | opcodes.OP_IF, 110 | Buffer.from("ord", "utf8"), 111 | 1, 112 | 1, 113 | Buffer.concat([Buffer.from(memeType, "utf8")]), 114 | 1, 115 | 2, 116 | pointerBuffer[i], 117 | 1, 118 | 3, 119 | inscriptionBuffer, 120 | 1, 121 | 5, 122 | metadataBuffer, 123 | 1, 124 | 7, 125 | metaProtocol, 126 | opcodes.OP_0 127 | ); 128 | contentBufferArray.forEach((item: Buffer) => { 129 | childOrdinalStacks.push(item) 130 | }) 131 | childOrdinalStacks.push(opcodes.OP_ENDIF) 132 | } 133 | return childOrdinalStacks; 134 | } 135 | 136 | async function childInscribe() { 137 | const keyPair = wallet.ecPair; 138 | const childOrdinalStack = createChildInscriptionTapScript(); 139 | 140 | console.log(childOrdinalStack) 141 | 142 | const ordinal_script = script.compile(childOrdinalStack); 143 | 144 | const scriptTree: Taptree = { 145 | output: ordinal_script, 146 | }; 147 | 148 | const redeem = { 149 | output: ordinal_script, 150 | redeemVersion: 192, 151 | }; 152 | 153 | const ordinal_p2tr = payments.p2tr({ 154 | internalPubkey: toXOnly(keyPair.publicKey), 155 | network, 156 | scriptTree, 157 | redeem, 158 | }); 159 | 160 | const address = ordinal_p2tr.address ?? ""; 161 | console.log("send coin to address", address); 162 | 163 | const utxos = await waitUntilUTXO(address as string); 164 | 165 | const psbt = new Psbt({ network }); 166 | 167 | const parentInscriptionUTXO = { 168 | txid: txhash, 169 | vout: 1, 170 | value: 546 171 | } 172 | psbt.addInput({ 173 | hash: parentInscriptionUTXO.txid, 174 | index: parentInscriptionUTXO.vout, 175 | witnessUtxo: { 176 | value: parentInscriptionUTXO.value, 177 | script: wallet.output, 178 | }, 179 | tapInternalKey: toXOnly(keyPair.publicKey), 180 | }); 181 | 182 | psbt.addInput({ 183 | hash: utxos[0].txid, 184 | index: utxos[0].vout, 185 | tapInternalKey: toXOnly(keyPair.publicKey), 186 | witnessUtxo: { value: utxos[0].value, script: ordinal_p2tr.output! }, 187 | tapLeafScript: [ 188 | { 189 | leafVersion: redeem.redeemVersion, 190 | script: redeem.output, 191 | controlBlock: ordinal_p2tr.witness![ordinal_p2tr.witness!.length - 1], 192 | }, 193 | ], 194 | }); 195 | 196 | const edicts: any = []; 197 | edicts.push({ 198 | id: new RuneId(2817883, 2295), 199 | amount: 10000, 200 | output: 2, 201 | }); 202 | edicts.push({ 203 | id: new RuneId(2817883, 2295), 204 | amount: 10000, 205 | output: 3, 206 | }); 207 | edicts.push({ 208 | id: new RuneId(2817883, 2295), 209 | amount: 10000, 210 | output: 4, 211 | }); 212 | edicts.push({ 213 | id: new RuneId(2817883, 2295), 214 | amount: 10000, 215 | output: 5, 216 | }); 217 | const mintstone = new Runestone( 218 | edicts, 219 | none(), 220 | none(), 221 | none() 222 | ); 223 | 224 | psbt.addOutput({ 225 | script: mintstone.encipher(), 226 | value: 0, 227 | }); 228 | 229 | const change = utxos[0].value - 546 * 5 - fee; 230 | 231 | psbt.addOutput({ 232 | address: receiveAddress, //Destination Address 233 | value: 546, 234 | }); 235 | 236 | psbt.addOutput({ 237 | address: receiveAddress, //Destination Address 238 | value: 546, 239 | }); 240 | 241 | psbt.addOutput({ 242 | address: receiveAddress, //Destination Address 243 | value: 546, 244 | }); 245 | 246 | psbt.addOutput({ 247 | address: receiveAddress, //Destination Address 248 | value: 546, 249 | }); 250 | 251 | psbt.addOutput({ 252 | address: receiveAddress, //Destination Address 253 | value: 546, 254 | }); 255 | 256 | psbt.addOutput({ 257 | address: receiveAddress, // Change address 258 | value: change, 259 | }); 260 | 261 | await signAndSend(keyPair, psbt); 262 | } 263 | 264 | childInscribe() 265 | 266 | export async function signAndSend( 267 | keypair: BTCSigner, 268 | psbt: Psbt, 269 | ) { 270 | const signer = tweakSigner(keypair, { network }) 271 | psbt.signInput(0, signer); 272 | psbt.signInput(1, keypair); 273 | psbt.finalizeAllInputs() 274 | const tx = psbt.extractTransaction(); 275 | 276 | console.log(tx.virtualSize()) 277 | console.log(tx.toHex()); 278 | 279 | // const txid = await broadcast(tx.toHex()); 280 | // console.log(`Success! Txid is ${txid}`); 281 | } 282 | 283 | export async function waitUntilUTXO(address: string) { 284 | return new Promise((resolve, reject) => { 285 | let intervalId: any; 286 | const checkForUtxo = async () => { 287 | try { 288 | const response: AxiosResponse = await blockstream.get( 289 | `/address/${address}/utxo` 290 | ); 291 | const data: IUTXO[] = response.data 292 | ? JSON.parse(response.data) 293 | : undefined; 294 | console.log(data); 295 | if (data.length > 0) { 296 | resolve(data); 297 | clearInterval(intervalId); 298 | } 299 | } catch (error) { 300 | reject(error); 301 | clearInterval(intervalId); 302 | } 303 | }; 304 | intervalId = setInterval(checkForUtxo, 4000); 305 | }); 306 | } 307 | 308 | export async function getTx(id: string): Promise { 309 | const response: AxiosResponse = await blockstream.get( 310 | `/tx/${id}/hex` 311 | ); 312 | return response.data; 313 | } 314 | 315 | const blockstream = new axios.Axios({ 316 | baseURL: `https://mempool.space/testnet/api`, 317 | // baseURL: `https://mempool.space/api`, 318 | }); 319 | 320 | export async function broadcast(txHex: string) { 321 | const response: AxiosResponse = await blockstream.post("/tx", txHex); 322 | return response.data; 323 | } 324 | 325 | function tapTweakHash(pubKey: Buffer, h: Buffer | undefined): Buffer { 326 | return crypto.taggedHash( 327 | "TapTweak", 328 | Buffer.concat(h ? [pubKey, h] : [pubKey]) 329 | ); 330 | } 331 | 332 | function toXOnly(pubkey: Buffer): Buffer { 333 | return pubkey.subarray(1, 33); 334 | } 335 | 336 | function tweakSigner(signer: any, opts: any = {}) { 337 | let privateKey = signer.privateKey; 338 | if (!privateKey) { 339 | throw new Error('Private key is required for tweaking signer!'); 340 | } 341 | if (signer.publicKey[0] === 3) { 342 | privateKey = ecc.privateNegate(privateKey); 343 | } 344 | const tweakedPrivateKey = ecc.privateAdd(privateKey, tapTweakHash(toXOnly(signer.publicKey), opts.tweakHash)); 345 | if (!tweakedPrivateKey) { 346 | throw new Error('Invalid tweaked private key!'); 347 | } 348 | return ECPair.fromPrivateKey(Buffer.from(tweakedPrivateKey), { 349 | network: opts.network, 350 | }); 351 | } 352 | 353 | interface IUTXO { 354 | txid: string; 355 | vout: number; 356 | status: { 357 | confirmed: boolean; 358 | block_height: number; 359 | block_hash: string; 360 | block_time: number; 361 | }; 362 | value: number; 363 | } 364 | 365 | -------------------------------------------------------------------------------- /Reinscription.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Transaction, 3 | script, 4 | Psbt, 5 | initEccLib, 6 | networks, 7 | Signer as BTCSigner, 8 | crypto, 9 | payments, 10 | opcodes, 11 | address as Address 12 | } from "bitcoinjs-lib"; 13 | 14 | import { Taptree } from "bitcoinjs-lib/src/types"; 15 | import { ECPairFactory, ECPairAPI } from "ecpair"; 16 | import ecc from "@bitcoinerlab/secp256k1"; 17 | import axios, { AxiosResponse } from "axios"; 18 | import networkConfig from "config/network.config"; 19 | import { WIFWallet } from 'utils/WIFWallet' 20 | import { SeedWallet } from "utils/SeedWallet"; 21 | import cbor from 'cbor' 22 | //test 23 | const network = networks.testnet; 24 | // const network = networks.bitcoin; 25 | 26 | initEccLib(ecc as any); 27 | const ECPair: ECPairAPI = ECPairFactory(ecc); 28 | 29 | // const seed: string = process.env.MNEMONIC as string; 30 | // const networkType: string = networkConfig.networkType; 31 | // const wallet = new SeedWallet({ networkType: networkType, seed: seed }); 32 | 33 | const privateKey: string = process.env.PRIVATE_KEY as string; 34 | const networkType: string = networkConfig.networkType; 35 | const wallet = new WIFWallet({ networkType: networkType, privateKey: privateKey }); 36 | 37 | const receiveAddress: string = "tb1ppx220ln489s5wqu8mqgezm7twwpj0avcvle3vclpdkpqvdg3mwqsvydajn"; 38 | const metadata = { 39 | 'type': 'Bitmap', 40 | 'description': 'Bitmap Community Parent Ordinal' 41 | } 42 | const metadataBuffer = cbor.encode(metadata); 43 | 44 | export function createparentInscriptionTapScript(): Array { 45 | 46 | const keyPair = wallet.ecPair; 47 | const parentOrdinalStacks: any = [ 48 | toXOnly(keyPair.publicKey), 49 | opcodes.OP_CHECKSIG, 50 | opcodes.OP_FALSE, 51 | opcodes.OP_IF, 52 | Buffer.from("ord", "utf8"), 53 | 1, 54 | 1, 55 | Buffer.concat([Buffer.from("text/plain;charset=utf-8", "utf8")]), 56 | 1, 57 | 5, 58 | metadataBuffer, 59 | opcodes.OP_0, 60 | Buffer.concat([Buffer.from("reinscription.bitmap", "utf8")]), 61 | opcodes.OP_ENDIF, 62 | ]; 63 | return parentOrdinalStacks; 64 | } 65 | 66 | async function reInscribe() { 67 | const keyPair = wallet.ecPair; 68 | const parentOrdinalStack = createparentInscriptionTapScript(); 69 | 70 | const ordinal_script = script.compile(parentOrdinalStack); 71 | 72 | const scriptTree: Taptree = { 73 | output: ordinal_script, 74 | }; 75 | 76 | const redeem = { 77 | output: ordinal_script, 78 | redeemVersion: 192, 79 | }; 80 | 81 | const ordinal_p2tr = payments.p2tr({ 82 | internalPubkey: toXOnly(keyPair.publicKey), 83 | network, 84 | scriptTree, 85 | redeem, 86 | }); 87 | 88 | const address = ordinal_p2tr.address ?? ""; 89 | console.log("Sending coin to address", address); 90 | 91 | const SendOrdinalsPsbt = new Psbt({ network }); 92 | 93 | const sendOrdinalPsbtFee = 30000; 94 | 95 | const SendUtxos: Array = [ 96 | { 97 | txid: '7402984dae838f6700b561f425aacac82b91bc5924fb853631af65f0431cc76a', 98 | vout: 0, 99 | value: 546 100 | }, 101 | { 102 | txid: 'ea4303aaa2c7939931a2ba129c9fc915d1905d441f2a74b6cd694c71665c7682', 103 | vout: 2, 104 | value: 129454 105 | } 106 | ] 107 | 108 | SendOrdinalsPsbt.addInput({ 109 | hash: SendUtxos[0].txid, 110 | index: SendUtxos[0].vout, 111 | witnessUtxo: { 112 | value: SendUtxos[0].value, 113 | script: wallet.output, 114 | }, 115 | tapInternalKey: toXOnly(keyPair.publicKey), 116 | }); 117 | 118 | SendOrdinalsPsbt.addInput({ 119 | hash: SendUtxos[1].txid, 120 | index: SendUtxos[1].vout, 121 | witnessUtxo: { 122 | value: SendUtxos[1].value, 123 | script: wallet.output, 124 | }, 125 | tapInternalKey: toXOnly(keyPair.publicKey), 126 | }); 127 | 128 | SendOrdinalsPsbt.addOutput({ 129 | address: address, //Destination Address 130 | value: 70000, 131 | }); 132 | 133 | const SendOrdinalUtxoChange = SendUtxos[0].value + SendUtxos[1].value - 70000 - sendOrdinalPsbtFee; 134 | 135 | SendOrdinalsPsbt.addOutput({ 136 | address: receiveAddress, //Destination Address 137 | value: SendOrdinalUtxoChange, 138 | }); 139 | 140 | await SendUtxoSignAndSend(keyPair, SendOrdinalsPsbt); 141 | 142 | const utxos = await waitUntilUTXO(address as string); 143 | const psbt = new Psbt({ network }); 144 | 145 | const transaction_fee = 30000; 146 | 147 | psbt.addInput({ 148 | hash: utxos[0].txid, 149 | index: utxos[0].vout, 150 | tapInternalKey: toXOnly(keyPair.publicKey), 151 | witnessUtxo: { value: utxos[0].value, script: ordinal_p2tr.output! }, 152 | tapLeafScript: [ 153 | { 154 | leafVersion: redeem.redeemVersion, 155 | script: redeem.output, 156 | controlBlock: ordinal_p2tr.witness![ordinal_p2tr.witness!.length - 1], 157 | }, 158 | ], 159 | }); 160 | const change = utxos[0].value - 546 - transaction_fee; 161 | 162 | psbt.addOutput({ 163 | address: receiveAddress, //Destination Address 164 | value: 546, 165 | }); 166 | 167 | psbt.addOutput({ 168 | address: receiveAddress, // Change address 169 | value: change, 170 | }); 171 | 172 | await signAndSend(keyPair, psbt); 173 | } 174 | 175 | reInscribe() 176 | 177 | export async function signAndSend( 178 | keypair: BTCSigner, 179 | psbt: Psbt, 180 | ) { 181 | psbt.signInput(0, keypair); 182 | psbt.finalizeAllInputs() 183 | const tx = psbt.extractTransaction(); 184 | 185 | console.log(tx.virtualSize()) 186 | console.log(tx.toHex()) 187 | 188 | // const txid = await broadcast(tx.toHex()); 189 | // console.log(`Success! Txid is ${txid}`); 190 | } 191 | 192 | 193 | export async function SendUtxoSignAndSend( 194 | keypair: BTCSigner, 195 | psbt: Psbt, 196 | ) { 197 | const signer = tweakSigner(keypair, { network }) 198 | psbt.signInput(0, signer); 199 | psbt.signInput(1, signer); 200 | psbt.finalizeAllInputs() 201 | const tx = psbt.extractTransaction(); 202 | 203 | console.log(tx.virtualSize()) 204 | } 205 | 206 | export async function waitUntilUTXO(address: string) { 207 | return new Promise((resolve, reject) => { 208 | let intervalId: any; 209 | const checkForUtxo = async () => { 210 | try { 211 | const response: AxiosResponse = await blockstream.get( 212 | `/address/${address}/utxo` 213 | ); 214 | const data: IUTXO[] = response.data 215 | ? JSON.parse(response.data) 216 | : undefined; 217 | console.log(data); 218 | if (data.length > 0) { 219 | resolve(data); 220 | clearInterval(intervalId); 221 | } 222 | } catch (error) { 223 | reject(error); 224 | clearInterval(intervalId); 225 | } 226 | }; 227 | intervalId = setInterval(checkForUtxo, 4000); 228 | }); 229 | } 230 | export async function getTx(id: string): Promise { 231 | const response: AxiosResponse = await blockstream.get( 232 | `/tx/${id}/hex` 233 | ); 234 | return response.data; 235 | } 236 | const blockstream = new axios.Axios({ 237 | baseURL: `https://mempool.space/testnet/api`, 238 | // baseURL: `https://mempool.space/api`, 239 | }); 240 | export async function broadcast(txHex: string) { 241 | const response: AxiosResponse = await blockstream.post("/tx", txHex); 242 | return response.data; 243 | } 244 | function tapTweakHash(pubKey: Buffer, h: Buffer | undefined): Buffer { 245 | return crypto.taggedHash( 246 | "TapTweak", 247 | Buffer.concat(h ? [pubKey, h] : [pubKey]) 248 | ); 249 | } 250 | function toXOnly(pubkey: Buffer): Buffer { 251 | return pubkey.subarray(1, 33); 252 | } 253 | function tweakSigner(signer: any, opts: any = {}) { 254 | let privateKey = signer.privateKey; 255 | if (!privateKey) { 256 | throw new Error('Private key is required for tweaking signer!'); 257 | } 258 | if (signer.publicKey[0] === 3) { 259 | privateKey = ecc.privateNegate(privateKey); 260 | } 261 | const tweakedPrivateKey = ecc.privateAdd(privateKey, tapTweakHash(toXOnly(signer.publicKey), opts.tweakHash)); 262 | if (!tweakedPrivateKey) { 263 | throw new Error('Invalid tweaked private key!'); 264 | } 265 | return ECPair.fromPrivateKey(Buffer.from(tweakedPrivateKey), { 266 | network: opts.network, 267 | }); 268 | } 269 | interface IUTXO { 270 | txid: string; 271 | vout: number; 272 | status: { 273 | confirmed: boolean; 274 | block_height: number; 275 | block_hash: string; 276 | block_time: number; 277 | }; 278 | value: number; 279 | } -------------------------------------------------------------------------------- /Runestone.ts: -------------------------------------------------------------------------------- 1 | import { Runestone, SpacedRune } from "runestone-js"; 2 | 3 | function exampleDecodeFromTxBuffer() { 4 | // https://mempool.space/testnet/tx/0201fbf76be120e0245f95011e9e92bf588d9d5888b0c1eb62823312a3a86a87 5 | const txBuffer = Buffer.from( 6 | // this is the pure output buffer 7 | "0a00d3e99d01a303e80700", 8 | "hex", 9 | ); 10 | console.log({ txBuffer }); 11 | 12 | const runestone = Runestone.dechiper(txBuffer); 13 | const buffer = runestone.enchiper(); 14 | console.log({ buffer }); 15 | // output: 16 | // { 17 | // buffer: Buffer(37) [ 2, 3, 4, 213, 181, 157, 129, 207, 163, 160, 245, 32, 1, 38, 3, 160, 2, 6, 184, 156, 222, 147, 137, 142, 202, 1, 5, 88, 10, 232, 7, 8, 136, 164, 1, 22, 1 ], 18 | // } 19 | console.log(JSON.stringify(runestone)); 20 | // output: 21 | // {"edicts":[],"etching":{"divisibility":"38","premine":"888888888888888","rune":"XVERSEFORTEST","spacers":"288","symbol":"X","terms":{"amount":"1000","cap":"21000","height":{},"offset":{}}},"pointer":"1"} 22 | 23 | const spacedRune = new SpacedRune( 24 | runestone.etching?.rune!, 25 | runestone.etching?.spacers!, 26 | ); 27 | console.log(spacedRune.toString()); 28 | // output: XVERSE•FOR•TEST 29 | } 30 | 31 | exampleDecodeFromTxBuffer() -------------------------------------------------------------------------------- /Text_Child_Inscription.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Transaction, 3 | script, 4 | Psbt, 5 | initEccLib, 6 | networks, 7 | Signer as BTCSigner, 8 | crypto, 9 | payments, 10 | opcodes, 11 | address as Address 12 | } from "bitcoinjs-lib"; 13 | import { Taptree } from "bitcoinjs-lib/src/types"; 14 | import { ECPairFactory, ECPairAPI } from "ecpair"; 15 | import ecc from "@bitcoinerlab/secp256k1"; 16 | import axios, { AxiosResponse } from "axios"; 17 | import networkConfig from "config/network.config"; 18 | import { WIFWallet } from 'utils/WIFWallet' 19 | import { SeedWallet } from "utils/SeedWallet"; 20 | import cbor from 'cbor'; 21 | 22 | //test 23 | const network = networks.testnet; 24 | // const network = networks.bitcoin; 25 | 26 | initEccLib(ecc as any); 27 | const ECPair: ECPairAPI = ECPairFactory(ecc); 28 | 29 | // const seed: string = process.env.MNEMONIC as string; 30 | // const networkType: string = networkConfig.networkType; 31 | // const wallet = new SeedWallet({ networkType: networkType, seed: seed }); 32 | 33 | const privateKey: string = process.env.PRIVATE_KEY as string; 34 | const networkType: string = networkConfig.networkType; 35 | const wallet = new WIFWallet({ networkType: networkType, privateKey: privateKey }); 36 | 37 | export const contentBuffer = (content: string) => { 38 | return Buffer.from(content, 'utf8') 39 | } 40 | 41 | // inputs 42 | const txhash: string = '31b1ad7898cd5fad903598a9c389e4109bb086688ac481e1bfe8adc6e7a3651e'; 43 | const receiveAddress: string = 'tb1ppx220ln489s5wqu8mqgezm7twwpj0avcvle3vclpdkpqvdg3mwqsvydajn'; 44 | const memeType: string = 'text/plain;charset=utf-8'; 45 | const metadata = { 46 | 'type': 'Bitmap', 47 | 'description': 'Bitmap Community Parent Ordinal' 48 | } 49 | const metaProtocol: Buffer = Buffer.concat([Buffer.from("parcel.bitmap", "utf8")]); 50 | const fee = 60000; 51 | const contentBufferData: Buffer = contentBuffer('0.364972.bitmap') 52 | 53 | 54 | const parentInscriptionTXID: string = 'd9b95d549219eebcd1be0360f41c7164c4ad040b716475630154f08263ab2fdf'; 55 | const revealtxIDBuffer = Buffer.from(parentInscriptionTXID, 'hex'); 56 | const inscriptionBuffer = revealtxIDBuffer.reverse(); 57 | const pointer1: number = 546 * 1; 58 | const pointer2: number = 546 * 2; 59 | const pointer3: number = 546 * 3; 60 | const pointerBuffer1: Buffer = Buffer.from(pointer1.toString(16).padStart(4, '0'), 'hex').reverse(); 61 | const pointerBuffer2: Buffer = Buffer.from(pointer2.toString(16).padStart(4, '0'), 'hex').reverse(); 62 | const pointerBuffer3: Buffer = Buffer.from(pointer3.toString(16).padStart(4, '0'), 'hex').reverse(); 63 | const metadataBuffer = cbor.encode(metadata); 64 | 65 | const splitBuffer = (buffer: Buffer, chunkSize: number) => { 66 | let chunks = []; 67 | for (let i = 0; i < buffer.length; i += chunkSize) { 68 | const chunk = buffer.subarray(i, i + chunkSize); 69 | chunks.push(chunk); 70 | } 71 | return chunks; 72 | }; 73 | const contentBufferArray: Array = splitBuffer(contentBufferData, 400) 74 | 75 | export function createChildInscriptionTapScript(): Array { 76 | 77 | const keyPair = wallet.ecPair; 78 | let childOrdinalStacks: any = [ 79 | toXOnly(keyPair.publicKey), 80 | opcodes.OP_CHECKSIG, 81 | opcodes.OP_FALSE, 82 | opcodes.OP_IF, 83 | Buffer.from("ord", "utf8"), 84 | 1, 85 | 1, 86 | Buffer.concat([Buffer.from(memeType, "utf8")]), 87 | 1, 88 | 2, 89 | pointerBuffer1, 90 | 1, 91 | 3, 92 | inscriptionBuffer, 93 | 1, 94 | 5, 95 | metadataBuffer, 96 | 1, 97 | 7, 98 | metaProtocol, 99 | opcodes.OP_0 100 | ]; 101 | contentBufferArray.forEach((item: Buffer) => { 102 | childOrdinalStacks.push(item) 103 | }) 104 | childOrdinalStacks.push(opcodes.OP_ENDIF) 105 | 106 | console.log(childOrdinalStacks) 107 | 108 | return childOrdinalStacks; 109 | } 110 | 111 | async function childInscribe() { 112 | const keyPair = wallet.ecPair; 113 | const childOrdinalStack = createChildInscriptionTapScript(); 114 | 115 | const ordinal_script = script.compile(childOrdinalStack); 116 | 117 | const scriptTree: Taptree = { 118 | output: ordinal_script, 119 | }; 120 | 121 | const redeem = { 122 | output: ordinal_script, 123 | redeemVersion: 192, 124 | }; 125 | 126 | const ordinal_p2tr = payments.p2tr({ 127 | internalPubkey: toXOnly(keyPair.publicKey), 128 | network, 129 | scriptTree, 130 | redeem, 131 | }); 132 | 133 | const address = ordinal_p2tr.address ?? ""; 134 | console.log("send coin to address", address); 135 | 136 | const utxos = await waitUntilUTXO(address as string); 137 | 138 | const psbt = new Psbt({ network }); 139 | const parentInscriptionUTXO = { 140 | txid: txhash, 141 | vout: 0, 142 | value: 546 143 | } 144 | psbt.addInput({ 145 | hash: parentInscriptionUTXO.txid, 146 | index: parentInscriptionUTXO.vout, 147 | witnessUtxo: { 148 | value: parentInscriptionUTXO.value, 149 | script: wallet.output, 150 | }, 151 | tapInternalKey: toXOnly(keyPair.publicKey), 152 | }); 153 | 154 | psbt.addInput({ 155 | hash: utxos[0].txid, 156 | index: utxos[0].vout, 157 | tapInternalKey: toXOnly(keyPair.publicKey), 158 | witnessUtxo: { value: utxos[0].value, script: ordinal_p2tr.output! }, 159 | tapLeafScript: [ 160 | { 161 | leafVersion: redeem.redeemVersion, 162 | script: redeem.output, 163 | controlBlock: ordinal_p2tr.witness![ordinal_p2tr.witness!.length - 1], 164 | }, 165 | ], 166 | }); 167 | 168 | const change = utxos[0].value - 546 * 2 - fee; 169 | 170 | psbt.addOutput({ 171 | address: receiveAddress, //Destination Address 172 | value: 546, 173 | }); 174 | 175 | psbt.addOutput({ 176 | address: receiveAddress, //Destination Address 177 | value: 546, 178 | }); 179 | 180 | psbt.addOutput({ 181 | address: receiveAddress, // Change address 182 | value: change, 183 | }); 184 | 185 | await signAndSend(keyPair, psbt); 186 | } 187 | 188 | childInscribe() 189 | 190 | export async function signAndSend( 191 | keypair: BTCSigner, 192 | psbt: Psbt, 193 | ) { 194 | const signer = tweakSigner(keypair, { network }) 195 | psbt.signInput(0, signer); 196 | psbt.signInput(1, keypair); 197 | psbt.finalizeAllInputs() 198 | const tx = psbt.extractTransaction(); 199 | console.log(tx.virtualSize()) 200 | console.log(tx.toHex()); 201 | 202 | // const txid = await broadcast(tx.toHex()); 203 | // console.log(`Success! Txid is ${txid}`); 204 | } 205 | 206 | export async function waitUntilUTXO(address: string) { 207 | return new Promise((resolve, reject) => { 208 | let intervalId: any; 209 | const checkForUtxo = async () => { 210 | try { 211 | const response: AxiosResponse = await blockstream.get( 212 | `/address/${address}/utxo` 213 | ); 214 | const data: IUTXO[] = response.data 215 | ? JSON.parse(response.data) 216 | : undefined; 217 | console.log(data); 218 | if (data.length > 0) { 219 | resolve(data); 220 | clearInterval(intervalId); 221 | } 222 | } catch (error) { 223 | reject(error); 224 | clearInterval(intervalId); 225 | } 226 | }; 227 | intervalId = setInterval(checkForUtxo, 4000); 228 | }); 229 | } 230 | 231 | export async function getTx(id: string): Promise { 232 | const response: AxiosResponse = await blockstream.get( 233 | `/tx/${id}/hex` 234 | ); 235 | return response.data; 236 | } 237 | 238 | const blockstream = new axios.Axios({ 239 | baseURL: `https://mempool.space/testnet/api`, 240 | // baseURL: `https://mempool.space/api`, 241 | }); 242 | 243 | export async function broadcast(txHex: string) { 244 | const response: AxiosResponse = await blockstream.post("/tx", txHex); 245 | return response.data; 246 | } 247 | 248 | function tapTweakHash(pubKey: Buffer, h: Buffer | undefined): Buffer { 249 | return crypto.taggedHash( 250 | "TapTweak", 251 | Buffer.concat(h ? [pubKey, h] : [pubKey]) 252 | ); 253 | } 254 | 255 | function toXOnly(pubkey: Buffer): Buffer { 256 | return pubkey.subarray(1, 33); 257 | } 258 | 259 | function tweakSigner(signer: any, opts: any = {}) { 260 | let privateKey = signer.privateKey; 261 | if (!privateKey) { 262 | throw new Error('Private key is required for tweaking signer!'); 263 | } 264 | if (signer.publicKey[0] === 3) { 265 | privateKey = ecc.privateNegate(privateKey); 266 | } 267 | const tweakedPrivateKey = ecc.privateAdd(privateKey, tapTweakHash(toXOnly(signer.publicKey), opts.tweakHash)); 268 | if (!tweakedPrivateKey) { 269 | throw new Error('Invalid tweaked private key!'); 270 | } 271 | return ECPair.fromPrivateKey(Buffer.from(tweakedPrivateKey), { 272 | network: opts.network, 273 | }); 274 | } 275 | 276 | interface IUTXO { 277 | txid: string; 278 | vout: number; 279 | status: { 280 | confirmed: boolean; 281 | block_height: number; 282 | block_hash: string; 283 | block_time: number; 284 | }; 285 | value: number; 286 | } 287 | 288 | -------------------------------------------------------------------------------- /UTXO_merge.ts: -------------------------------------------------------------------------------- 1 | import networkConfig from "config/network.config"; 2 | import { getUtxos, pushBTCpmt } from "./utils/mempool"; 3 | import * as Bitcoin from "bitcoinjs-lib"; 4 | import * as ecc from "tiny-secp256k1"; 5 | import { SeedWallet } from "utils/SeedWallet"; 6 | // import { WIFWallet } from 'utils/WIFWallet' 7 | import dotenv from "dotenv"; 8 | import { redeemMergeUTXOPsbt, mergeUTXOPsbt } from "controller/utxo.merge.controller"; 9 | 10 | const TESTNET_FEERATE = 20; 11 | const MERGE_COUNT = 5; 12 | 13 | dotenv.config(); 14 | Bitcoin.initEccLib(ecc); 15 | 16 | const networkType: string = networkConfig.networkType; 17 | const seed: string = process.env.MNEMONIC as string; 18 | // const privateKey: string = process.env.PRIVATE_KEY as string; 19 | 20 | 21 | const mergeUTXO = async () => { 22 | const wallet = new SeedWallet({ networkType: networkType, seed: seed }); 23 | // const wallet = new WIFWallet({ networkType: networkType, privateKey: privateKey }); 24 | 25 | const utxos = await getUtxos(wallet.address, networkType); 26 | if (utxos.length < MERGE_COUNT) throw new Error("No btcs"); 27 | 28 | let redeemPsbt: Bitcoin.Psbt = redeemMergeUTXOPsbt(wallet, utxos, networkType, MERGE_COUNT); 29 | redeemPsbt = wallet.signPsbt(redeemPsbt, wallet.ecPair) 30 | let redeemFee = redeemPsbt.extractTransaction().virtualSize() * TESTNET_FEERATE; 31 | 32 | let psbt = mergeUTXOPsbt(wallet, utxos, networkType, MERGE_COUNT, redeemFee); 33 | let signedPsbt = wallet.signPsbt(psbt, wallet.ecPair) 34 | 35 | const txHex = signedPsbt.extractTransaction().toHex(); 36 | const txId = await pushBTCpmt(txHex, networkType); 37 | console.log(`Merge_UTXO_TxId=======> ${txId}`) 38 | } 39 | 40 | mergeUTXO(); -------------------------------------------------------------------------------- /UTXO_send.ts: -------------------------------------------------------------------------------- 1 | import networkConfig from "config/network.config"; 2 | import { getUtxos, pushBTCpmt } from "./utils/mempool"; 3 | import * as Bitcoin from "bitcoinjs-lib"; 4 | import * as ecc from "tiny-secp256k1"; 5 | import dotenv from "dotenv"; 6 | import { redeemSendUTXOPsbt, sendUTXOPsbt} from "controller/utxo.send.controller"; 7 | import { SeedWallet } from "utils/SeedWallet"; 8 | // import { WIFWallet } from 'utils/WIFWallet' 9 | 10 | const TESTNET_FEERATE = 20; 11 | const SEND_UTXO_LIMIT = 10000; 12 | const RECEIVEADDRESS = 'tb1pr62qc83slv3zy7mjaygeq2t2033hvslgljnr6lxylephys646ehqptvk8a'; 13 | 14 | dotenv.config(); 15 | Bitcoin.initEccLib(ecc); 16 | 17 | const networkType: string = networkConfig.networkType; 18 | const seed: string = process.env.MNEMONIC as string; 19 | // const privateKey: string = process.env.PRIVATE_KEY as string; 20 | 21 | 22 | const sendUTXO = async () => { 23 | const wallet = new SeedWallet({ networkType: networkType, seed: seed }); 24 | // const wallet = new WIFWallet({ networkType: networkType, privateKey: privateKey }); 25 | 26 | const utxos = await getUtxos(wallet.address, networkType); 27 | const utxo = utxos.find((utxo) => utxo.value > SEND_UTXO_LIMIT); 28 | if (utxo === undefined) throw new Error("No btcs"); 29 | 30 | let redeemPsbt: Bitcoin.Psbt = redeemSendUTXOPsbt(wallet, utxo, networkType); 31 | redeemPsbt = wallet.signPsbt(redeemPsbt, wallet.ecPair) 32 | let redeemFee = redeemPsbt.extractTransaction().virtualSize() * TESTNET_FEERATE; 33 | 34 | let psbt = sendUTXOPsbt(wallet, utxo, networkType, redeemFee, RECEIVEADDRESS); 35 | let signedPsbt = wallet.signPsbt(psbt, wallet.ecPair) 36 | 37 | const txHex = signedPsbt.extractTransaction().toHex(); 38 | 39 | const txId = await pushBTCpmt(txHex, networkType); 40 | console.log(`Send_UTXO_TxId=======> ${txId}`) 41 | } 42 | 43 | sendUTXO(); -------------------------------------------------------------------------------- /UTXO_split.ts: -------------------------------------------------------------------------------- 1 | import networkConfig from "config/network.config"; 2 | import { getUtxos, pushBTCpmt } from "./utils/mempool"; 3 | import * as Bitcoin from "bitcoinjs-lib"; 4 | import * as ecc from "tiny-secp256k1"; 5 | import dotenv from "dotenv"; 6 | import { redeemSplitUTXOPsbt, splitUTXOPsbt} from "controller/utxo.split.controller"; 7 | import { SeedWallet } from "utils/SeedWallet"; 8 | // import { WIFWallet } from 'utils/WIFWallet' 9 | 10 | const TESTNET_FEERATE = 20; 11 | const SPLIT_UTXO_LIMIT = 30000; 12 | const SPLIT_COUNT = 1; 13 | 14 | dotenv.config(); 15 | Bitcoin.initEccLib(ecc); 16 | 17 | const networkType: string = networkConfig.networkType; 18 | const seed: string = process.env.MNEMONIC as string; 19 | // const privateKey: string = process.env.PRIVATE_KEY as string; 20 | 21 | 22 | const splitUTXO = async () => { 23 | const wallet = new SeedWallet({ networkType: networkType, seed: seed }); 24 | // const wallet = new WIFWallet({ networkType: networkType, privateKey: privateKey }); 25 | 26 | const utxos = await getUtxos(wallet.address, networkType); 27 | const utxo = utxos.find((utxo) => utxo.value > SPLIT_UTXO_LIMIT); 28 | if (utxo === undefined) throw new Error("No btcs"); 29 | 30 | let redeemPsbt: Bitcoin.Psbt = redeemSplitUTXOPsbt(wallet, utxo, networkType, SPLIT_COUNT); 31 | redeemPsbt = wallet.signPsbt(redeemPsbt, wallet.ecPair) 32 | let redeemFee = redeemPsbt.extractTransaction().virtualSize() * TESTNET_FEERATE; 33 | 34 | let psbt = splitUTXOPsbt(wallet, utxo, networkType, SPLIT_COUNT, redeemFee); 35 | let signedPsbt = wallet.signPsbt(psbt, wallet.ecPair) 36 | 37 | const txHex = signedPsbt.extractTransaction().toHex(); 38 | 39 | const txId = await pushBTCpmt(txHex, networkType); 40 | console.log(`Split_UTXO_TxId=======> ${txId}`) 41 | } 42 | 43 | splitUTXO(); -------------------------------------------------------------------------------- /config/network.config.ts: -------------------------------------------------------------------------------- 1 | const networkConfig = { 2 | networkType: "testnet", 3 | // networkType: "mainnet", 4 | }; 5 | 6 | export default networkConfig; 7 | -------------------------------------------------------------------------------- /controller/utxo.merge.controller.ts: -------------------------------------------------------------------------------- 1 | import * as Bitcoin from "bitcoinjs-lib"; 2 | import * as ecc from "tiny-secp256k1"; 3 | Bitcoin.initEccLib(ecc); 4 | 5 | interface IUtxo { 6 | txid: string; 7 | vout: number; 8 | value: number; 9 | } 10 | 11 | export const redeemMergeUTXOPsbt = (wallet: any, utxos: IUtxo[], networkType: string, mergeCount: number): Bitcoin.Psbt => { 12 | let value = 0; 13 | 14 | const psbt = new Bitcoin.Psbt({ 15 | network: networkType == "testnet" ? Bitcoin.networks.testnet : Bitcoin.networks.bitcoin 16 | }); 17 | for (let i = 0; i < mergeCount; i++) { 18 | psbt.addInput({ 19 | hash: utxos[i].txid, 20 | index: utxos[i].vout, 21 | witnessUtxo: { 22 | value: utxos[i].value, 23 | script: wallet.output, 24 | }, 25 | tapInternalKey: Buffer.from(wallet.publicKey, "hex").subarray(1, 33), 26 | }); 27 | value += utxos[i].value; 28 | } 29 | 30 | psbt.addOutput({ 31 | address: wallet.address, 32 | value: value - 1000, 33 | }); 34 | return psbt; 35 | } 36 | 37 | export const mergeUTXOPsbt = (wallet: any, utxos: IUtxo[], networkType: string, mergeCount: number, fee: number): Bitcoin.Psbt => { 38 | let value = 0; 39 | 40 | const psbt = new Bitcoin.Psbt({ 41 | network: networkType == "testnet" ? Bitcoin.networks.testnet : Bitcoin.networks.bitcoin 42 | }); 43 | for (let i = 0; i < mergeCount; i++) { 44 | psbt.addInput({ 45 | hash: utxos[i].txid, 46 | index: utxos[i].vout, 47 | witnessUtxo: { 48 | value: utxos[i].value, 49 | script: wallet.output, 50 | }, 51 | tapInternalKey: Buffer.from(wallet.publicKey, "hex").subarray(1, 33), 52 | }); 53 | value += utxos[i].value; 54 | } 55 | 56 | psbt.addOutput({ 57 | address: wallet.address, 58 | value: value - fee, 59 | }); 60 | 61 | if (value < fee) throw new Error("No enough Fee"); 62 | return psbt; 63 | } 64 | -------------------------------------------------------------------------------- /controller/utxo.send.controller.ts: -------------------------------------------------------------------------------- 1 | import * as Bitcoin from "bitcoinjs-lib"; 2 | import * as ecc from "tiny-secp256k1"; 3 | Bitcoin.initEccLib(ecc); 4 | 5 | 6 | interface IUtxo { 7 | txid: string; 8 | vout: number; 9 | value: number; 10 | } 11 | 12 | export const redeemSendUTXOPsbt = (wallet: any, utxo: IUtxo, networkType: string): Bitcoin.Psbt => { 13 | const psbt = new Bitcoin.Psbt({ 14 | network: networkType == "testnet" ? Bitcoin.networks.testnet : Bitcoin.networks.bitcoin 15 | }); 16 | 17 | 18 | psbt.addInput({ 19 | hash: utxo.txid, 20 | index: utxo.vout, 21 | witnessUtxo: { 22 | value: utxo.value, 23 | script: wallet.output, 24 | }, 25 | tapInternalKey: Buffer.from(wallet.publicKey, "hex").subarray(1, 33), 26 | }); 27 | 28 | psbt.addOutput({ 29 | address: wallet.address, 30 | value: utxo.value - 1000, 31 | }); 32 | 33 | return psbt; 34 | } 35 | 36 | export const sendUTXOPsbt = (wallet: any, utxo: IUtxo, networkType: string, fee: number, address: string): Bitcoin.Psbt => { 37 | const psbt = new Bitcoin.Psbt({ 38 | network: networkType == "testnet" ? Bitcoin.networks.testnet : Bitcoin.networks.bitcoin 39 | }); 40 | 41 | psbt.addInput({ 42 | hash: utxo.txid, 43 | index: utxo.vout, 44 | witnessUtxo: { 45 | value: utxo.value, 46 | script: wallet.output, 47 | }, 48 | tapInternalKey: Buffer.from(wallet.publicKey, "hex").subarray(1, 33), 49 | }); 50 | 51 | psbt.addOutput({ 52 | address: address, 53 | value: utxo.value - fee, 54 | }); 55 | 56 | return psbt; 57 | } 58 | 59 | -------------------------------------------------------------------------------- /controller/utxo.split.controller.ts: -------------------------------------------------------------------------------- 1 | import * as Bitcoin from "bitcoinjs-lib"; 2 | import * as ecc from "tiny-secp256k1"; 3 | Bitcoin.initEccLib(ecc); 4 | 5 | const UTXO_OUTPUT = 546; 6 | 7 | interface IUtxo { 8 | txid: string; 9 | vout: number; 10 | value: number; 11 | } 12 | 13 | export const redeemSplitUTXOPsbt = (wallet: any, utxo: IUtxo, networkType: string, splitCount: number): Bitcoin.Psbt => { 14 | const psbt = new Bitcoin.Psbt({ 15 | network: networkType == "testnet" ? Bitcoin.networks.testnet : Bitcoin.networks.bitcoin 16 | }); 17 | psbt.addInput({ 18 | hash: utxo.txid, 19 | index: utxo.vout, 20 | witnessUtxo: { 21 | value: utxo.value, 22 | script: wallet.output, 23 | }, 24 | tapInternalKey: Buffer.from(wallet.publicKey, "hex").subarray(1, 33), 25 | }); 26 | for (let i = 0; i < splitCount; i++) { 27 | psbt.addOutput({ 28 | address: wallet.address, 29 | value: UTXO_OUTPUT, 30 | }); 31 | } 32 | 33 | psbt.addOutput({ 34 | address: wallet.address, 35 | value: utxo.value - UTXO_OUTPUT * splitCount - 1000, 36 | }); 37 | 38 | return psbt; 39 | } 40 | 41 | export const splitUTXOPsbt = (wallet: any, utxo: IUtxo, networkType: string, splitCount: number, fee: number): Bitcoin.Psbt => { 42 | const psbt = new Bitcoin.Psbt({ 43 | network: networkType == "testnet" ? Bitcoin.networks.testnet : Bitcoin.networks.bitcoin 44 | }); 45 | psbt.addInput({ 46 | hash: utxo.txid, 47 | index: utxo.vout, 48 | witnessUtxo: { 49 | value: utxo.value, 50 | script: wallet.output, 51 | }, 52 | tapInternalKey: Buffer.from(wallet.publicKey, "hex").subarray(1, 33), 53 | }); 54 | for (let i = 0; i < splitCount; i++) { 55 | psbt.addOutput({ 56 | address: wallet.address, 57 | value: UTXO_OUTPUT, 58 | }); 59 | } 60 | 61 | psbt.addOutput({ 62 | address: wallet.address, 63 | value: utxo.value - UTXO_OUTPUT * splitCount - fee, 64 | }); 65 | 66 | return psbt; 67 | } 68 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "utxo-management", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "utxo-management", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@bitcoinerlab/secp256k1": "^1.1.1", 13 | "@ordjs/runestone": "^1.6.0", 14 | "axios": "^1.6.5", 15 | "bip32": "^4.0.0", 16 | "bip39": "^3.1.0", 17 | "bitcoinjs-lib": "^6.1.5", 18 | "cbor": "^9.0.2", 19 | "dotenv": "^16.3.2", 20 | "ecpair": "^2.1.0", 21 | "runelib": "^1.0.6", 22 | "runestone-js": "^0.3.0", 23 | "tiny-secp256k1": "^2.2.3" 24 | }, 25 | "devDependencies": { 26 | "@types/node": "^20.11.5", 27 | "ts-node": "^10.9.2", 28 | "typescript": "^5.3.3" 29 | } 30 | }, 31 | "node_modules/@bitcoinerlab/secp256k1": { 32 | "version": "1.1.1", 33 | "resolved": "https://registry.npmjs.org/@bitcoinerlab/secp256k1/-/secp256k1-1.1.1.tgz", 34 | "integrity": "sha512-uhjW51WfVLpnHN7+G0saDcM/k9IqcyTbZ+bDgLF3AX8V/a3KXSE9vn7UPBrcdU72tp0J4YPR7BHp2m7MLAZ/1Q==", 35 | "dependencies": { 36 | "@noble/hashes": "^1.1.5", 37 | "@noble/secp256k1": "^1.7.1" 38 | } 39 | }, 40 | "node_modules/@cspotcode/source-map-support": { 41 | "version": "0.8.1", 42 | "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", 43 | "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", 44 | "dev": true, 45 | "dependencies": { 46 | "@jridgewell/trace-mapping": "0.3.9" 47 | }, 48 | "engines": { 49 | "node": ">=12" 50 | } 51 | }, 52 | "node_modules/@jridgewell/resolve-uri": { 53 | "version": "3.1.1", 54 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", 55 | "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", 56 | "dev": true, 57 | "engines": { 58 | "node": ">=6.0.0" 59 | } 60 | }, 61 | "node_modules/@jridgewell/sourcemap-codec": { 62 | "version": "1.4.15", 63 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", 64 | "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==", 65 | "dev": true 66 | }, 67 | "node_modules/@jridgewell/trace-mapping": { 68 | "version": "0.3.9", 69 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", 70 | "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", 71 | "dev": true, 72 | "dependencies": { 73 | "@jridgewell/resolve-uri": "^3.0.3", 74 | "@jridgewell/sourcemap-codec": "^1.4.10" 75 | } 76 | }, 77 | "node_modules/@noble/hashes": { 78 | "version": "1.3.3", 79 | "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz", 80 | "integrity": "sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==", 81 | "engines": { 82 | "node": ">= 16" 83 | }, 84 | "funding": { 85 | "url": "https://paulmillr.com/funding/" 86 | } 87 | }, 88 | "node_modules/@noble/secp256k1": { 89 | "version": "1.7.1", 90 | "resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-1.7.1.tgz", 91 | "integrity": "sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw==", 92 | "funding": [ 93 | { 94 | "type": "individual", 95 | "url": "https://paulmillr.com/funding/" 96 | } 97 | ] 98 | }, 99 | "node_modules/@ordjs/runestone": { 100 | "version": "1.6.0", 101 | "resolved": "https://registry.npmjs.org/@ordjs/runestone/-/runestone-1.6.0.tgz", 102 | "integrity": "sha512-hAdQIioQ7iwfGOOfR6GigZRtbVjXBGFrmyAhyVv3noaaVoYAWjzDOAIn5Ca7xC2R/pMbfJB8t4n5hMhHObRb7A==" 103 | }, 104 | "node_modules/@scure/base": { 105 | "version": "1.1.5", 106 | "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.5.tgz", 107 | "integrity": "sha512-Brj9FiG2W1MRQSTB212YVPRrcbjkv48FoZi/u4l/zds/ieRrqsh7aUf6CLwkAq61oKXr/ZlTzlY66gLIj3TFTQ==", 108 | "funding": { 109 | "url": "https://paulmillr.com/funding/" 110 | } 111 | }, 112 | "node_modules/@tsconfig/node10": { 113 | "version": "1.0.9", 114 | "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", 115 | "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", 116 | "dev": true 117 | }, 118 | "node_modules/@tsconfig/node12": { 119 | "version": "1.0.11", 120 | "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", 121 | "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", 122 | "dev": true 123 | }, 124 | "node_modules/@tsconfig/node14": { 125 | "version": "1.0.3", 126 | "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", 127 | "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", 128 | "dev": true 129 | }, 130 | "node_modules/@tsconfig/node16": { 131 | "version": "1.0.4", 132 | "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", 133 | "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", 134 | "dev": true 135 | }, 136 | "node_modules/@types/node": { 137 | "version": "20.11.5", 138 | "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.5.tgz", 139 | "integrity": "sha512-g557vgQjUUfN76MZAN/dt1z3dzcUsimuysco0KeluHgrPdJXkP/XdAURgyO2W9fZWHRtRBiVKzKn8vyOAwlG+w==", 140 | "dev": true, 141 | "dependencies": { 142 | "undici-types": "~5.26.4" 143 | } 144 | }, 145 | "node_modules/acorn": { 146 | "version": "8.11.3", 147 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", 148 | "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", 149 | "dev": true, 150 | "bin": { 151 | "acorn": "bin/acorn" 152 | }, 153 | "engines": { 154 | "node": ">=0.4.0" 155 | } 156 | }, 157 | "node_modules/acorn-walk": { 158 | "version": "8.3.2", 159 | "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", 160 | "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", 161 | "dev": true, 162 | "engines": { 163 | "node": ">=0.4.0" 164 | } 165 | }, 166 | "node_modules/arg": { 167 | "version": "4.1.3", 168 | "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", 169 | "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", 170 | "dev": true 171 | }, 172 | "node_modules/asynckit": { 173 | "version": "0.4.0", 174 | "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", 175 | "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" 176 | }, 177 | "node_modules/axios": { 178 | "version": "1.6.5", 179 | "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.5.tgz", 180 | "integrity": "sha512-Ii012v05KEVuUoFWmMW/UQv9aRIc3ZwkWDcM+h5Il8izZCtRVpDUfwpoFf7eOtajT3QiGR4yDUx7lPqHJULgbg==", 181 | "dependencies": { 182 | "follow-redirects": "^1.15.4", 183 | "form-data": "^4.0.0", 184 | "proxy-from-env": "^1.1.0" 185 | } 186 | }, 187 | "node_modules/base-x": { 188 | "version": "4.0.0", 189 | "resolved": "https://registry.npmjs.org/base-x/-/base-x-4.0.0.tgz", 190 | "integrity": "sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw==" 191 | }, 192 | "node_modules/bech32": { 193 | "version": "2.0.0", 194 | "resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz", 195 | "integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==" 196 | }, 197 | "node_modules/big-varuint-js": { 198 | "version": "0.2.1", 199 | "resolved": "https://registry.npmjs.org/big-varuint-js/-/big-varuint-js-0.2.1.tgz", 200 | "integrity": "sha512-79JS5h53rP+L2CyXbpgMaLPKyHek7VAglJbug3KWR3CBtBabkcvxaU0PSMTBP9jBhDYoJsz5f+Ow20XSp8n52A==", 201 | "peerDependencies": { 202 | "typescript": "^5.0.0" 203 | } 204 | }, 205 | "node_modules/bip174": { 206 | "version": "2.1.1", 207 | "resolved": "https://registry.npmjs.org/bip174/-/bip174-2.1.1.tgz", 208 | "integrity": "sha512-mdFV5+/v0XyNYXjBS6CQPLo9ekCx4gtKZFnJm5PMto7Fs9hTTDpkkzOB7/FtluRI6JbUUAu+snTYfJRgHLZbZQ==", 209 | "engines": { 210 | "node": ">=8.0.0" 211 | } 212 | }, 213 | "node_modules/bip32": { 214 | "version": "4.0.0", 215 | "resolved": "https://registry.npmjs.org/bip32/-/bip32-4.0.0.tgz", 216 | "integrity": "sha512-aOGy88DDlVUhspIXJN+dVEtclhIsfAUppD43V0j40cPTld3pv/0X/MlrZSZ6jowIaQQzFwP8M6rFU2z2mVYjDQ==", 217 | "dependencies": { 218 | "@noble/hashes": "^1.2.0", 219 | "@scure/base": "^1.1.1", 220 | "typeforce": "^1.11.5", 221 | "wif": "^2.0.6" 222 | }, 223 | "engines": { 224 | "node": ">=6.0.0" 225 | } 226 | }, 227 | "node_modules/bip39": { 228 | "version": "3.1.0", 229 | "resolved": "https://registry.npmjs.org/bip39/-/bip39-3.1.0.tgz", 230 | "integrity": "sha512-c9kiwdk45Do5GL0vJMe7tS95VjCii65mYAH7DfWl3uW8AVzXKQVUm64i3hzVybBDMp9r7j9iNxR85+ul8MdN/A==", 231 | "dependencies": { 232 | "@noble/hashes": "^1.2.0" 233 | } 234 | }, 235 | "node_modules/bitcoinjs-lib": { 236 | "version": "6.1.5", 237 | "resolved": "https://registry.npmjs.org/bitcoinjs-lib/-/bitcoinjs-lib-6.1.5.tgz", 238 | "integrity": "sha512-yuf6xs9QX/E8LWE2aMJPNd0IxGofwfuVOiYdNUESkc+2bHHVKjhJd8qewqapeoolh9fihzHGoDCB5Vkr57RZCQ==", 239 | "dependencies": { 240 | "@noble/hashes": "^1.2.0", 241 | "bech32": "^2.0.0", 242 | "bip174": "^2.1.1", 243 | "bs58check": "^3.0.1", 244 | "typeforce": "^1.11.3", 245 | "varuint-bitcoin": "^1.1.2" 246 | }, 247 | "engines": { 248 | "node": ">=8.0.0" 249 | } 250 | }, 251 | "node_modules/bs58": { 252 | "version": "5.0.0", 253 | "resolved": "https://registry.npmjs.org/bs58/-/bs58-5.0.0.tgz", 254 | "integrity": "sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==", 255 | "dependencies": { 256 | "base-x": "^4.0.0" 257 | } 258 | }, 259 | "node_modules/bs58check": { 260 | "version": "3.0.1", 261 | "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-3.0.1.tgz", 262 | "integrity": "sha512-hjuuJvoWEybo7Hn/0xOrczQKKEKD63WguEjlhLExYs2wUBcebDC1jDNK17eEAD2lYfw82d5ASC1d7K3SWszjaQ==", 263 | "dependencies": { 264 | "@noble/hashes": "^1.2.0", 265 | "bs58": "^5.0.0" 266 | } 267 | }, 268 | "node_modules/cbor": { 269 | "version": "9.0.2", 270 | "resolved": "https://registry.npmjs.org/cbor/-/cbor-9.0.2.tgz", 271 | "integrity": "sha512-JPypkxsB10s9QOWwa6zwPzqE1Md3vqpPc+cai4sAecuCsRyAtAl/pMyhPlMbT/xtPnm2dznJZYRLui57qiRhaQ==", 272 | "dependencies": { 273 | "nofilter": "^3.1.0" 274 | }, 275 | "engines": { 276 | "node": ">=16" 277 | } 278 | }, 279 | "node_modules/cipher-base": { 280 | "version": "1.0.4", 281 | "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", 282 | "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", 283 | "dependencies": { 284 | "inherits": "^2.0.1", 285 | "safe-buffer": "^5.0.1" 286 | } 287 | }, 288 | "node_modules/combined-stream": { 289 | "version": "1.0.8", 290 | "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", 291 | "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", 292 | "dependencies": { 293 | "delayed-stream": "~1.0.0" 294 | }, 295 | "engines": { 296 | "node": ">= 0.8" 297 | } 298 | }, 299 | "node_modules/create-hash": { 300 | "version": "1.2.0", 301 | "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", 302 | "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", 303 | "dependencies": { 304 | "cipher-base": "^1.0.1", 305 | "inherits": "^2.0.1", 306 | "md5.js": "^1.3.4", 307 | "ripemd160": "^2.0.1", 308 | "sha.js": "^2.4.0" 309 | } 310 | }, 311 | "node_modules/create-require": { 312 | "version": "1.1.1", 313 | "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", 314 | "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", 315 | "dev": true 316 | }, 317 | "node_modules/delayed-stream": { 318 | "version": "1.0.0", 319 | "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", 320 | "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", 321 | "engines": { 322 | "node": ">=0.4.0" 323 | } 324 | }, 325 | "node_modules/diff": { 326 | "version": "4.0.2", 327 | "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", 328 | "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", 329 | "dev": true, 330 | "engines": { 331 | "node": ">=0.3.1" 332 | } 333 | }, 334 | "node_modules/dotenv": { 335 | "version": "16.3.2", 336 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.2.tgz", 337 | "integrity": "sha512-HTlk5nmhkm8F6JcdXvHIzaorzCoziNQT9mGxLPVXW8wJF1TiGSL60ZGB4gHWabHOaMmWmhvk2/lPHfnBiT78AQ==", 338 | "engines": { 339 | "node": ">=12" 340 | }, 341 | "funding": { 342 | "url": "https://github.com/motdotla/dotenv?sponsor=1" 343 | } 344 | }, 345 | "node_modules/ecpair": { 346 | "version": "2.1.0", 347 | "resolved": "https://registry.npmjs.org/ecpair/-/ecpair-2.1.0.tgz", 348 | "integrity": "sha512-cL/mh3MtJutFOvFc27GPZE2pWL3a3k4YvzUWEOvilnfZVlH3Jwgx/7d6tlD7/75tNk8TG2m+7Kgtz0SI1tWcqw==", 349 | "dependencies": { 350 | "randombytes": "^2.1.0", 351 | "typeforce": "^1.18.0", 352 | "wif": "^2.0.6" 353 | }, 354 | "engines": { 355 | "node": ">=8.0.0" 356 | } 357 | }, 358 | "node_modules/follow-redirects": { 359 | "version": "1.15.5", 360 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", 361 | "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", 362 | "funding": [ 363 | { 364 | "type": "individual", 365 | "url": "https://github.com/sponsors/RubenVerborgh" 366 | } 367 | ], 368 | "engines": { 369 | "node": ">=4.0" 370 | }, 371 | "peerDependenciesMeta": { 372 | "debug": { 373 | "optional": true 374 | } 375 | } 376 | }, 377 | "node_modules/form-data": { 378 | "version": "4.0.0", 379 | "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", 380 | "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", 381 | "dependencies": { 382 | "asynckit": "^0.4.0", 383 | "combined-stream": "^1.0.8", 384 | "mime-types": "^2.1.12" 385 | }, 386 | "engines": { 387 | "node": ">= 6" 388 | } 389 | }, 390 | "node_modules/hash-base": { 391 | "version": "3.1.0", 392 | "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", 393 | "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", 394 | "dependencies": { 395 | "inherits": "^2.0.4", 396 | "readable-stream": "^3.6.0", 397 | "safe-buffer": "^5.2.0" 398 | }, 399 | "engines": { 400 | "node": ">=4" 401 | } 402 | }, 403 | "node_modules/inherits": { 404 | "version": "2.0.4", 405 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 406 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 407 | }, 408 | "node_modules/make-error": { 409 | "version": "1.3.6", 410 | "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", 411 | "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", 412 | "dev": true 413 | }, 414 | "node_modules/md5.js": { 415 | "version": "1.3.5", 416 | "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", 417 | "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", 418 | "dependencies": { 419 | "hash-base": "^3.0.0", 420 | "inherits": "^2.0.1", 421 | "safe-buffer": "^5.1.2" 422 | } 423 | }, 424 | "node_modules/mime-db": { 425 | "version": "1.52.0", 426 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", 427 | "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", 428 | "engines": { 429 | "node": ">= 0.6" 430 | } 431 | }, 432 | "node_modules/mime-types": { 433 | "version": "2.1.35", 434 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", 435 | "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", 436 | "dependencies": { 437 | "mime-db": "1.52.0" 438 | }, 439 | "engines": { 440 | "node": ">= 0.6" 441 | } 442 | }, 443 | "node_modules/nofilter": { 444 | "version": "3.1.0", 445 | "resolved": "https://registry.npmjs.org/nofilter/-/nofilter-3.1.0.tgz", 446 | "integrity": "sha512-l2NNj07e9afPnhAhvgVrCD/oy2Ai1yfLpuo3EpiO1jFTsB4sFz6oIfAfSZyQzVpkZQ9xS8ZS5g1jCBgq4Hwo0g==", 447 | "engines": { 448 | "node": ">=12.19" 449 | } 450 | }, 451 | "node_modules/proxy-from-env": { 452 | "version": "1.1.0", 453 | "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", 454 | "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" 455 | }, 456 | "node_modules/randombytes": { 457 | "version": "2.1.0", 458 | "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", 459 | "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", 460 | "dependencies": { 461 | "safe-buffer": "^5.1.0" 462 | } 463 | }, 464 | "node_modules/readable-stream": { 465 | "version": "3.6.2", 466 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", 467 | "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", 468 | "dependencies": { 469 | "inherits": "^2.0.3", 470 | "string_decoder": "^1.1.1", 471 | "util-deprecate": "^1.0.1" 472 | }, 473 | "engines": { 474 | "node": ">= 6" 475 | } 476 | }, 477 | "node_modules/ripemd160": { 478 | "version": "2.0.2", 479 | "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", 480 | "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", 481 | "dependencies": { 482 | "hash-base": "^3.0.0", 483 | "inherits": "^2.0.1" 484 | } 485 | }, 486 | "node_modules/runelib": { 487 | "version": "1.0.6", 488 | "resolved": "https://registry.npmjs.org/runelib/-/runelib-1.0.6.tgz", 489 | "integrity": "sha512-/cQKVT+f3Qzh1iC45ce5gvhB/rnKK8g/m1SxSBQaE98gwHMDuqj4V3AgBMtzmpCANmmU/TkoOH/urk2EPClmSw==", 490 | "dependencies": { 491 | "bitcoinjs-lib": "^6.1.5" 492 | } 493 | }, 494 | "node_modules/runestone-js": { 495 | "version": "0.3.0", 496 | "resolved": "https://registry.npmjs.org/runestone-js/-/runestone-js-0.3.0.tgz", 497 | "integrity": "sha512-8ddhFrIZ/H1zKJ5HcSWsFabexunF4Dk7odjJQFy92ZZ+3Oxicf/VndZdP1pnPu4y2R54ZO3rP2tkPtrQgqFhxA==", 498 | "dependencies": { 499 | "big-varuint-js": "^0.2.1" 500 | }, 501 | "peerDependencies": { 502 | "typescript": "^5.0.0" 503 | } 504 | }, 505 | "node_modules/safe-buffer": { 506 | "version": "5.2.1", 507 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 508 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 509 | "funding": [ 510 | { 511 | "type": "github", 512 | "url": "https://github.com/sponsors/feross" 513 | }, 514 | { 515 | "type": "patreon", 516 | "url": "https://www.patreon.com/feross" 517 | }, 518 | { 519 | "type": "consulting", 520 | "url": "https://feross.org/support" 521 | } 522 | ] 523 | }, 524 | "node_modules/sha.js": { 525 | "version": "2.4.11", 526 | "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", 527 | "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", 528 | "dependencies": { 529 | "inherits": "^2.0.1", 530 | "safe-buffer": "^5.0.1" 531 | }, 532 | "bin": { 533 | "sha.js": "bin.js" 534 | } 535 | }, 536 | "node_modules/string_decoder": { 537 | "version": "1.3.0", 538 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", 539 | "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", 540 | "dependencies": { 541 | "safe-buffer": "~5.2.0" 542 | } 543 | }, 544 | "node_modules/tiny-secp256k1": { 545 | "version": "2.2.3", 546 | "resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-2.2.3.tgz", 547 | "integrity": "sha512-SGcL07SxcPN2nGKHTCvRMkQLYPSoeFcvArUSCYtjVARiFAWU44cCIqYS0mYAU6nY7XfvwURuTIGo2Omt3ZQr0Q==", 548 | "dependencies": { 549 | "uint8array-tools": "0.0.7" 550 | }, 551 | "engines": { 552 | "node": ">=14.0.0" 553 | } 554 | }, 555 | "node_modules/ts-node": { 556 | "version": "10.9.2", 557 | "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", 558 | "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", 559 | "dev": true, 560 | "dependencies": { 561 | "@cspotcode/source-map-support": "^0.8.0", 562 | "@tsconfig/node10": "^1.0.7", 563 | "@tsconfig/node12": "^1.0.7", 564 | "@tsconfig/node14": "^1.0.0", 565 | "@tsconfig/node16": "^1.0.2", 566 | "acorn": "^8.4.1", 567 | "acorn-walk": "^8.1.1", 568 | "arg": "^4.1.0", 569 | "create-require": "^1.1.0", 570 | "diff": "^4.0.1", 571 | "make-error": "^1.1.1", 572 | "v8-compile-cache-lib": "^3.0.1", 573 | "yn": "3.1.1" 574 | }, 575 | "bin": { 576 | "ts-node": "dist/bin.js", 577 | "ts-node-cwd": "dist/bin-cwd.js", 578 | "ts-node-esm": "dist/bin-esm.js", 579 | "ts-node-script": "dist/bin-script.js", 580 | "ts-node-transpile-only": "dist/bin-transpile.js", 581 | "ts-script": "dist/bin-script-deprecated.js" 582 | }, 583 | "peerDependencies": { 584 | "@swc/core": ">=1.2.50", 585 | "@swc/wasm": ">=1.2.50", 586 | "@types/node": "*", 587 | "typescript": ">=2.7" 588 | }, 589 | "peerDependenciesMeta": { 590 | "@swc/core": { 591 | "optional": true 592 | }, 593 | "@swc/wasm": { 594 | "optional": true 595 | } 596 | } 597 | }, 598 | "node_modules/typeforce": { 599 | "version": "1.18.0", 600 | "resolved": "https://registry.npmjs.org/typeforce/-/typeforce-1.18.0.tgz", 601 | "integrity": "sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g==" 602 | }, 603 | "node_modules/typescript": { 604 | "version": "5.3.3", 605 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", 606 | "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", 607 | "bin": { 608 | "tsc": "bin/tsc", 609 | "tsserver": "bin/tsserver" 610 | }, 611 | "engines": { 612 | "node": ">=14.17" 613 | } 614 | }, 615 | "node_modules/uint8array-tools": { 616 | "version": "0.0.7", 617 | "resolved": "https://registry.npmjs.org/uint8array-tools/-/uint8array-tools-0.0.7.tgz", 618 | "integrity": "sha512-vrrNZJiusLWoFWBqz5Y5KMCgP9W9hnjZHzZiZRT8oNAkq3d5Z5Oe76jAvVVSRh4U8GGR90N2X1dWtrhvx6L8UQ==", 619 | "engines": { 620 | "node": ">=14.0.0" 621 | } 622 | }, 623 | "node_modules/undici-types": { 624 | "version": "5.26.5", 625 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", 626 | "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", 627 | "dev": true 628 | }, 629 | "node_modules/util-deprecate": { 630 | "version": "1.0.2", 631 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 632 | "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" 633 | }, 634 | "node_modules/v8-compile-cache-lib": { 635 | "version": "3.0.1", 636 | "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", 637 | "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", 638 | "dev": true 639 | }, 640 | "node_modules/varuint-bitcoin": { 641 | "version": "1.1.2", 642 | "resolved": "https://registry.npmjs.org/varuint-bitcoin/-/varuint-bitcoin-1.1.2.tgz", 643 | "integrity": "sha512-4EVb+w4rx+YfVM32HQX42AbbT7/1f5zwAYhIujKXKk8NQK+JfRVl3pqT3hjNn/L+RstigmGGKVwHA/P0wgITZw==", 644 | "dependencies": { 645 | "safe-buffer": "^5.1.1" 646 | } 647 | }, 648 | "node_modules/wif": { 649 | "version": "2.0.6", 650 | "resolved": "https://registry.npmjs.org/wif/-/wif-2.0.6.tgz", 651 | "integrity": "sha512-HIanZn1zmduSF+BQhkE+YXIbEiH0xPr1012QbFEGB0xsKqJii0/SqJjyn8dFv6y36kOznMgMB+LGcbZTJ1xACQ==", 652 | "dependencies": { 653 | "bs58check": "<3.0.0" 654 | } 655 | }, 656 | "node_modules/wif/node_modules/base-x": { 657 | "version": "3.0.9", 658 | "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz", 659 | "integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==", 660 | "dependencies": { 661 | "safe-buffer": "^5.0.1" 662 | } 663 | }, 664 | "node_modules/wif/node_modules/bs58": { 665 | "version": "4.0.1", 666 | "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", 667 | "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", 668 | "dependencies": { 669 | "base-x": "^3.0.2" 670 | } 671 | }, 672 | "node_modules/wif/node_modules/bs58check": { 673 | "version": "2.1.2", 674 | "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz", 675 | "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", 676 | "dependencies": { 677 | "bs58": "^4.0.0", 678 | "create-hash": "^1.1.0", 679 | "safe-buffer": "^5.1.2" 680 | } 681 | }, 682 | "node_modules/yn": { 683 | "version": "3.1.1", 684 | "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", 685 | "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", 686 | "dev": true, 687 | "engines": { 688 | "node": ">=6" 689 | } 690 | } 691 | } 692 | } 693 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "runestone-tools", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "etch": "npx tsx RUNE_Etching", 8 | "mint": "npx tsx RUNE_minting", 9 | "transfer": "npx tsx RUNE_transfer" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "ISC", 14 | "dependencies": { 15 | "@bitcoinerlab/secp256k1": "^1.1.1", 16 | "@ordjs/runestone": "^1.6.0", 17 | "axios": "^1.6.5", 18 | "bip32": "^4.0.0", 19 | "bip39": "^3.1.0", 20 | "bitcoinjs-lib": "^6.1.5", 21 | "cbor": "^9.0.2", 22 | "dotenv": "^16.3.2", 23 | "ecpair": "^2.1.0", 24 | "runelib": "^1.0.6", 25 | "runestone-js": "^0.3.0", 26 | "tiny-secp256k1": "^2.2.3" 27 | }, 28 | "devDependencies": { 29 | "@types/node": "^20.11.5", 30 | "ts-node": "^10.9.2", 31 | "typescript": "^5.3.3" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Runestone toolbox Typescript 2 | --- 3 | Runestone Etch, Mint, Transfer, Recursive Rune, Airdrop, Encipher, Decipher 4 | 5 | 6 | ## Prerequisites 7 | --- 8 | Before running the script, ensure you have the following dependencies installed: 9 | 10 | - `bitcoinjs-lib` 11 | - `ecpair` 12 | - `@bitcoinerlab/secp256k1` 13 | - `axios` 14 | - `runelib` 15 | - `cbor` 16 | 17 | You can install them using npm: 18 | 19 | ```sh 20 | npm install bitcoinjs-lib ecpair @bitcoinerlab/secp256k1 axios runelib 21 | ``` 22 | 23 | ## Configuration 24 | --- 25 | Ensure you have a `.env` file in your project root with the following variables: 26 | 27 | ```plaintext 28 | PRIVATE_KEY= 29 | MNEMONIC= (optional, if using SeedWallet) 30 | ``` 31 | 32 | ## Recursive Rune Etching Script 33 | 34 | This script allows you to create a recursive ordinal inscription on the Bitcoin testnet using Taproot addresses. It involves creating an HTML content, enciphering it as a Taproot script, and broadcasting the transaction to the testnet network. 35 | 36 | ### Usage 37 | 38 | 1. **Initialize ECC Library**: 39 | The script initializes the ECC library using `initEccLib` from `bitcoinjs-lib`. 40 | 41 | 2. **Wallet Setup**: 42 | The script supports two types of wallets: `SeedWallet` and `WIFWallet`. Currently, the `WIFWallet` is used. 43 | 44 | 3. **Create Etching**: 45 | The `etching` function is the main function that creates the recursive ordinal. It involves the following steps: 46 | - Define the HTML content to be inscribed. 47 | - Create an inscription object using `EtchInscription`. 48 | - Define a Taproot script with the inscription and the wallet's public key. 49 | - Generate a Taproot address and wait for UTXOs to be funded to this address. 50 | - Create a Partially Signed Bitcoin Transaction (PSBT). 51 | - Add inputs and outputs to the PSBT. 52 | - Sign and broadcast the transaction. 53 | 54 | 4. **Broadcast Transaction**: 55 | The `signAndSend` function handles the signing and broadcasting of the transaction. It supports both node environment and browser environment. 56 | 57 | ## Functions 58 | 59 | - **etching()**: Main function to create the recursive ordinal inscription. 60 | - **waitUntilUTXO(address: string)**: Polls the address for UTXOs until found. 61 | - **getTx(id: string)**: Fetches transaction hex by ID. 62 | - **signAndSend(keyPair: BTCSigner, psbt: Psbt, address: string)**: Signs and broadcasts the PSBT. 63 | - **broadcast(txHex: string)**: Broadcasts the raw transaction to the network. 64 | - **tapTweakHash(pubKey: Buffer, h: Buffer | undefined)**: Computes the Taproot tweak hash. 65 | - **toXOnly(pubkey: Buffer)**: Converts a public key to X-only format. 66 | - **tweakSigner(signer: BTCSigner, opts: any)**: Tweaks the signer for Taproot key tweaking. 67 | 68 | # Taproot Rune Minting Script 69 | 70 | This script enables the minting of Runes using Taproot addresses on the Bitcoin testnet. It leverages `bitcoinjs-lib`, `ecpair`, and `runelib` to create, sign, and broadcast a Bitcoin transaction containing Runes. 71 | 72 | ## Usage 73 | 74 | 1. **Initialize ECC Library**: 75 | The script initializes the ECC library using `initEccLib` from `bitcoinjs-lib`. 76 | 77 | 2. **Wallet Setup**: 78 | The script supports two types of wallets: `SeedWallet` and `WIFWallet`. Currently, the `WIFWallet` is used. 79 | 80 | 3. **Minting with Taproot**: 81 | The `mintWithTaproot` function is the main function that mints Runes. It involves the following steps: 82 | - Define Runes to be minted. 83 | - Create a Runestone with the specified Runes. 84 | - Tweak the signer for Taproot key tweaking. 85 | - Generate a Taproot address. 86 | - Wait for UTXOs to be funded to this address. 87 | - Create a Partially Signed Bitcoin Transaction (PSBT). 88 | - Add inputs and outputs to the PSBT. 89 | - Sign and broadcast the transaction. 90 | 91 | 4. **Broadcast Transaction**: 92 | The `signAndSend` function handles the signing and broadcasting of the transaction. It supports both node environment and browser environment. 93 | 94 | ## Functions 95 | 96 | - **mintWithTaproot()**: Main function to mint Runes using Taproot. 97 | - **waitUntilUTXO(address: string)**: Polls the address for UTXOs until found. 98 | - **getTx(id: string)**: Fetches transaction hex by ID. 99 | - **signAndSend(keyPair: BTCSigner, psbt: Psbt, address: string)**: Signs and broadcasts the PSBT. 100 | - **broadcast(txHex: string)**: Broadcasts the raw transaction to the network. 101 | - **tapTweakHash(pubKey: Buffer, h: Buffer | undefined)**: Computes the Taproot tweak hash. 102 | - **toXOnly(pubkey: Buffer)**: Converts a public key to X-only format. 103 | - **tweakSigner(signer: BTCSigner, opts: any)**: Tweaks the signer for Taproot key tweaking. 104 | - 105 | # Recursive Rune Minting Script with Taproot Inscription 106 | 107 | This script allows you to mint recursive Runes on the Bitcoin testnet using Taproot addresses. The script uses `bitcoinjs-lib`, `ecpair`, and `runelib` libraries to create, sign, and broadcast Bitcoin transactions containing Runes. 108 | 109 | ## Prerequisites 110 | 111 | Make sure you have the following dependencies installed: 112 | 113 | - `bitcoinjs-lib` 114 | - `ecpair` 115 | - `@bitcoinerlab/secp256k1` 116 | - `axios` 117 | - `cbor` 118 | - `runelib` 119 | 120 | You can install them using npm: 121 | 122 | ```sh 123 | npm install bitcoinjs-lib ecpair @bitcoinerlab/secp256k1 axios cbor runelib 124 | ``` 125 | 126 | ## Usage 127 | 128 | ### Steps Overview 129 | 130 | 1. **Initialize ECC Library**: 131 | The script initializes the ECC library using `initEccLib` from `bitcoinjs-lib`. 132 | 133 | 2. **Wallet Setup**: 134 | The script supports two types of wallets: `SeedWallet` and `WIFWallet`. Currently, the `WIFWallet` is used. 135 | 136 | 3. **Create Taproot Inscription**: 137 | The `createChildInscriptionTapScript` function creates the Taproot script for the inscription, using the provided content and metadata. 138 | 139 | 4. **Mint Runes and Create PSBT**: 140 | The `childInscribe` function mints Runes and creates a Partially Signed Bitcoin Transaction (PSBT). It includes: 141 | - Funding the Taproot address. 142 | - Adding inputs and outputs to the PSBT. 143 | - Signing and broadcasting the transaction. 144 | 145 | 5. **Broadcast Transaction**: 146 | The `signAndSend` function handles the signing and broadcasting of the transaction. 147 | 148 | ## Functions 149 | 150 | - **contentBuffer(content: string)**: Converts content string to a buffer. 151 | - **createChildInscriptionTapScript()**: Creates the Taproot script for child inscriptions. 152 | - **childInscribe()**: Main function to mint Runes using Taproot. 153 | - **signAndSend(keyPair: BTCSigner, psbt: Psbt)**: Signs and broadcasts the PSBT. 154 | - **waitUntilUTXO(address: string)**: Polls the address for UTXOs until found. 155 | - **getTx(id: string)**: Fetches transaction hex by ID. 156 | - **broadcast(txHex: string)**: Broadcasts the raw transaction to the network. 157 | - **tapTweakHash(pubKey: Buffer, h: Buffer | undefined)**: Computes the Taproot tweak hash. 158 | - **toXOnly(pubkey: Buffer)**: Converts a public key to X-only format. 159 | - **tweakSigner(signer: BTCSigner, opts: any)**: Tweaks the signer for Taproot key tweaking. 160 | 161 | ## Important Variables 162 | 163 | - **network**: Configured to use Bitcoin testnet. 164 | - **txhash**: Transaction hash of the parent inscription. 165 | - **memeType**: MIME type for the content. 166 | - **metaProtocol**: Meta protocol buffer. 167 | - **receiveAddress**: Address to receive the Runes. 168 | - **metadata**: Metadata for the inscription. 169 | - **fee**: Transaction fee. 170 | - **parentInscriptionTXID**: Transaction ID of the parent inscription. 171 | - **contentBufferData**: Buffer containing the content for the inscription. 172 | - **revealtxIDBuffer**: Buffer of the parent inscription transaction ID. 173 | - **pointerBuffer**: Array of buffers for pointers in the Taproot script. 174 | - **metadataBuffer**: CBOR encoded metadata buffer. 175 | - **contentBufferArray**: Array of content buffers split into chunks. 176 | ## Example Output 177 | 178 | The script will output logs at various stages: 179 | - The Taproot address where funds should be sent. 180 | - The UTXO being used. 181 | - The raw transaction hex to be broadcasted. 182 | - The transaction ID after broadcasting. 183 | 184 | ## Notes 185 | 186 | - The script is configured to work with the Bitcoin testnet. 187 | - Ensure that you have testnet coins available in the provided private key. 188 | - Adjust the fee and other parameters as needed. 189 | 190 | --- 191 | ## Contact Info 192 | If you have technical issues and development inquiries, please contact me. 193 | 194 | - Telegram: https://t.me/rizz_cat/ 195 | - Twitter: https://x.com/rez_cats/ 196 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | // "module": "esnext", 4 | // "moduleResolution": "bundler", 5 | "module": "commonjs", 6 | "target": "es2015", 7 | "lib": ["es6"], 8 | "allowJs": true, 9 | "outDir": "build", 10 | "strict": true, 11 | "noImplicitAny": true, 12 | "esModuleInterop": true, 13 | "resolveJsonModule": true, 14 | "baseUrl": "." 15 | } 16 | } -------------------------------------------------------------------------------- /utils/SeedWallet.ts: -------------------------------------------------------------------------------- 1 | import * as bitcoin from "bitcoinjs-lib"; 2 | import { initEccLib, networks } from "bitcoinjs-lib"; 3 | import * as ecc from "tiny-secp256k1"; 4 | import * as bip39 from "bip39"; 5 | import BIP32Factory, { type BIP32Interface } from "bip32"; 6 | import ECPairFactory, { type ECPairInterface } from "ecpair"; 7 | import dotenv from "dotenv"; 8 | 9 | interface ISeedWallet { 10 | networkType: string; 11 | seed: string; 12 | } 13 | 14 | dotenv.config(); 15 | initEccLib(ecc); 16 | 17 | const ECPair = ECPairFactory(ecc); 18 | const bip32 = BIP32Factory(ecc); 19 | 20 | 21 | 22 | export class SeedWallet { 23 | private hdPath = "m/86'/0'/0'/0/0"; 24 | private network: bitcoin.networks.Network; 25 | public ecPair: ECPairInterface; 26 | public address: string; 27 | public output: Buffer; 28 | public publicKey: string; 29 | private bip32: BIP32Interface; 30 | 31 | constructor(walletParam : ISeedWallet) { 32 | if(walletParam.networkType == "mainnet") { 33 | this.network = networks.bitcoin; 34 | } else { 35 | this.network = networks.testnet; 36 | } 37 | const mnemonic = walletParam.seed; 38 | 39 | if (!bip39.validateMnemonic(mnemonic)) { 40 | throw new Error("invalid mnemonic"); 41 | } 42 | this.bip32 = bip32.fromSeed( 43 | bip39.mnemonicToSeedSync(mnemonic), 44 | this.network 45 | ); 46 | this.ecPair = ECPair.fromPrivateKey( 47 | this.bip32.derivePath(this.hdPath).privateKey!, 48 | { network: this.network } 49 | ); 50 | const { address, output } = bitcoin.payments.p2tr({ 51 | internalPubkey: this.ecPair.publicKey.subarray(1, 33), 52 | network: this.network, 53 | }); 54 | this.address = address as string; 55 | this.output = output as Buffer; 56 | this.publicKey = this.ecPair.publicKey.toString("hex"); 57 | } 58 | 59 | signPsbt(psbt: bitcoin.Psbt, ecPair: ECPairInterface): bitcoin.Psbt { 60 | const tweakedChildNode = ecPair.tweak( 61 | bitcoin.crypto.taggedHash("TapTweak", ecPair.publicKey.subarray(1, 33)) 62 | ); 63 | for (let i = 0; i < psbt.inputCount; i++) { 64 | psbt.signInput(i, tweakedChildNode); 65 | psbt.validateSignaturesOfInput(i, () => true); 66 | psbt.finalizeInput(i); 67 | } 68 | return psbt; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /utils/WIFWallet.ts: -------------------------------------------------------------------------------- 1 | import * as bitcoin from "bitcoinjs-lib"; 2 | import { initEccLib, networks } from "bitcoinjs-lib"; 3 | import * as ecc from "tiny-secp256k1"; 4 | import BIP32Factory, { type BIP32Interface } from "bip32"; 5 | import ECPairFactory, { type ECPairInterface } from "ecpair"; 6 | import dotenv from "dotenv"; 7 | 8 | interface IWIFWallet { 9 | networkType: string; 10 | privateKey: string; 11 | } 12 | 13 | dotenv.config(); 14 | initEccLib(ecc); 15 | 16 | const ECPair = ECPairFactory(ecc); 17 | const bip32 = BIP32Factory(ecc); 18 | 19 | 20 | 21 | export class WIFWallet { 22 | private hdPath = "m/86'/0'/0'/0/0"; 23 | private network: bitcoin.networks.Network; 24 | public ecPair: ECPairInterface; 25 | public address: string; 26 | public output: Buffer; 27 | public publicKey: string; 28 | 29 | constructor(walletParam: IWIFWallet) { 30 | if (walletParam.networkType == "mainnet") { 31 | this.network = networks.bitcoin; 32 | } else { 33 | this.network = networks.testnet; 34 | } 35 | 36 | this.ecPair = ECPair.fromWIF(walletParam.privateKey, this.network); 37 | 38 | const { address, output } = bitcoin.payments.p2tr({ 39 | internalPubkey: this.ecPair.publicKey.subarray(1, 33), 40 | network: this.network, 41 | }); 42 | this.address = address as string; 43 | this.output = output as Buffer; 44 | this.publicKey = this.ecPair.publicKey.toString("hex"); 45 | } 46 | 47 | signPsbt(psbt: bitcoin.Psbt, ecPair: ECPairInterface): bitcoin.Psbt { 48 | const tweakedChildNode = ecPair.tweak( 49 | bitcoin.crypto.taggedHash("TapTweak", ecPair.publicKey.subarray(1, 33)) 50 | ); 51 | 52 | for (let i = 0; i < psbt.inputCount; i++) { 53 | psbt.signInput(i, tweakedChildNode); 54 | psbt.validateSignaturesOfInput(i, () => true); 55 | psbt.finalizeInput(i); 56 | } 57 | return psbt; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /utils/mempool.ts: -------------------------------------------------------------------------------- 1 | import axios, { type AxiosError } from "axios"; 2 | 3 | interface IUtxo { 4 | txid: string; 5 | vout: number; 6 | value: number; 7 | } 8 | 9 | export const getScriptPubkey = async (tx: string, address: string, networkType: string): Promise => { 10 | const url = `https://mempool.space/${networkType}/api/tx/${tx}`; 11 | const res = await axios.get(url); 12 | const output = res.data.vout.find((output: any) => output.scriptpubkey_address === address) 13 | return output.scriptpubkey; 14 | }; 15 | 16 | export const getUtxos = async (address: string, networkType: string): Promise => { 17 | const url = `https://mempool.space/${networkType}/api/address/${address}/utxo`; 18 | const res = await axios.get(url); 19 | const utxos: IUtxo[] = []; 20 | 21 | res.data.forEach((utxoData: any) => { 22 | utxos.push({ 23 | txid: utxoData.txid, 24 | vout: utxoData.vout, 25 | value: utxoData.value, 26 | }); 27 | }); 28 | return utxos; 29 | }; 30 | 31 | export const pushBTCpmt = async (rawtx: any, networkType: string) => { 32 | const txid = await postData( 33 | `https://mempool.space/${networkType}/api/tx`, 34 | rawtx 35 | ); 36 | return txid; 37 | } 38 | 39 | const postData = async ( 40 | url: string, 41 | json: any, 42 | content_type = "text/plain", 43 | apikey = "" 44 | ): Promise => { 45 | while (1) { 46 | try { 47 | const headers: any = {}; 48 | if (content_type) headers["Content-Type"] = content_type; 49 | if (apikey) headers["X-Api-Key"] = apikey; 50 | const res = await axios.post(url, json, { 51 | headers, 52 | }); 53 | return res.data as string; 54 | } catch (err) { 55 | const axiosErr = err as AxiosError; 56 | console.log("axiosErr.response?.data", axiosErr.response?.data); 57 | if ( 58 | !(axiosErr.response?.data as string).includes( 59 | 'sendrawtransaction RPC error: {"code":-26,"message":"too-long-mempool-chain,' 60 | ) 61 | ) 62 | throw new Error("Got an err when push tx"); 63 | } 64 | } 65 | } --------------------------------------------------------------------------------