├── .env ├── .env.example ├── .gitignore ├── Child_Inscription.ts ├── Delegate_inscription.ts ├── Encipher_Decipher_Runestone.ts ├── HTML_Child_Inscription.ts ├── Image_Child_Inscription.ts ├── Native-Segwit_Multi-Sig.ts ├── Parent_Inscription.ts ├── README.md ├── RUNE_airdrop.ts ├── RUNE_etching.ts ├── RUNE_etching_temp.ts ├── RUNE_minting.ts ├── RUNE_transfer.ts ├── Recursive_Rune.ts ├── Reinscription.ts ├── Runestone.ts ├── Taproot_Multi-Sig.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 ├── tsconfig.json └── utils ├── SeedWallet.ts ├── WIFWallet.ts ├── mempool.ts └── mutisigWallet.ts /.env: -------------------------------------------------------------------------------- 1 | MNEMONIC = mixed trophy belt busy six obey ring blade smile energy shop kind 2 | PRIVATE_KEY = cVM5SRsLwAA9o3ZABKENw19GRyL3b2Hf8L9gzRGQqJbxY3EFcMKP 3 | NODE = true -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | MNEMONIC = 2 | PRIVATE_KEY = -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env -------------------------------------------------------------------------------- /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 | 21 | //test 22 | // const network = networks.testnet; 23 | const network = networks.bitcoin; 24 | 25 | initEccLib(ecc as any); 26 | const ECPair: ECPairAPI = ECPairFactory(ecc); 27 | 28 | // const seed: string = process.env.MNEMONIC as string; 29 | // const networkType: string = networkConfig.networkType; 30 | // const wallet = new SeedWallet({ networkType: networkType, seed: seed }); 31 | 32 | const privateKey: string = process.env.PRIVATE_KEY as string; 33 | const networkType: string = networkConfig.networkType; 34 | const wallet = new WIFWallet({ networkType: networkType, privateKey: privateKey }); 35 | 36 | export const contentBuffer = (content: string) => { 37 | return Buffer.from(` 38 | 39 | 40 | 41 | 42 | Build Your Own Recursive Ordinal 43 | 44 | 45 |
46 | 47 |
48 | 49 | `, 'utf8') 50 | } 51 | 52 | const txhash: string = '9bcaa71d811e34b7176f50982712fb5f4b7726ae2084cb0f886e7eb3a4db5d69'; 53 | const txidBuffer = Buffer.from(txhash, 'hex') 54 | const inscriptionBuffer = txidBuffer.reverse() 55 | const pointer1: number = 546 * 1; 56 | const pointer2: number = 546 * 2; 57 | const pointer3: number = 546 * 3; 58 | const pointerBuffer1: Buffer = Buffer.from(pointer1.toString(16).padStart(4, '0'), 'hex'); 59 | const pointerBuffer2: Buffer = Buffer.from(pointer2.toString(16).padStart(4, '0'), 'hex'); 60 | const pointerBuffer3: Buffer = Buffer.from(pointer3.toString(16).padStart(4, '0'), 'hex'); 61 | 62 | const content1 = '6cfd2a4ca879ba92e328acd0e2e8b0e42726bc6ff2b78d10706e138fb38b10f6i0' 63 | const content2 = '9566bc0e79ca905bf01db55bf154acb3a04e00e8bb2e9d4903639c5badf66d42i0' 64 | const contentBufferData1 = Buffer.from('0.364972.bitmap', 'utf8') 65 | const contentBufferData2 = Buffer.from('1.364972.bitmap', 'utf8') 66 | 67 | export function createChildInscriptionTapScript(): Array { 68 | 69 | const metadataBuffer = Buffer.from('a26474797065644269746d61706b6465736372697074696f6e78184269746d617020436f6d6d756e697479204f72646e616c73', 'hex'); 70 | 71 | const keyPair = wallet.ecPair; 72 | const childOrdinalStacks: any = [ 73 | toXOnly(keyPair.publicKey), 74 | opcodes.OP_CHECKSIG, 75 | opcodes.OP_FALSE, 76 | opcodes.OP_IF, 77 | Buffer.from("ord", "utf8"), 78 | 1, 79 | 1, 80 | Buffer.concat([Buffer.from("text/html;charset=utf-8", "utf8")]), 81 | 1, 82 | 3, 83 | inscriptionBuffer, 84 | 1, 85 | 2, 86 | pointerBuffer1, 87 | 1, 88 | 5, 89 | metadataBuffer, 90 | opcodes.OP_0, 91 | contentBufferData1, 92 | opcodes.OP_ENDIF, 93 | opcodes.OP_FALSE, 94 | opcodes.OP_IF, 95 | Buffer.from("ord", "utf8"), 96 | 1, 97 | 1, 98 | Buffer.concat([Buffer.from("text/html;charset=utf-8", "utf8")]), 99 | 1, 100 | 3, 101 | inscriptionBuffer, 102 | 1, 103 | 2, 104 | pointerBuffer2, 105 | 1, 106 | 5, 107 | metadataBuffer, 108 | opcodes.OP_0, 109 | contentBufferData2, 110 | opcodes.OP_ENDIF 111 | ]; 112 | 113 | return childOrdinalStacks; 114 | } 115 | 116 | async function childInscribe() { 117 | const keyPair = wallet.ecPair; 118 | const childOrdinalStack = createChildInscriptionTapScript(); 119 | 120 | const ordinal_script = script.compile(childOrdinalStack); 121 | 122 | const scriptTree: Taptree = { 123 | output: ordinal_script, 124 | }; 125 | 126 | const redeem = { 127 | output: ordinal_script, 128 | redeemVersion: 192, 129 | }; 130 | 131 | const ordinal_p2tr = payments.p2tr({ 132 | internalPubkey: toXOnly(keyPair.publicKey), 133 | network, 134 | scriptTree, 135 | redeem, 136 | }); 137 | 138 | const address = ordinal_p2tr.address ?? ""; 139 | console.log("send coin to address", address); 140 | 141 | const utxos = await waitUntilUTXO(address as string); 142 | console.log(`Using UTXO ${utxos[0].txid}:${utxos[0].vout}`); 143 | 144 | const psbt = new Psbt({ network }); 145 | const parentInscriptionUTXO = { 146 | txid: txhash, 147 | vout: 0, 148 | value: 546 149 | } 150 | psbt.addInput({ 151 | hash: parentInscriptionUTXO.txid, 152 | index: parentInscriptionUTXO.vout, 153 | witnessUtxo: { 154 | value: parentInscriptionUTXO.value, 155 | script: wallet.output, 156 | }, 157 | tapInternalKey: toXOnly(keyPair.publicKey), 158 | }); 159 | 160 | psbt.addInput({ 161 | hash: utxos[0].txid, 162 | index: utxos[0].vout, 163 | tapInternalKey: toXOnly(keyPair.publicKey), 164 | witnessUtxo: { value: utxos[0].value, script: ordinal_p2tr.output! }, 165 | tapLeafScript: [ 166 | { 167 | leafVersion: redeem.redeemVersion, 168 | script: redeem.output, 169 | controlBlock: ordinal_p2tr.witness![ordinal_p2tr.witness!.length - 1], 170 | }, 171 | ], 172 | }); 173 | 174 | const fee = 4500; 175 | 176 | const change = utxos[0].value - 546 * 3 - fee; 177 | 178 | psbt.addOutput({ 179 | address: "bc1pjr267uqlj79zqmv5m9vftvjp6m8fycwm8mq63497tyudq3kp3a7qh49jue", //Destination Address 180 | value: 546, 181 | }); 182 | 183 | psbt.addOutput({ 184 | address: "bc1pjr267uqlj79zqmv5m9vftvjp6m8fycwm8mq63497tyudq3kp3a7qh49jue", //Destination Address 185 | value: 546, 186 | }); 187 | 188 | psbt.addOutput({ 189 | address: "bc1pjr267uqlj79zqmv5m9vftvjp6m8fycwm8mq63497tyudq3kp3a7qh49jue", //Destination Address 190 | value: 546, 191 | }); 192 | 193 | psbt.addOutput({ 194 | address: "bc1pjr267uqlj79zqmv5m9vftvjp6m8fycwm8mq63497tyudq3kp3a7qh49jue", // Change address 195 | value: change, 196 | }); 197 | 198 | await signAndSend(keyPair, psbt); 199 | } 200 | 201 | childInscribe() 202 | 203 | export async function signAndSend( 204 | keypair: BTCSigner, 205 | psbt: Psbt, 206 | ) { 207 | const signer = tweakSigner(keypair, { network }) 208 | psbt.signInput(0, signer); 209 | psbt.signInput(1, keypair); 210 | psbt.finalizeAllInputs() 211 | const tx = psbt.extractTransaction(); 212 | console.log(tx.virtualSize()) 213 | console.log(`Broadcasting Transaction Hex: ${tx.toHex()}`); 214 | 215 | // const txid = await broadcast(tx.toHex()); 216 | // console.log(`Success! Txid is ${txid}`); 217 | } 218 | 219 | export async function waitUntilUTXO(address: string) { 220 | return new Promise((resolve, reject) => { 221 | let intervalId: any; 222 | const checkForUtxo = async () => { 223 | try { 224 | const response: AxiosResponse = await blockstream.get( 225 | `/address/${address}/utxo` 226 | ); 227 | const data: IUTXO[] = response.data 228 | ? JSON.parse(response.data) 229 | : undefined; 230 | console.log(data); 231 | if (data.length > 0) { 232 | resolve(data); 233 | clearInterval(intervalId); 234 | } 235 | } catch (error) { 236 | reject(error); 237 | clearInterval(intervalId); 238 | } 239 | }; 240 | intervalId = setInterval(checkForUtxo, 4000); 241 | }); 242 | } 243 | 244 | export async function getTx(id: string): Promise { 245 | const response: AxiosResponse = await blockstream.get( 246 | `/tx/${id}/hex` 247 | ); 248 | return response.data; 249 | } 250 | 251 | 252 | const blockstream = new axios.Axios({ 253 | // baseURL: `https://mempool.space/testnet/api`, 254 | baseURL: `https://mempool.space/api`, 255 | }); 256 | 257 | export async function broadcast(txHex: string) { 258 | const response: AxiosResponse = await blockstream.post("/tx", txHex); 259 | return response.data; 260 | } 261 | 262 | function tapTweakHash(pubKey: Buffer, h: Buffer | undefined): Buffer { 263 | return crypto.taggedHash( 264 | "TapTweak", 265 | Buffer.concat(h ? [pubKey, h] : [pubKey]) 266 | ); 267 | } 268 | 269 | function toXOnly(pubkey: Buffer): Buffer { 270 | return pubkey.subarray(1, 33); 271 | } 272 | 273 | function tweakSigner(signer: any, opts: any = {}) { 274 | let privateKey = signer.privateKey; 275 | if (!privateKey) { 276 | throw new Error('Private key is required for tweaking signer!'); 277 | } 278 | if (signer.publicKey[0] === 3) { 279 | privateKey = ecc.privateNegate(privateKey); 280 | } 281 | const tweakedPrivateKey = ecc.privateAdd(privateKey, tapTweakHash(toXOnly(signer.publicKey), opts.tweakHash)); 282 | if (!tweakedPrivateKey) { 283 | throw new Error('Invalid tweaked private key!'); 284 | } 285 | return ECPair.fromPrivateKey(Buffer.from(tweakedPrivateKey), { 286 | network: opts.network, 287 | }); 288 | } 289 | 290 | interface IUTXO { 291 | txid: string; 292 | vout: number; 293 | status: { 294 | confirmed: boolean; 295 | block_height: number; 296 | block_hash: string; 297 | block_time: number; 298 | }; 299 | value: number; 300 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Native-Segwit_Multi-Sig.ts: -------------------------------------------------------------------------------- 1 | import * as bitcoin from "bitcoinjs-lib"; 2 | 3 | const network = bitcoin.networks.testnet; // Otherwise, bitcoin = mainnet and regnet = local 4 | 5 | export async function createNativeSegwit( 6 | originPubkeys: string[], // Singer pubkeys list 7 | threshold: number // Number of Co-Signers 8 | ) { 9 | try { 10 | const hexedPubkeys = originPubkeys.map((pubkey) => 11 | Buffer.from(pubkey, "hex") 12 | ); 13 | const p2ms = bitcoin.payments.p2ms({ 14 | m: parseInt(threshold.toString()), 15 | pubkeys: hexedPubkeys, 16 | network, 17 | }); 18 | const p2wsh = bitcoin.payments.p2wsh({ redeem: p2ms, network }); 19 | 20 | return { 21 | success: true, 22 | message: "Create Musig Wallet successfully.", 23 | payload: { 24 | address: p2wsh.address, 25 | }, 26 | }; 27 | } catch (error: any) { 28 | console.log("error in creating segwit address ==> ", error); 29 | return { 30 | success: false, 31 | message: "There is something error", 32 | payload: null, 33 | }; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Runestone toolbox Typescript 2 | --- 3 | Runestone Etch, Mint, Transfer, Recursive Rune, Airdrop, Encipher, Decipher 4 | 5 | ## Prerequisites 6 | --- 7 | Before running the script, ensure you have the following dependencies installed: 8 | 9 | - `bitcoinjs-lib` 10 | - `ecpair` 11 | - `@bitcoinerlab/secp256k1` 12 | - `axios` 13 | - `runelib` 14 | - `cbor` 15 | 16 | You can install them using npm: 17 | 18 | ```sh 19 | npm install bitcoinjs-lib ecpair @bitcoinerlab/secp256k1 axios runelib 20 | ``` 21 | 22 | ## Configuration 23 | --- 24 | Ensure you have a `.env` file in your project root with the following variables: 25 | 26 | ```plaintext 27 | PRIVATE_KEY= 28 | MNEMONIC= (optional, if using SeedWallet) 29 | ``` 30 | 31 | ## Recursive Rune Etching Script 32 | 33 | 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. 34 | 35 | ### Usage 36 | 37 | 1. **Initialize ECC Library**: 38 | The script initializes the ECC library using `initEccLib` from `bitcoinjs-lib`. 39 | 40 | 2. **Wallet Setup**: 41 | The script supports two types of wallets: `SeedWallet` and `WIFWallet`. Currently, the `WIFWallet` is used. 42 | 43 | 3. **Create Etching**: 44 | The `etching` function is the main function that creates the recursive ordinal. It involves the following steps: 45 | - Define the HTML content to be inscribed. 46 | - Create an inscription object using `EtchInscription`. 47 | - Define a Taproot script with the inscription and the wallet's public key. 48 | - Generate a Taproot address and wait for UTXOs to be funded to this address. 49 | - Create a Partially Signed Bitcoin Transaction (PSBT). 50 | - Add inputs and outputs to the PSBT. 51 | - Sign and broadcast the transaction. 52 | 53 | 4. **Broadcast Transaction**: 54 | The `signAndSend` function handles the signing and broadcasting of the transaction. It supports both node environment and browser environment. 55 | 56 | ## Functions 57 | 58 | - **etching()**: Main function to create the recursive ordinal inscription. 59 | - **waitUntilUTXO(address: string)**: Polls the address for UTXOs until found. 60 | - **getTx(id: string)**: Fetches transaction hex by ID. 61 | - **signAndSend(keyPair: BTCSigner, psbt: Psbt, address: string)**: Signs and broadcasts the PSBT. 62 | - **broadcast(txHex: string)**: Broadcasts the raw transaction to the network. 63 | - **tapTweakHash(pubKey: Buffer, h: Buffer | undefined)**: Computes the Taproot tweak hash. 64 | - **toXOnly(pubkey: Buffer)**: Converts a public key to X-only format. 65 | - **tweakSigner(signer: BTCSigner, opts: any)**: Tweaks the signer for Taproot key tweaking. 66 | 67 | # Taproot Rune Minting Script 68 | 69 | 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. 70 | 71 | ## Usage 72 | 73 | 1. **Initialize ECC Library**: 74 | The script initializes the ECC library using `initEccLib` from `bitcoinjs-lib`. 75 | 76 | 2. **Wallet Setup**: 77 | The script supports two types of wallets: `SeedWallet` and `WIFWallet`. Currently, the `WIFWallet` is used. 78 | 79 | 3. **Minting with Taproot**: 80 | The `mintWithTaproot` function is the main function that mints Runes. It involves the following steps: 81 | - Define Runes to be minted. 82 | - Create a Runestone with the specified Runes. 83 | - Tweak the signer for Taproot key tweaking. 84 | - Generate a Taproot address. 85 | - Wait for UTXOs to be funded to this address. 86 | - Create a Partially Signed Bitcoin Transaction (PSBT). 87 | - Add inputs and outputs to the PSBT. 88 | - Sign and broadcast the transaction. 89 | 90 | 4. **Broadcast Transaction**: 91 | The `signAndSend` function handles the signing and broadcasting of the transaction. It supports both node environment and browser environment. 92 | 93 | ## Functions 94 | 95 | - **mintWithTaproot()**: Main function to mint Runes using Taproot. 96 | - **waitUntilUTXO(address: string)**: Polls the address for UTXOs until found. 97 | - **getTx(id: string)**: Fetches transaction hex by ID. 98 | - **signAndSend(keyPair: BTCSigner, psbt: Psbt, address: string)**: Signs and broadcasts the PSBT. 99 | - **broadcast(txHex: string)**: Broadcasts the raw transaction to the network. 100 | - **tapTweakHash(pubKey: Buffer, h: Buffer | undefined)**: Computes the Taproot tweak hash. 101 | - **toXOnly(pubkey: Buffer)**: Converts a public key to X-only format. 102 | - **tweakSigner(signer: BTCSigner, opts: any)**: Tweaks the signer for Taproot key tweaking. 103 | - 104 | # Recursive Rune Minting Script with Taproot Inscription 105 | 106 | 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. 107 | 108 | ## Prerequisites 109 | 110 | Make sure you have the following dependencies installed: 111 | 112 | - `bitcoinjs-lib` 113 | - `ecpair` 114 | - `@bitcoinerlab/secp256k1` 115 | - `axios` 116 | - `cbor` 117 | - `runelib` 118 | 119 | You can install them using npm: 120 | 121 | ```sh 122 | npm install bitcoinjs-lib ecpair @bitcoinerlab/secp256k1 axios cbor runelib 123 | ``` 124 | 125 | ## Usage 126 | 127 | ### Steps Overview 128 | 129 | 1. **Initialize ECC Library**: 130 | The script initializes the ECC library using `initEccLib` from `bitcoinjs-lib`. 131 | 132 | 2. **Wallet Setup**: 133 | The script supports two types of wallets: `SeedWallet` and `WIFWallet`. Currently, the `WIFWallet` is used. 134 | 135 | 3. **Create Taproot Inscription**: 136 | The `createChildInscriptionTapScript` function creates the Taproot script for the inscription, using the provided content and metadata. 137 | 138 | 4. **Mint Runes and Create PSBT**: 139 | The `childInscribe` function mints Runes and creates a Partially Signed Bitcoin Transaction (PSBT). It includes: 140 | - Funding the Taproot address. 141 | - Adding inputs and outputs to the PSBT. 142 | - Signing and broadcasting the transaction. 143 | 144 | 5. **Broadcast Transaction**: 145 | The `signAndSend` function handles the signing and broadcasting of the transaction. 146 | 147 | ## Functions 148 | 149 | - **contentBuffer(content: string)**: Converts content string to a buffer. 150 | - **createChildInscriptionTapScript()**: Creates the Taproot script for child inscriptions. 151 | - **childInscribe()**: Main function to mint Runes using Taproot. 152 | - **signAndSend(keyPair: BTCSigner, psbt: Psbt)**: Signs and broadcasts the PSBT. 153 | - **waitUntilUTXO(address: string)**: Polls the address for UTXOs until found. 154 | - **getTx(id: string)**: Fetches transaction hex by ID. 155 | - **broadcast(txHex: string)**: Broadcasts the raw transaction to the network. 156 | - **tapTweakHash(pubKey: Buffer, h: Buffer | undefined)**: Computes the Taproot tweak hash. 157 | - **toXOnly(pubkey: Buffer)**: Converts a public key to X-only format. 158 | - **tweakSigner(signer: BTCSigner, opts: any)**: Tweaks the signer for Taproot key tweaking. 159 | 160 | ## Important Variables 161 | 162 | - **network**: Configured to use Bitcoin testnet. 163 | - **txhash**: Transaction hash of the parent inscription. 164 | - **memeType**: MIME type for the content. 165 | - **metaProtocol**: Meta protocol buffer. 166 | - **receiveAddress**: Address to receive the Runes. 167 | - **metadata**: Metadata for the inscription. 168 | - **fee**: Transaction fee. 169 | - **parentInscriptionTXID**: Transaction ID of the parent inscription. 170 | - **contentBufferData**: Buffer containing the content for the inscription. 171 | - **revealtxIDBuffer**: Buffer of the parent inscription transaction ID. 172 | - **pointerBuffer**: Array of buffers for pointers in the Taproot script. 173 | - **metadataBuffer**: CBOR encoded metadata buffer. 174 | - **contentBufferArray**: Array of content buffers split into chunks. 175 | ## Example Output 176 | 177 | The script will output logs at various stages: 178 | - The Taproot address where funds should be sent. 179 | - The UTXO being used. 180 | - The raw transaction hex to be broadcasted. 181 | - The transaction ID after broadcasting. 182 | 183 | ## Notes 184 | 185 | - The script is configured to work with the Bitcoin testnet. 186 | - Ensure that you have testnet coins available in the provided private key. 187 | - Adjust the fee and other parameters as needed. 188 | 189 | --- 190 | -------------------------------------------------------------------------------- /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_etching_temp.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 | opcodes, 12 | } from "bitcoinjs-lib"; 13 | import { U32, U64, U128 } from "big-varuint-js"; 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 { Runestone, RuneId, SpacedRune, Symbol } from 'runestone-js'; 19 | import networkConfig from "config/network.config"; 20 | import { WIFWallet } from 'utils/WIFWallet' 21 | import { SeedWallet } from "utils/SeedWallet"; 22 | 23 | const network = networks.testnet; 24 | initEccLib(ecc as any); 25 | declare const window: any; 26 | const ECPair: ECPairAPI = ECPairFactory(ecc); 27 | 28 | // const seed: string = process.env.MNEMONIC as string; 29 | // const networkType: string = networkConfig.networkType; 30 | // const wallet = new SeedWallet({ networkType: networkType, seed: seed }); 31 | 32 | const privateKey: string = process.env.PRIVATE_KEY as string; 33 | const networkType: string = networkConfig.networkType; 34 | const wallet = new WIFWallet({ networkType: networkType, privateKey: privateKey }); 35 | 36 | const blockstream = new axios.Axios({ 37 | baseURL: `https://mempool.space/testnet/api`, 38 | }); 39 | 40 | async function getTaprootScript() { 41 | try { 42 | const response: AxiosResponse = await axios.get( 43 | `https://mempool.space/api/tx/b21f673d0da0b870c0aed945a018510ca93d79332d3a2ee655f665fc9dd2d261/hex`) 44 | const data = response.data; 45 | const tx = Transaction.fromHex(data); 46 | 47 | const inputIndex = 1; // Example input index 48 | 49 | // Access the witness buffer for the specific input 50 | const witnessBuffer = tx.ins[inputIndex].witness; 51 | 52 | // Example: Decompile the last element, assuming it contains relevant script information 53 | const etchingScriptBuffer = witnessBuffer[witnessBuffer.length -2]; 54 | const decompiledEtchingScript = script.decompile(etchingScriptBuffer); 55 | 56 | // Assuming that your etching stacks are in the decompiled script based on your known format 57 | console.log('Etching Stacks:', decompiledEtchingScript); 58 | 59 | } catch (error) { 60 | console.log(error) 61 | } 62 | } 63 | // getTaprootScript(); 64 | 65 | async function etching() { 66 | const keyPair = wallet.ecPair; 67 | const runestone = createRunestone(); 68 | const etchingStacks: any = [ 69 | toXOnly(keyPair.publicKey), 70 | opcodes.OP_CHECKSIG, 71 | opcodes.OP_FALSE, 72 | opcodes.OP_IF, 73 | Buffer.from("ord", "utf8"), 74 | 1, 75 | 1, 76 | Buffer.concat([Buffer.from("text/plain;charset=utf-8", "utf8")]), 77 | 1, 78 | 2, 79 | opcodes.OP_0, 80 | 1, 81 | 13, 82 | runestone.commitBuffer, 83 | opcodes.OP_0, 84 | Buffer.concat([Buffer.from("MoonCity Recursive Parent", "utf8")]), 85 | opcodes.OP_ENDIF, 86 | ]; 87 | const etching_script = script.compile(etchingStacks); 88 | 89 | const scriptTree: Taptree = { 90 | output: etching_script, 91 | }; 92 | 93 | const redeem = { 94 | output: etching_script, 95 | redeemVersion: 192, 96 | }; 97 | 98 | const etching_p2tr = payments.p2tr({ 99 | internalPubkey: toXOnly(keyPair.publicKey), 100 | network, 101 | scriptTree, 102 | redeem, 103 | }); 104 | 105 | const address = etching_p2tr.address ?? ""; 106 | console.log("send coin to address", address); 107 | 108 | const utxos = await waitUntilUTXO(address as string); 109 | console.log(`Using UTXO ${utxos[0].txid}:${utxos[0].vout}`); 110 | 111 | const psbt = new Psbt({ network }); 112 | 113 | psbt.addInput({ 114 | hash: utxos[0].txid, 115 | index: utxos[0].vout, 116 | tapInternalKey: toXOnly(keyPair.publicKey), 117 | witnessUtxo: { value: utxos[0].value, script: etching_p2tr.output! }, 118 | tapLeafScript: [ 119 | { 120 | leafVersion: redeem.redeemVersion, 121 | script: redeem.output, 122 | controlBlock: etching_p2tr.witness![etching_p2tr.witness!.length - 1], 123 | }, 124 | ], 125 | }); 126 | 127 | const runeScript = script.compile([ 128 | opcodes.OP_RETURN, 129 | opcodes.OP_13, 130 | runestone.buffer 131 | ]); 132 | 133 | psbt.addOutput({ 134 | script: runeScript, 135 | value: 0, 136 | }); 137 | 138 | const fee = 20000; 139 | 140 | const change = utxos[0].value - 546 - fee; 141 | 142 | psbt.addOutput({ 143 | address: "tb1pjr267uqlj79zqmv5m9vftvjp6m8fycwm8mq63497tyudq3kp3a7qqanaxk", // change address 144 | value: 546, 145 | }); 146 | 147 | psbt.addOutput({ 148 | address: "tb1pjr267uqlj79zqmv5m9vftvjp6m8fycwm8mq63497tyudq3kp3a7qqanaxk", // change address 149 | value: change, 150 | }); 151 | 152 | await signAndSend(keyPair, psbt, address as string); 153 | } 154 | 155 | export function createRunestone() { 156 | 157 | const spacedRune = SpacedRune.fromString("MOONCITY.RECURSIVE.PARENT"); 158 | 159 | const runestone = new Runestone({ 160 | edicts: [], 161 | pointer: new U32(BigInt(0)), 162 | etching: { 163 | rune: spacedRune.rune, 164 | spacers: spacedRune.spacers, 165 | premine: new U128(BigInt(10000000)), 166 | symbol: Symbol.fromString("$"), 167 | terms: { 168 | amount: new U128(BigInt(10000000)), 169 | cap: new U128(BigInt(10000000)), 170 | }, 171 | }, 172 | // mint: new RuneId(new U64(2587000n), new U32(1818n)) 173 | }); 174 | 175 | const buffer = runestone.enchiper(); 176 | return { buffer, commitBuffer: runestone.etching?.rune?.commitBuffer() }; 177 | 178 | } 179 | export async function waitUntilUTXO(address: string) { 180 | return new Promise((resolve, reject) => { 181 | let intervalId: any; 182 | const checkForUtxo = async () => { 183 | try { 184 | const response: AxiosResponse = await blockstream.get( 185 | `/address/${address}/utxo` 186 | ); 187 | const data: IUTXO[] = response.data 188 | ? JSON.parse(response.data) 189 | : undefined; 190 | console.log(data); 191 | if (data.length > 0) { 192 | resolve(data); 193 | clearInterval(intervalId); 194 | } 195 | } catch (error) { 196 | reject(error); 197 | clearInterval(intervalId); 198 | } 199 | }; 200 | intervalId = setInterval(checkForUtxo, 4000); 201 | }); 202 | } 203 | 204 | export async function getTx(id: string): Promise { 205 | const response: AxiosResponse = await blockstream.get( 206 | `/tx/${id}/hex` 207 | ); 208 | return response.data; 209 | } 210 | 211 | export async function signAndSend( 212 | keypair: BTCSigner, 213 | psbt: Psbt, 214 | address: string 215 | ) { 216 | if (process.env.NODE) { 217 | psbt.signAllInputs(keypair); 218 | psbt.finalizeAllInputs(); 219 | 220 | const tx = psbt.extractTransaction(); 221 | console.log(`Broadcasting Transaction Hex: ${tx.toHex()}`); 222 | // const txid = await broadcast(tx.toHex()); 223 | // console.log(`Success! Txid is ${txid}`); 224 | } else { 225 | // in browser 226 | 227 | try { 228 | let res = await window.unisat.signPsbt(psbt.toHex(), { 229 | toSignInputs: [ 230 | { 231 | index: 0, 232 | address: address, 233 | }, 234 | ], 235 | }); 236 | 237 | console.log("signed psbt", res); 238 | 239 | res = await window.unisat.pushPsbt(res); 240 | 241 | console.log("txid", res); 242 | } catch (e) { 243 | console.log(e); 244 | } 245 | } 246 | } 247 | 248 | export async function broadcast(txHex: string) { 249 | const response: AxiosResponse = await blockstream.post("/tx", txHex); 250 | return response.data; 251 | } 252 | 253 | function tapTweakHash(pubKey: Buffer, h: Buffer | undefined): Buffer { 254 | return crypto.taggedHash( 255 | "TapTweak", 256 | Buffer.concat(h ? [pubKey, h] : [pubKey]) 257 | ); 258 | } 259 | 260 | function toXOnly(pubkey: Buffer): Buffer { 261 | return pubkey.subarray(1, 33); 262 | } 263 | 264 | function tweakSigner(signer: BTCSigner, opts: any = {}): BTCSigner { 265 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 266 | // @ts-ignore 267 | let privateKey: Uint8Array | undefined = signer.privateKey!; 268 | if (!privateKey) { 269 | throw new Error("Private key is required for tweaking signer!"); 270 | } 271 | if (signer.publicKey[0] === 3) { 272 | privateKey = ecc.privateNegate(privateKey); 273 | } 274 | 275 | const tweakedPrivateKey = ecc.privateAdd( 276 | privateKey, 277 | tapTweakHash(toXOnly(signer.publicKey), opts.tweakHash) 278 | ); 279 | if (!tweakedPrivateKey) { 280 | throw new Error("Invalid tweaked private key!"); 281 | } 282 | 283 | return ECPair.fromPrivateKey(Buffer.from(tweakedPrivateKey), { 284 | network: opts.network, 285 | }); 286 | } 287 | 288 | interface IUTXO { 289 | txid: string; 290 | vout: number; 291 | status: { 292 | confirmed: boolean; 293 | block_height: number; 294 | block_hash: string; 295 | block_time: number; 296 | }; 297 | value: number; 298 | } 299 | 300 | 301 | // main 302 | etching(); 303 | -------------------------------------------------------------------------------- /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() -------------------------------------------------------------------------------- /Taproot_Multi-Sig.ts: -------------------------------------------------------------------------------- 1 | import * as bitcoin from "bitcoinjs-lib"; 2 | import { LEAF_VERSION_TAPSCRIPT } from "bitcoinjs-lib/src/payments/bip341"; 3 | import { toXOnly } from "bitcoinjs-lib/src/psbt/bip371"; 4 | import BIP32Factory from "bip32"; 5 | import * as ecc from "tiny-secp256k1"; 6 | import { TaprootMultisigWallet } from "utils/mutisigWallet"; 7 | 8 | const rng = require("randombytes"); 9 | const bip32 = BIP32Factory(ecc); 10 | 11 | export const createTaprootMultisig = async ( 12 | pubkeyList: string[], // Singer pubkeys list 13 | threshold: number // Number of Co-Signers 14 | ) => { 15 | try { 16 | const leafPubkeys: Buffer[] = pubkeyList.map((pubkey: string) => 17 | toXOnly(Buffer.from(pubkey, "hex")) 18 | ); 19 | 20 | const leafKey = bip32.fromSeed(rng(64), bitcoin.networks.testnet); 21 | 22 | const multiSigWallet = new TaprootMultisigWallet( 23 | leafPubkeys, 24 | threshold * 1, 25 | leafKey.privateKey!, 26 | LEAF_VERSION_TAPSCRIPT 27 | ).setNetwork(bitcoin.networks.testnet); 28 | 29 | console.log("address ==> ", multiSigWallet.address); 30 | 31 | return { 32 | success: true, 33 | message: "Create Musig Wallet successfully.", 34 | payload: { 35 | address: multiSigWallet.address, 36 | }, 37 | }; 38 | } catch (error: any) { 39 | return { 40 | success: false, 41 | message: "There is something error", 42 | payload: null, 43 | }; 44 | } 45 | }; 46 | -------------------------------------------------------------------------------- /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": "runestone-tools", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "runestone-tools", 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.16.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.16.5", 138 | "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.5.tgz", 139 | "integrity": "sha512-VwYCweNo3ERajwy0IUlqqcyZ8/A7Zwa9ZP3MnENWcB11AejO+tLy3pu850goUW2FC/IJMdZUfKpX/yxL1gymCA==", 140 | "dev": true, 141 | "dependencies": { 142 | "undici-types": "~6.19.2" 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": "6.19.8", 625 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", 626 | "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", 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.16.5", 30 | "ts-node": "^10.9.2", 31 | "typescript": "^5.3.3" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /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 | } -------------------------------------------------------------------------------- /utils/mutisigWallet.ts: -------------------------------------------------------------------------------- 1 | import * as bitcoin from "bitcoinjs-lib"; 2 | import * as ecc from "tiny-secp256k1"; 3 | import { Taptree } from "bitcoinjs-lib/src/types"; 4 | import { tapleafHash } from "bitcoinjs-lib/src/payments/bip341"; 5 | 6 | function makeUnspendableInternalKey(provableNonce?: Buffer): Buffer { 7 | // This is the generator point of secp256k1. Private key is known (equal to 1) 8 | const G = Buffer.from( 9 | "0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8", 10 | "hex" 11 | ); 12 | // This is the hash of the uncompressed generator point. 13 | // It is also a valid X value on the curve, but we don't know what the private key is. 14 | // Since we know this X value (a fake "public key") is made from a hash of a well known value, 15 | // We can prove that the internalKey is unspendable. 16 | const Hx = bitcoin.crypto.sha256(G); 17 | 18 | if (provableNonce) { 19 | if (provableNonce.length !== 32) { 20 | throw new Error( 21 | "provableNonce must be a 32 byte random value shared between script holders" 22 | ); 23 | } 24 | // Using a shared random value, we create an unspendable internalKey 25 | // P = H + int(hash_taptweak(provableNonce))*G 26 | // Since we don't know H's private key (see explanation above), we can't know P's private key 27 | const tapHash = bitcoin.crypto.taggedHash("TapTweak", provableNonce); 28 | const ret = ecc.xOnlyPointAddTweak(new Uint8Array(Hx), new Uint8Array(tapHash)); 29 | if (!ret) { 30 | throw new Error( 31 | "provableNonce produced an invalid key when tweaking the G hash" 32 | ); 33 | } 34 | return Buffer.from(ret.xOnlyPubkey); 35 | } else { 36 | // The downside to using no shared provable nonce is that anyone viewing a spend 37 | // on the blockchain can KNOW that you CAN'T use key spend. 38 | // Most people would be ok with this being public, but some wallets (exchanges etc) 39 | // might not want ANY details about how their wallet works public. 40 | return Hx; 41 | } 42 | } 43 | 44 | export class TaprootMultisigWallet { 45 | private leafScriptCache: Buffer | null = null; 46 | private internalPubkeyCache: Buffer | null = null; 47 | private paymentCache: bitcoin.Payment | null = null; 48 | private readonly publicKeyCache: Buffer; 49 | network: bitcoin.Network; 50 | 51 | constructor( 52 | /** 53 | * A list of all the (x-only) pubkeys in the multisig 54 | */ 55 | private readonly pubkeys: Buffer[], 56 | /** 57 | * The number of required signatures 58 | */ 59 | private readonly requiredSigs: number, 60 | /** 61 | * The private key you hold. 62 | */ 63 | private readonly privateKey: Buffer, 64 | /** 65 | * leaf version (0xc0 currently) 66 | */ 67 | readonly leafVersion: number, 68 | /** 69 | * Optional shared nonce. This should be used in wallets where 70 | * the fact that key-spend is unspendable should not be public, 71 | * BUT each signer must verify that it is unspendable to be safe. 72 | */ 73 | private readonly sharedNonce?: Buffer 74 | ) { 75 | this.network = bitcoin.networks.bitcoin; 76 | 77 | const pubkey = ecc.pointFromScalar(new Uint8Array(privateKey)); 78 | 79 | if (!pubkey) throw "Invalid Keys"; 80 | 81 | this.publicKeyCache = Buffer.from(pubkey); 82 | 83 | // IMPORTANT: Make sure the pubkeys are sorted (To prevent ordering issues between wallet signers) 84 | this.pubkeys.sort((a, b) => a.compare(new Uint8Array(b))); 85 | } 86 | 87 | setNetwork(network: bitcoin.Network): this { 88 | this.network = network; 89 | return this; 90 | } 91 | 92 | // Required for Signer interface. 93 | // Prevent setting by using a getter. 94 | get publicKey(): Buffer { 95 | return this.publicKeyCache; 96 | } 97 | 98 | /** 99 | * Lazily build the leafScript. A 2 of 3 would look like: 100 | * key1 OP_CHECKSIG key2 OP_CHECKSIGADD key3 OP_CHECKSIGADD OP_2 OP_GREATERTHANOREQUAL 101 | */ 102 | get leafScript(): Buffer { 103 | if (this.leafScriptCache) { 104 | return this.leafScriptCache; 105 | } 106 | const ops = []; 107 | this.pubkeys.forEach((pubkey) => { 108 | if (ops.length === 0) { 109 | ops.push(pubkey); 110 | ops.push(bitcoin.opcodes.OP_CHECKSIG); 111 | } else { 112 | ops.push(pubkey); 113 | ops.push(bitcoin.opcodes.OP_CHECKSIGADD); 114 | } 115 | }); 116 | if (this.requiredSigs > 16) { 117 | ops.push(bitcoin.script.number.encode(this.requiredSigs)); 118 | } else { 119 | ops.push(bitcoin.opcodes.OP_1 - 1 + this.requiredSigs); 120 | } 121 | ops.push(bitcoin.opcodes.OP_GREATERTHANOREQUAL); 122 | 123 | this.leafScriptCache = bitcoin.script.compile(ops); 124 | return this.leafScriptCache; 125 | } 126 | 127 | get internalPubkey(): Buffer { 128 | if (this.internalPubkeyCache) { 129 | return this.internalPubkeyCache; 130 | } 131 | // See the helper function for explanation 132 | this.internalPubkeyCache = makeUnspendableInternalKey(this.sharedNonce); 133 | return this.internalPubkeyCache; 134 | } 135 | 136 | get scriptTree(): Taptree { 137 | // If more complicated, maybe it should be cached. 138 | // (ie. if other scripts are created only to create the tree 139 | // and will only be stored in the tree.) 140 | return { 141 | output: this.leafScript, 142 | }; 143 | } 144 | 145 | get redeem(): { 146 | output: Buffer; 147 | redeemVersion: number; 148 | } { 149 | return { 150 | output: this.leafScript, 151 | redeemVersion: this.leafVersion, 152 | }; 153 | } 154 | 155 | private get payment(): bitcoin.Payment { 156 | if (this.paymentCache) { 157 | return this.paymentCache; 158 | } 159 | this.paymentCache = bitcoin.payments.p2tr({ 160 | internalPubkey: this.internalPubkey, 161 | scriptTree: this.scriptTree, 162 | redeem: this.redeem, 163 | network: this.network, 164 | }); 165 | return this.paymentCache; 166 | } 167 | 168 | get output(): Buffer { 169 | return this.payment.output!; 170 | } 171 | 172 | get address(): string { 173 | return this.payment.address!; 174 | } 175 | 176 | get controlBlock(): Buffer { 177 | const witness = this.payment.witness!; 178 | return witness[witness.length - 1]; 179 | } 180 | 181 | verifyInputScript(psbt: bitcoin.Psbt, index: number) { 182 | if (index >= psbt.data.inputs.length) 183 | throw new Error("Invalid input index"); 184 | const input = psbt.data.inputs[index]; 185 | if (!input.tapLeafScript) throw new Error("Input has no tapLeafScripts"); 186 | const hasMatch = 187 | input.tapLeafScript.length === 1 && 188 | input.tapLeafScript[0].leafVersion === this.leafVersion && 189 | input.tapLeafScript[0].script.equals(new Uint8Array(this.leafScript)) && 190 | input.tapLeafScript[0].controlBlock.equals(new Uint8Array(this.controlBlock)); 191 | if (!hasMatch) 192 | throw new Error( 193 | "No matching leafScript, or extra leaf script. Refusing to sign." 194 | ); 195 | } 196 | 197 | addInput( 198 | psbt: bitcoin.Psbt, 199 | hash: string | Buffer, 200 | index: number, 201 | value: number 202 | ) { 203 | psbt.addInput({ 204 | hash, 205 | index, 206 | witnessUtxo: { value, script: this.output }, 207 | }); 208 | psbt.updateInput(psbt.inputCount - 1, { 209 | tapLeafScript: [ 210 | { 211 | leafVersion: this.leafVersion, 212 | script: this.leafScript, 213 | controlBlock: this.controlBlock, 214 | }, 215 | ], 216 | }); 217 | } 218 | 219 | addDummySigs(psbt: bitcoin.Psbt) { 220 | const leafHash = tapleafHash({ 221 | output: this.leafScript, 222 | version: this.leafVersion, 223 | }); 224 | for (const input of psbt.data.inputs) { 225 | if (!input.tapScriptSig) continue; 226 | const signedPubkeys = input.tapScriptSig 227 | .filter((ts) => ts.leafHash.equals(new Uint8Array(leafHash))) 228 | .map((ts) => ts.pubkey); 229 | for (const pubkey of this.pubkeys) { 230 | if (signedPubkeys.some((sPub) => sPub.equals(new Uint8Array(pubkey)))) continue; 231 | // Before finalizing, every key that did not sign must have an empty signature 232 | // in place where their signature would be. 233 | // In order to do this currently we need to construct a dummy signature manually. 234 | input.tapScriptSig.push({ 235 | // This can be reused for each dummy signature 236 | leafHash, 237 | // This is the pubkey that didn't sign 238 | pubkey, 239 | // This must be an empty Buffer. 240 | signature: Buffer.from([]), 241 | }); 242 | } 243 | } 244 | } 245 | 246 | // required for Signer interface 247 | sign(hash: Buffer, _lowR?: boolean): Buffer { 248 | return Buffer.from(ecc.sign(new Uint8Array(hash), new Uint8Array(this.privateKey))); 249 | } 250 | 251 | // required for Signer interface 252 | signSchnorr(hash: Buffer): Buffer { 253 | return Buffer.from(ecc.signSchnorr(new Uint8Array(hash), new Uint8Array(this.privateKey))); 254 | } 255 | } 256 | --------------------------------------------------------------------------------