2023-03-23 00:36:51 +01:00
<!DOCTYPE html>
< html >
< head >
< meta charset = "UTF-8" >
< meta name = "viewport" content = "width=device-width, user-scalable=no" />
< link rel = "shortcut icon" type = "image/png" href = "favicon.png" >
< title > Inscribe the planet</ title >
< style >
* {
box-sizing : border-box ;
font-size : 1.15 rem ;
font-family : monospace ;
background-color : black ;
color : white ;
}
html {
max-width : 70 ch ;
padding : 3 rem 1 rem ;
margin : auto ;
line-height : 1.25 ;
}
h1 {
font-size : 2 rem ;
}
h2 {
font-size : 1.5 rem ;
}
input {
line-height : 1.25 ;
width : 100 % ;
height : 1.8 rem ;
font-size : 1.15 rem ;
border : 1 px solid white ;
}
. black-bg {
display : none ;
width : 100 % ;
position : fixed ;
top : 0 ;
left : 0 ;
background-color : black ;
opacity : .5 ;
width : 100 vw ;
height : 100 vh ;
}
. modal {
2023-03-28 03:52:32 +02:00
display : none ;
2023-03-23 00:36:51 +01:00
position : fixed ;
box-sizing : border-box ;
top : 50 % ;
left : 50 % ;
transform : translate ( -50 % , -50 % );
width : 100 % ;
max-width : 560 px ;
background-color : white ;
2023-03-28 03:52:32 +02:00
padding : 20 px ;
color : black ;
text-align : center ;
}
. modal-content {
overflow-y : auto ;
max-height : 90 vh ;
background-color : white ;
2023-03-23 00:36:51 +01:00
color : black ;
text-align : center ;
}
. display {
display : none ;
border : 1 px solid white ;
padding : 1 rem ;
border-radius : 1 rem ;
word-wrap : break-word ;
text-align : center ;
position : relative ;
}
. display p {
text-align : left ;
}
. checking_mempool , . checking_mempool span {
color : lightgreen ;
}
. feerates {
display : flex ;
justify-content : space-around ;
}
. fee {
width : 100 % ;
max-width : 6 rem ;
margin : 0 px 0.5 rem ;
text-align : center ;
cursor : pointer ;
}
. fee . num {
background-color : grey ;
border : 1 px solid white ;
padding : .3 rem ;
}
. minfee . num {
background-color : green ;
}
. safari_warning {
display : none ;
}
. registration , . upload_file {
text-decoration : underline ;
cursor : pointer ;
}
. dns_form , . dns_checker , . timer {
display : none ;
}
@ media screen and ( max-width : 600px ) {
}
</ style >
2023-03-28 03:52:32 +02:00
< script src = "https://bundle.run/buffer@6.0.3" ></ script >
< script > const buf = buffer </ script >
2023-03-23 17:03:42 +01:00
< script src = "bton.min.js" ></ script >
2023-03-23 00:36:51 +01:00
< script > const BTON = window . bton </ script >
< script src = "https://unpkg.com/@cmdcode/crypto-utils@1.5.11" ></ script >
< script src = "https://bundle.run/bech32@2.0.0" ></ script >
< script src = "https://supertestnet.github.io/bitcoin-chess/js/qrcode.js" ></ script >
< script >
2023-03-28 03:52:32 +02:00
let $ = document . querySelector . bind ( document );
let $$ = document . querySelectorAll . bind ( document );
let url_params = new URLSearchParams ( window . location . search );
let url_keys = url_params . keys ();
let $_GET = {}
for ( let key of url_keys ) $_GET [ key ] = url_params . get ( key );
2023-03-23 00:36:51 +01:00
</ script >
< script >
function encodeBase64 ( file ) {
return new Promise ( function ( resolve , reject ) {
2023-03-28 03:52:32 +02:00
let imgReader = new FileReader ();
2023-03-23 00:36:51 +01:00
imgReader . onloadend = function () {
resolve ( imgReader . result . toString () );
}
imgReader . readAsDataURL ( file );
});
}
function base64ToHex ( str ) {
const raw = atob ( str );
let result = '' ;
for ( let i = 0 ; i < raw . length ; i ++ ) {
const hex = raw . charCodeAt ( i ). toString ( 16 );
result += ( hex . length === 2 ? hex : '0' + hex );
}
return result . toLowerCase ();
}
function hexToBytes ( hex ) {
return Uint8Array . from ( hex . match ( /.{1,2}/g ). map ( ( byte ) => parseInt ( byte , 16 ) ) );
}
function bytesToHex ( bytes ) {
return bytes . reduce ( ( str , byte ) => str + byte . toString ( 16 ). padStart ( 2 , "0" ), "" );
}
function textToHex ( text ) {
var encoder = new TextEncoder (). encode ( text );
return [... new Uint8Array ( encoder )]
. map ( x => x . toString ( 16 ). padStart ( 2 , "0" ) )
. join ( "" );
}
function createQR ( content ) {
2023-03-28 03:52:32 +02:00
let dataUriPngImage = document . createElement ( "img" ),
2023-03-23 00:36:51 +01:00
s = QRCode . generatePNG ( content , {
ecclevel : "M" ,
format : "html" ,
fillcolor : "#FFFFFF" ,
textcolor : "#000000" ,
margin : 4 ,
modulesize : 8 ,
});
dataUriPngImage . src = s ;
dataUriPngImage . id = "qr_code" ;
return dataUriPngImage ;
}
async function getBitcoinPriceFromCoinbase () {
2023-03-28 03:52:32 +02:00
let data = await getData ( "https://api.coinbase.com/v2/prices/BTC-USD/spot" );
let json = JSON . parse ( data );
let price = json [ "data" ][ "amount" ];
return price ;
2023-03-23 00:36:51 +01:00
}
async function getBitcoinPriceFromKraken () {
2023-03-28 03:52:32 +02:00
let data = await getData ( "https://api.kraken.com/0/public/Ticker?pair=XBTUSD" );
let json = JSON . parse ( data );
let price = json [ "result" ][ "XXBTZUSD" ][ "a" ][ 0 ];
return price ;
2023-03-23 00:36:51 +01:00
}
async function getBitcoinPriceFromCoindesk () {
2023-03-28 03:52:32 +02:00
let data = await getData ( "https://api.coindesk.com/v1/bpi/currentprice.json" );
let json = JSON . parse ( data );
let price = json [ "bpi" ][ "USD" ][ "rate_float" ];
return price ;
2023-03-23 00:36:51 +01:00
}
async function getBitcoinPriceFromGemini () {
2023-03-28 03:52:32 +02:00
let data = await getData ( "https://api.gemini.com/v2/ticker/BTCUSD" );
let json = JSON . parse ( data );
let price = json [ "bid" ];
return price ;
2023-03-23 00:36:51 +01:00
}
async function getBitcoinPriceFromBybit () {
2023-03-28 03:52:32 +02:00
let data = await getData ( "https://api-testnet.bybit.com/derivatives/v3/public/order-book/L2?category=linear&symbol=BTCUSDT" );
let json = JSON . parse ( data );
let price = json [ "result" ][ "b" ][ 0 ][ 0 ];
return price ;
2023-03-23 00:36:51 +01:00
}
async function getBitcoinPrice () {
2023-03-28 03:52:32 +02:00
let prices = [];
let cbprice = await getBitcoinPriceFromCoinbase ();
let kprice = await getBitcoinPriceFromKraken ();
let cdprice = await getBitcoinPriceFromCoindesk ();
let gprice = await getBitcoinPriceFromGemini ();
let bprice = await getBitcoinPriceFromBybit ();
2023-03-23 00:36:51 +01:00
prices . push ( Number ( cbprice ), Number ( kprice ), Number ( cdprice ), Number ( gprice ), Number ( bprice ) );
prices . sort ();
return prices [ 2 ];
}
</ script >
</ head >
< body >
2023-03-28 03:52:32 +02:00
< h1 style = "margin-bottom: 0" > Inscribe the Planet</ h1 >
< small style = "font-size: 13px;" > Trustless Inscriptions - No Service Fees</ small >
< hr />
2023-03-23 00:36:51 +01:00
< h2 class = "safari_warning" > This tool may not work in safari browser. If you are using that, consider switching.</ h2 >
2023-03-28 03:52:32 +02:00
< div id = "setup" >
< p > Receiving address < span class = "type_of_address" > (taproot)</ span ></ p >
< p >< input class = "address" placeholder = "Enter a taproot address from an ordinals wallet" ></ p >
< p class = "instructions" > Upload files or < span class = "registration" > click here</ span > to register sats domains</ p > < form id = "app-form" >
< p class = "file_form" >
< input
accept = "
.json,
.pdf,
.asc,
.yaml,
.yml,
.flac,
.mp3,
.wav,
.apng,
.avif,
.gif,
.jpg,
.jpeg,
.png,
.svg,
.webp,
.glb,
.stl,
.html,
.txt,
.mp4,
.webm
" type = "file" class = "form" multiple >
< button style = "margin-top: 15px;" id = "bytes_checker" type = "button" > Check if bytes are inscribed already</ button >
</ p >
< p class = "dns_form" >< textarea class = "dns" style = "width: 100%; height: 250px;" placeholder = "One domain per line. Example: ordi.sats btc.sats" ></ textarea ></ p >
< p >< button class = "dns_checker" type = "button" > Check availability</ button > < span class = "timer" > 61</ span ></ p >
< p > Select a feerate (sats/vB)</ p >
< div class = "feerates" >< div class = "fee minfee" >< div class = "num" > ...</ div >< div class = "name" > Min</ div ></ div >< div class = "fee midfee" >< div class = "num" > ...</ div >< div class = "name" > Mid</ div ></ div >< div class = "fee maxfee" >< div class = "num" > ...</ div >< div class = "name" > Max</ div ></ div ></ div >
< div id = "sliderange" >
< p > sats/vB: < span id = "sats_per_byte" > 1</ span ></ p >
< input id = "sats_range" type = "range" min = "1" max = "100" value = "1" >
</ div >
< script >
var slider = document . getElementById ( "sats_range" );
var output = document . getElementById ( "sats_per_byte" );
output . innerHTML = slider . value ;
slider . oninput = function () {
output . innerHTML = this . value ;
sessionStorage [ "feerate" ] = this . value ;
$$ ( '.fee .num' ). forEach ( function ( item ) {
item . style . backgroundColor = "grey" ;
});
}
</ script >
</ form >
< div >
< label style = "display: inline-block" for = "padding" > Padding</ label > < input style = "display: inline-block; width: 200px;" id = "padding" type = "text" value = "" />
2023-03-23 00:36:51 +01:00
</ div >
2023-03-28 03:52:32 +02:00
< hr />
</ div >
< p >< button class = "submit" > Submit</ button >< button style = "display:none;" class = "startover" > Reset</ button ></ p >
2023-03-23 00:36:51 +01:00
< div class = "display" ></ div >
< div style = "display: none;" class = "file" ></ div >
< script >
2023-03-28 03:52:32 +02:00
// adjust for your needs
let padding = 10000; // default padding as of ord native wallet
let encodedAddressPrefix = 'tb'; // mainnet: 'bc', signet/testnet: 'tb'
let mempoolNetwork = 'signet/'; // mainnet: '', 'signet/', 'testnet/'
// no changes from here
2023-03-23 00:36:51 +01:00
const privkey = bytesToHex( cryptoUtils.Noble.utils.randomPrivateKey() );
2023-03-28 03:52:32 +02:00
let pushing = false;
let files = [];
sessionStorage.clear();
window.onload = function(){
$('#padding').value = padding;
};
2023-03-23 00:36:51 +01:00
function showRegister() {
2023-03-28 03:52:32 +02:00
files = [];
$('#app-form').reset();
2023-03-23 00:36:51 +01:00
$( '.file_form' ).style.display = "none";
$( '.dns_form' ).style.display = "block";
$( '.dns_checker' ).style.display = "inline";
$( '.dns' ).value = "";
2023-03-28 03:52:32 +02:00
$( '.instructions' ).innerHTML = 'Enter domain names or < span class = "upload_file" > click here</ span > to upload files';
2023-03-23 00:36:51 +01:00
$( '.upload_file' ).onclick = showUploader;
}
function showUploader() {
2023-03-28 03:52:32 +02:00
files = [];
$('#app-form').reset();
2023-03-23 00:36:51 +01:00
$( '.file_form' ).style.display = "block";
$( '.dns_form' ).style.display = "none";
$( '.dns_checker' ).style.display = "none";
2023-03-28 03:52:32 +02:00
$( '.instructions' ).innerHTML = 'Upload files or < span class = "registration" > click here</ span > to register sats domains';
2023-03-23 00:36:51 +01:00
$( '.registration' ).onclick = showRegister;
}
showUploader();
2023-03-23 17:03:42 +01:00
$( '.form' ).addEventListener( "change", async function() {
2023-03-28 03:52:32 +02:00
files = [];
let limit_reached = 0;
2023-03-23 17:03:42 +01:00
for(let i = 0; i < this.files.length ; i ++)
{
2023-03-28 03:52:32 +02:00
let b64 ;
let mimetype = this.files[i].type;
2023-03-23 17:03:42 +01:00
if ( mimetype . includes ( " text / plain " ) ) {
mimetype += "; charset = utf-8";
}
2023-03-28 03:52:32 +02:00
if ( this . files [ i ]. size > = 350000) {
2023-03-23 17:03:42 +01:00
2023-03-28 03:52:32 +02:00
limit_reached += 1;
2023-03-23 17:03:42 +01:00
} else {
b64 = await encodeBase64( this.files[i] );
2023-03-28 03:52:32 +02:00
let base64 = b64.substring( b64.indexOf( "base64," ) + 7 );
let hex = base64ToHex( base64 );
//console.log( "hex:", hex );
//console.log( "bytes:", hexToBytes( hex ) );
let sha256 = await fileToSha256Hex(this.files[i]);
files.push({name: this.files[i].name, hex: hex, mimetype : mimetype, sha256: sha256.replace('0x','')});
2023-03-23 17:03:42 +01:00
}
}
2023-03-28 03:52:32 +02:00
if(limit_reached != 0)
{
alert( limit_reached + " of your desired inscriptions exceed(s) the maximum of 350kb." )
}
console.log(files);
});
$( '.startover' ).addEventListener( "click", async function() {
location.reload();
2023-03-23 17:03:42 +01:00
});
2023-03-23 00:36:51 +01:00
$( '.submit' ).addEventListener( "click", async function() {
2023-03-28 03:52:32 +02:00
if(!isValidAddress())
{
alert('Invalid taproot address.');
return;
}
2023-03-23 00:36:51 +01:00
if ( $( '.file_form' ).style.display == "none" ) {
2023-03-23 17:03:42 +01:00
let sats_domains = $( '.dns' ).value.split("\n");
2023-03-28 03:52:32 +02:00
let sats_domains_cleaned = [];
2023-03-23 00:36:51 +01:00
2023-03-28 03:52:32 +02:00
for(let sats_domain in sats_domains) {
let domain = sats_domains[sats_domain].trim();
if (domain == '' || sats_domains_cleaned.includes(domain)) {
continue;
}
sats_domains_cleaned.push(domain);
}
for(let sats_domain in sats_domains_cleaned)
2023-03-23 17:03:42 +01:00
{
2023-03-28 03:52:32 +02:00
2023-03-23 17:03:42 +01:00
let mimetype = "text/plain;charset=utf-8";
2023-03-28 03:52:32 +02:00
let domain = {"p":"sns","op":"reg","name":sats_domains_cleaned[sats_domain].trim()};
let sha256 = await fileToSha256Hex(JSON.stringify( domain ));
files.push({name : sats_domains_cleaned[sats_domain].trim(),hex: textToHex( JSON.stringify( domain ) ), mimetype : mimetype, sha256: sha256.replace('0x','')});
2023-03-23 17:03:42 +01:00
console.log(domain);
}
}
2023-03-23 00:36:51 +01:00
2023-03-28 03:52:32 +02:00
if(files.length == 0)
{
alert('Nothing to inscribe. Please upload some files or use one of the additional options.');
return;
}
let _padding = parseInt($('#padding').value);
if(!isNaN(_padding) && _padding < = Number.MAX_SAFE_INTEGER && _padding >= 1)
{
padding = _padding;
}
else
{
alert('Invalid padding. Please enter a sats amount for each inscription.');
return;
}
$( '.submit' ).style.display = "none";
$( '.startover' ).style.display = "block";
2023-03-23 00:36:51 +01:00
const KeyPair = cryptoUtils.KeyPair;
const seckey = new KeyPair(privkey);
const pubkey = seckey.pub.rawX;
2023-03-23 17:03:42 +01:00
const ec = new TextEncoder();
2023-03-23 00:36:51 +01:00
2023-03-23 17:03:42 +01:00
const init_script = [
2023-03-23 00:36:51 +01:00
pubkey,
'OP_CHECKSIG'
];
const init_leaf = await BTON.Tap.getLeaf(BTON.Script.encode(init_script));
const [ init_tapkey ] = await BTON.Tap.getPubkey(pubkey, [ init_leaf ]);
const init_cblock = await BTON.Tap.getPath(pubkey, init_leaf);
2023-03-23 17:03:42 +01:00
let inscriptions = [];
let total_fee = 0;
2023-03-23 00:36:51 +01:00
2023-03-28 03:52:32 +02:00
let feerate = await getMinFeeRate();
sessionStorage[ "determined_feerate" ] = sessionStorage[ "feerate" ];
if ( sessionStorage[ "feerate" ] ) {
feerate = Number( sessionStorage[ "feerate" ] );
sessionStorage[ "determined_feerate" ] = sessionStorage[ "feerate" ];
}
2023-03-23 17:03:42 +01:00
for(let i = 0; i < files.length ; i ++)
{
2023-03-28 03:52:32 +02:00
2023-03-23 17:03:42 +01:00
const hex = files[i].hex;
const data = hexToBytes( hex );
const mimetype = ec.encode( files [ i ]. mimetype );
2023-03-23 00:36:51 +01:00
2023-03-23 17:03:42 +01:00
const script = [
pubkey ,
' OP_CHECKSIG ',
' OP_0 ',
' OP_IF ',
ec . encode (' ord '),
' 01 ',
mimetype ,
' OP_0 ',
data ,
' OP_ENDIF '
];
const leaf = await BTON . Tap . getLeaf ( BTON . Script . encode ( script ));
const [ tapkey ] = await BTON . Tap . getPubkey ( pubkey , [ leaf ]);
const cblock = await BTON . Tap . getPath ( pubkey , leaf );
2023-03-28 03:52:32 +02:00
let inscriptionAddress = BTON.Tap.encodeAddress( tapkey , encodedAddressPrefix );
2023-03-23 17:03:42 +01:00
console . log (' Inscription address: ', inscriptionAddress );
console . log (' Tapkey: ', tapkey );
2023-03-28 03:52:32 +02:00
let txsize = 500 + Math . floor ( data . length / 4 );
2023-03-23 17:03:42 +01:00
2023-03-28 03:52:32 +02:00
let fee = feerate * txsize ;
2023-03-23 17:03:42 +01:00
total_fee += fee ;
inscriptions . push (
{
leaf: leaf ,
tapkey : tapkey ,
cblock : cblock ,
inscriptionAddress : inscriptionAddress ,
txsize : txsize ,
fee : fee ,
script : script
}
);
}
2023-03-23 00:36:51 +01:00
2023-03-28 03:52:32 +02:00
let fundingAddress = BTON.Tap.encodeAddress( init_tapkey , encodedAddressPrefix );
2023-03-23 00:36:51 +01:00
console . log (' Funding address: ', fundingAddress );
2023-03-28 03:52:32 +02:00
let toAddress = $( '. address ' ). value ;
2023-03-23 00:36:51 +01:00
console . log (' Address that will receive the inscription: ', toAddress );
2023-03-28 03:52:32 +02:00
let decodedToAddress = "5120" + BTON . Tap . decodeAddress ( toAddress ). hex ;
2023-03-23 00:36:51 +01:00
console . log (' To address decoded: ', decodedToAddress );
2023-03-28 03:52:32 +02:00
let total_fees = ( 500 * feerate ) + total_fee + ( padding * inscriptions . length );
let sats_price = await satsToDollars ( total_fees );
2023-03-23 00:36:51 +01:00
sats_price = Math.floor( sats_price * 100 ) / 100 ;
2023-03-28 03:52:32 +02:00
let html = `<p > Please send at least ${total_fees} sats ($${sats_price}) to the address below (click to copy). Once you sent the amount, do NOT close this window!</ p >< p >< input readonly = "readonly" onclick = "copyFundingAddress()" id = "fundingAddress" type = "text" value = "${fundingAddress}" style = "width: 80%;" /> < span id = "fundingAddressCopied" ></ span ></ p > `;
2023-03-23 00:36:51 +01:00
$( '.display' ).innerHTML = html;
2023-03-28 03:52:32 +02:00
let qr_value = "bitcoin:" + fundingAddress + "?amount=" + satsToBitcoin( total_fees );
2023-03-23 00:36:51 +01:00
console.log( "qr:", qr_value );
$( '.display' ).append( createQR( qr_value ) );
$( '.display' ).innerHTML += `< p class = "checking_mempool" > Checking the mempool< span class = "dots" > .</ span ></ p > `;
2023-03-28 03:52:32 +02:00
$( '.display' ).innerHTML += '< p > ' + ( padding * inscriptions.length ) + ` sats will go to the address</ p >< p > ${total_fee} sats will go to miners as a mining fee</ p > `;
2023-03-23 00:36:51 +01:00
$( '.display' ).style.display = "block";
2023-03-28 03:52:32 +02:00
$('#setup').style.display = "none";
2023-03-23 00:36:51 +01:00
await loopTilAddressReceivesMoney( fundingAddress, true );
await waitSomeSeconds( 2 );
2023-03-28 03:52:32 +02:00
let txinfo = await addressReceivedMoneyInThisTx( fundingAddress );
2023-03-23 00:36:51 +01:00
2023-03-28 03:52:32 +02:00
let txid = txinfo[ 0 ];
let vout = txinfo[ 1 ];
let amt = txinfo[ 2 ];
2023-03-23 00:36:51 +01:00
console.log( "yay! txid:", txid, "vout:", vout, "amount:", amt );
2023-03-28 03:52:32 +02:00
$('.modal-content').innerHTML = '< span id = "funds-msg" > Funds are on the way, please wait...</ span > ';
$( '.modal' ).style.display = "block";
2023-03-23 17:03:42 +01:00
let outputs = [];
for(let i = 0; i < inscriptions.length ; i ++)
{
outputs . push (
{
2023-03-28 03:52:32 +02:00
value: padding + inscriptions [ i ]. fee ,
2023-03-23 17:03:42 +01:00
scriptPubKey: ' 5120 ' + inscriptions [ i ]. tapkey
}
);
2023-03-23 00:36:51 +01:00
}
const init_redeemtx = {
version: 2 ,
input: [{
txid: txid ,
vout: vout ,
prevout: { value: amt , scriptPubKey: ' 5120 ' + init_tapkey },
witness: []
}],
2023-03-23 17:03:42 +01:00
output: outputs ,
2023-03-23 00:36:51 +01:00
locktime: 0
};
const init_sig = await BTON . Sig . taproot . sign ( seckey . raw , init_redeemtx , 0 , { extention: init_leaf });
init_redeemtx . input [ 0 ]. witness = [ init_sig , init_script , init_cblock ];
console . dir ( init_redeemtx , { depth: null });
2023-03-28 03:52:32 +02:00
let rawtx = BTON.Tx.encode(init_redeemtx);
let _txid = await pushBTCpmt ( rawtx );
2023-03-23 00:36:51 +01:00
2023-03-28 03:52:32 +02:00
console . log (' Init TX ', _txid );
2023-03-23 00:36:51 +01:00
2023-03-24 04:01:29 +01:00
async function inscribe ( inscription , vout )
2023-03-23 17:03:42 +01:00
{
2023-03-23 00:36:51 +01:00
2023-03-23 17:03:42 +01:00
await loopTilAddressReceivesMoney ( inscription . inscriptionAddress , false );
await waitSomeSeconds ( 2 );
2023-03-28 03:52:32 +02:00
let txinfo2 = await addressReceivedMoneyInThisTx ( inscription . inscriptionAddress );
2023-03-23 00:36:51 +01:00
2023-03-28 03:52:32 +02:00
document . getElementById (' modal-reset '). style . display = 'block' ;
document . getElementById (' funds-msg '). style . display = 'none' ;
let txid2 = txinfo2[ 0 ];
let amt2 = txinfo2[ 2 ];
2023-03-23 00:36:51 +01:00
2023-03-23 17:03:42 +01:00
const redeemtx = {
version: 2 ,
input: [{
txid: txid2 ,
2023-03-24 04:01:29 +01:00
vout: vout ,
2023-03-23 17:03:42 +01:00
prevout: { value: amt2 , scriptPubKey: ' 5120 ' + inscription . tapkey },
witness: []
}],
output: [{
value: amt2 - inscription . fee ,
scriptPubKey: decodedToAddress
}],
locktime: 0
};
2023-03-23 00:36:51 +01:00
2023-03-23 17:03:42 +01:00
const sig = await BTON . Sig . taproot . sign ( seckey . raw , redeemtx , 0 , { extention: inscription . leaf });
redeemtx . input [ 0 ]. witness = [ sig , inscription . script , inscription . cblock ];
2023-03-23 00:36:51 +01:00
2023-03-23 17:03:42 +01:00
console . dir ( redeemtx , { depth: null });
2023-03-23 00:36:51 +01:00
2023-03-28 03:52:32 +02:00
let rawtx2 = BTON.Tx.encode(redeemtx);
2023-03-24 04:01:29 +01:00
// since we don ' t know any mempool space api rate limits , we will be careful with spamming
await isPushing ();
pushing = true;
2023-03-28 03:52:32 +02:00
let _txid2 = await pushBTCpmt ( rawtx2 );
2023-03-24 04:01:29 +01:00
await sleep ( 1000 );
pushing = false;
2023-03-23 00:36:51 +01:00
2023-03-28 03:52:32 +02:00
try
{
JSON . parse ( _txid2 );
let html = `<p style = "background-color: white; color: black;" > Error: ${_txid2}</ p > `;
html += '< hr /> ';
$( '.modal' ).innerHTML += html;
}
catch (e)
{
let html = `< p style = "background-color: white; color: black;" > Inscription #${vout} transaction:</ p >< p style = "word-wrap: break-word;" >< a href = "https://mempool.space/${mempoolNetwork}tx/${_txid2}" target = "_blank" > https://mempool.space/${mempoolNetwork}tx/${_txid2}</ a ></ p > `;
html += `< p style = "background-color: white; color: black;" > Ordinals explorer (after tx confirmation):</ p >< p style = "word-wrap: break-word;" >< a href = "https://ordinals.com/inscription/${_txid2}i0" target = "_blank" > https://ordinals.com/inscription/${_txid2}i0</ a ></ p > `;
html += '< hr /> ';
$( '.modal-content' ).innerHTML += html;
}
2023-03-23 00:36:51 +01:00
2023-03-23 17:03:42 +01:00
$( '.modal' ).style.display = "block";
$( '.black-bg' ).style.display = "block";
}
for(let i = 0; i < inscriptions.length ; i ++)
{
2023-03-24 04:01:29 +01:00
inscribe ( inscriptions [ i ], i );
2023-03-23 17:03:42 +01:00
}
2023-03-23 00:36:51 +01:00
});
2023-03-28 03:52:32 +02:00
function arrayBufferToBuffer ( ab ) {
var buffer = new buf . Buffer ( ab . byteLength )
var view = new Uint8Array ( ab )
for ( var i = 0; i < buffer . length ; ++ i ) {
buffer [ i ] = view [ i ]
}
return buffer
}
function hexString ( buffer ) {
const byteArray = new Uint8Array ( buffer )
const hexCodes = [...byteArray].map(value = > {
return value.toString(16).padStart(2, '0')
})
return '0x'+hexCodes.join('')
}
async function fileToArrayBuffer (file) {
return new Promise(function (resolve, reject) {
const reader = new FileReader()
const readFile = function(event) {
const buffer = reader.result
resolve(buffer)
}
reader.addEventListener('load', readFile)
reader.readAsArrayBuffer(file)
})
}
async function bufferToSha256 (buffer) {
return window.crypto.subtle.digest('SHA-256', buffer)
}
async function fileToSha256Hex (file) {
const buffer = await fileToArrayBuffer(file)
const hash = await bufferToSha256(arrayBufferToBuffer(buffer))
return hexString(hash)
}
function copyFundingAddress() {
let copyText = document.getElementById("fundingAddress");
copyText.select();
copyText.setSelectionRange(0, 99999);
navigator.clipboard.writeText(copyText.value);
document.getElementById("fundingAddressCopied").innerHTML = ' Copied!';
setTimeout(function(){
document.getElementById("fundingAddressCopied").innerHTML = '';
}, 5000);
}
2023-03-24 04:01:29 +01:00
async function isPushing()
{
while(pushing)
{
await sleep(10);
}
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
2023-03-23 00:36:51 +01:00
async function getMaxFeeRate() {
2023-03-28 03:52:32 +02:00
let fees = await getData( "https://mempool.space/"+mempoolNetwork+"api/v1/fees/recommended" );
2023-03-23 00:36:51 +01:00
fees = JSON.parse( fees );
// if ( !( "minimumFee" in fees ) ) return "error -- site down";
// var minfee = fees[ "minimumFee" ];
if ( !( "fastestFee" in fees ) ) return "error -- site down";
2023-03-28 03:52:32 +02:00
let maxfee = fees[ "fastestFee" ];
2023-03-23 00:36:51 +01:00
return maxfee;
}
async function getMinFeeRate() {
2023-03-28 03:52:32 +02:00
let fees = await getData( "https://mempool.space/"+mempoolNetwork+"api/v1/fees/recommended" );
2023-03-23 00:36:51 +01:00
fees = JSON.parse( fees );
if ( !( "minimumFee" in fees ) ) return "error -- site down";
2023-03-28 03:52:32 +02:00
let minfee = fees[ "minimumFee" ];
2023-03-23 00:36:51 +01:00
// if ( !( "fastestFee" in fees ) ) return "error -- site down";
// var maxfee = fees[ "fastestFee" ];
return minfee;
}
function isValidTaprootAddress( address ) {
try {
BTON.Tap.decodeAddress( address ).hex;
return true;
} catch( e ) {}
return;
}
function isValidJson( content ) {
if ( !content ) return;
try {
var json = JSON.parse( content );
} catch ( e ) {
return;
}
return true;
}
async function getAllFeeRates() {
2023-03-28 03:52:32 +02:00
let fees = await getData( "https://mempool.space/"+mempoolNetwork+"api/v1/fees/recommended" );
2023-03-23 00:36:51 +01:00
fees = JSON.parse( fees );
return fees;
}
function getData( url ) {
return new Promise( async function( resolve, reject ) {
function inner_get( url ) {
2023-03-28 03:52:32 +02:00
let xhttp = new XMLHttpRequest();
2023-03-23 00:36:51 +01:00
xhttp.open( "GET", url, true );
xhttp.send();
return xhttp;
}
2023-03-28 03:52:32 +02:00
let data = inner_get( url );
2023-03-23 00:36:51 +01:00
data.onerror = function( e ) {
resolve( "error" );
}
async function isResponseReady() {
return new Promise( function( resolve2, reject ) {
if ( !data.responseText || data.readyState != 4 ) {
setTimeout( async function() {
2023-03-28 03:52:32 +02:00
let msg = await isResponseReady();
2023-03-23 00:36:51 +01:00
resolve2( msg );
}, 1 );
} else {
resolve2( data.responseText );
}
});
}
2023-03-28 03:52:32 +02:00
let returnable = await isResponseReady();
2023-03-23 00:36:51 +01:00
resolve( returnable );
});
}
async function pushBTCpmt( rawtx ) {
2023-03-28 03:52:32 +02:00
let txid = await postData( "https://mempool.space/"+mempoolNetwork+"api/tx", rawtx );
2023-03-23 00:36:51 +01:00
return txid;
}
2023-03-28 03:52:32 +02:00
function waitSomeSeconds( number ) {
let num = number.toString() + "000";
2023-03-23 00:36:51 +01:00
num = Number( num );
return new Promise( function( resolve, reject ) {
setTimeout( function() { resolve( "" ); }, num );
});
}
async function postData( url, json, content_type = "", apikey = "" ) {
2023-03-28 03:52:32 +02:00
let rtext = "";
2023-03-23 00:36:51 +01:00
function inner_post( url, json, content_type = "", apikey = "" ) {
2023-03-28 03:52:32 +02:00
let xhttp = new XMLHttpRequest();
2023-03-23 00:36:51 +01:00
xhttp.open( "POST", url, true );
if ( content_type ) {
xhttp.setRequestHeader( `Content-Type`, content_type );
}
if ( apikey ) {
xhttp.setRequestHeader( `X-Api-Key`, apikey );
}
xhttp.send( json );
return xhttp;
}
2023-03-28 03:52:32 +02:00
let data = inner_post( url, json, content_type, apikey );
2023-03-23 00:36:51 +01:00
data.onerror = function( e ) {
rtext = "error";
}
async function isResponseReady() {
return new Promise( function( resolve, reject ) {
if ( rtext == "error" ) {
resolve( rtext );
}
if ( !data.responseText || data.readyState != 4 ) {
setTimeout( async function() {
2023-03-28 03:52:32 +02:00
let msg = await isResponseReady();
2023-03-23 00:36:51 +01:00
resolve( msg );
}, 50 );
} else {
resolve( data.responseText );
}
});
}
2023-03-28 03:52:32 +02:00
let returnable = await isResponseReady();
2023-03-23 00:36:51 +01:00
return returnable;
}
async function loopTilAddressReceivesMoney( address, includeMempool ) {
2023-03-28 03:52:32 +02:00
let itReceivedMoney = false;
2023-03-23 00:36:51 +01:00
async function isDataSetYet( data_i_seek ) {
return new Promise( function( resolve, reject ) {
if ( !data_i_seek ) {
setTimeout( async function() {
console.log( "waiting for address to receive money..." );
itReceivedMoney = await addressOnceHadMoney( address, includeMempool );
2023-03-28 03:52:32 +02:00
let msg = await isDataSetYet( itReceivedMoney );
2023-03-23 00:36:51 +01:00
resolve( msg );
}, 2000 );
} else {
resolve( data_i_seek );
}
});
}
async function getTimeoutData() {
2023-03-28 03:52:32 +02:00
let data_i_seek = await isDataSetYet( itReceivedMoney );
2023-03-23 00:36:51 +01:00
return data_i_seek;
}
2023-03-28 03:52:32 +02:00
let returnable = await getTimeoutData();
2023-03-23 00:36:51 +01:00
return returnable;
}
async function addressReceivedMoneyInThisTx( address ) {
2023-03-28 03:52:32 +02:00
let txid;
let vout;
let amt;
let nonjson = await getData( "https://mempool.space/"+mempoolNetwork+"api/address/" + address + "/txs" );
let json = JSON.parse( nonjson );
2023-03-23 00:36:51 +01:00
json.forEach( function( tx ) {
tx[ "vout" ].forEach( function( output, index ) {
if ( output[ "scriptpubkey_address" ] == address ) {
txid = tx[ "txid" ];
vout = index;
amt = output[ "value" ];
}
});
});
return [txid, vout, amt];
}
async function addressOnceHadMoney( address, includeMempool ) {
2023-03-28 03:52:32 +02:00
let url = "https://mempool.space/"+mempoolNetwork+"api/address/" + address;
let nonjson = await getData( url );
2023-03-23 00:36:51 +01:00
if ( !isValidJson( nonjson ) ) return false;
2023-03-28 03:52:32 +02:00
let json = JSON.parse( nonjson );
2023-03-23 00:36:51 +01:00
if ( json[ "chain_stats" ][ "tx_count" ] > 0 || ( includeMempool && json[ "mempool_stats" ][ "tx_count" ] > 0 ) ) {
return true;
}
return false;
}
function dotLoop( string ) {
if ( !$( '.dots' ) ) {
setTimeout( function() {dotLoop( string );}, 1000 );
return;
}
if ( string.length < 3 ) {
string = string + ".";
} else {
string = "." ;
}
$( '. dots ' ). innerText = string;
setTimeout ( function () { dotLoop ( string );}, 1000 );
}
dotLoop ( "." );
function timer ( num ) {
if ( ! num ) {
$( '. timer ' ). style . display = "none" ;
return ;
}
num = num - 1 ;
$( '. timer ' ). innerText = num;
setTimeout ( function () { timer ( num );}, 1000 );
}
function satsToBitcoin ( sats ) {
if ( sats > = 100000000 ) sats = sats * 10;
2023-03-28 03:52:32 +02:00
let string = String( sats ).padStart( 8, "0" ).slice( 0,-9 ) + "." + String( sats ).padStart( 8, "0" ).slice( -9 );
2023-03-23 00:36:51 +01:00
if ( string.substring( 0, 1 ) == "." ) string = "0" + string;
return string;
}
async function satsToDollars( sats ) {
if ( sats >= 100000000 ) sats = sats * 10;
2023-03-28 03:52:32 +02:00
let bitcoin_price = sessionStorage[ "bitcoin_price" ];
let value_in_dollars = Number( String( sats ).padStart( 8, "0" ).slice( 0,-9 ) + "." + String( sats ).padStart( 8, "0" ).slice( -9 ) ) * bitcoin_price;
2023-03-23 00:36:51 +01:00
return value_in_dollars;
}
function modalVanish() {
$( ".black-bg" ).style.display = "none";
$( ".modal" ).style.display = "none";
}
$$( '.fee' ).forEach( function( item ) {
item.onclick = function() {
$$( '.fee .num' ).forEach( function( item2 ) {
item2.style.backgroundColor = "grey";
});
this.getElementsByClassName( "num" )[ 0 ].style.backgroundColor = "green";
sessionStorage[ "feerate" ] = this.getElementsByClassName( "num" )[ 0 ].innerText;
$( '#sats_per_byte' ).innerText = Number( this.getElementsByClassName( "num" )[ 0 ].innerText );
$( '#sats_range' ).value = Number( this.getElementsByClassName( "num" )[ 0 ].innerText );
}
});
2023-03-28 03:52:32 +02:00
function isValidAddress() {
if ( !isValidTaprootAddress( $( '.address' ).value ) ) {
return false;
}
return true;
}
2023-03-23 00:36:51 +01:00
function checkAddress() {
2023-03-28 03:52:32 +02:00
if ( !isValidAddress() ) {
2023-03-23 00:36:51 +01:00
$( '.address' ).style.backgroundColor = "#ff5252";
$( '.address' ).style.border = "2px solid red";
$( '.type_of_address' ).style.border = "1px solid white";
} else {
$( '.address' ).style.backgroundColor = "initial";
$( '.address' ).style.border = "1px solid white";
$( '.type_of_address' ).style.borderStyle = "none";
}
}
2023-03-28 03:52:32 +02:00
2023-03-23 00:36:51 +01:00
$( '.address' ).onchange = checkAddress;
$( '.address' ).onpaste = checkAddress;
$( '.address' ).onkeyup = checkAddress;
2023-03-28 03:52:32 +02:00
2023-03-23 00:36:51 +01:00
async function isUsedDomain( domain ) {
2023-03-28 03:52:32 +02:00
let data = await getData( `https://api.sats.id/names/${domain}` );
2023-03-23 00:36:51 +01:00
console.log( "data:", data );
data = JSON.parse( data );
console.log( "data:", data );
if ( "name" in data ) return true;
2023-03-28 03:52:32 +02:00
if ( data[ "error" ] == "Too many requests" ) return null;
2023-03-23 00:36:51 +01:00
return false;
}
async function checkDomain() {
2023-03-28 03:52:32 +02:00
$('.dns_checker').innerHTML = 'Please wait...';
let i = 1;
let registered = [];
let rate_limited = false;
let sats_domains = $( '.dns' ).value.split("\n");
let sats_domains_cleaned = [];
for(let sats_domain in sats_domains) {
let domain = sats_domains[sats_domain].trim();
if (domain == '' || sats_domains_cleaned.includes(domain)) {
continue;
}
sats_domains_cleaned.push(domain);
}
for(let sats_domain in sats_domains_cleaned)
{
let domain = sats_domains_cleaned[sats_domain].trim();
$('.dns_checker').innerHTML = 'Checking...(' + i + '/'+sats_domains_cleaned.length+')';
let isUsed = await isUsedDomain( domain );
if ( domain && isUsed === true ) {
registered.push(domain);
} else if ( domain && isUsed === null ) {
rate_limited = true;
break;
}
await sleep(1000);
i++;
}
$('.dns_checker').innerHTML = 'Check availability';
if(rate_limited)
{
alert( 'Cannot check any domain availability as a rate limit occurred.' );
}
if(registered.length != 0)
{
alert( 'The domain(s) ' + registered.join(', ') + ' is/are already registered.' );
}
else
{
alert('All domains are available.');
2023-03-23 00:36:51 +01:00
}
}
2023-03-28 03:52:32 +02:00
2023-03-23 00:36:51 +01:00
$( '.dns_checker' ).onclick = checkDomain;
2023-03-28 03:52:32 +02:00
$('#bytes_checker').onclick = async function()
{
$('#bytes_checker').innerHTML = 'Please wait...';
let inscribed_already = [];
let errors = [];
for(let i = 0; i < files.length ; i ++)
{
$('# bytes_checker '). innerHTML = 'Please wait...(' + ( i + 1 ) + '/' + files . length + ')';
let hash_result = await getData (' https: // ordinalsbot . com / api / search ? hash = ' + files[i].sha256);
try
{
hash_result = JSON.parse(hash_result);
if(hash_result.results.length != 0)
{
inscribed_already.push(files[i].name);
}
}
catch(e)
{
errors.push(files[i].name);
}
await sleep(1000);
}
if(inscribed_already.length != 0)
{
alert("The following files are inscribed already: " + inscribed_already.join(' , '));
}
if ( errors . length != 0 )
{
alert (" Could not check the following files due to an error: " + inscribed_already . join (', '));
}
2023-03-28 04:00:19 +02:00
if ( inscribed_already . length = = 0 )
{
alert (" Your files seem not to be inscribed yet .");
}
2023-03-28 03:52:32 +02:00
$('# bytes_checker '). innerHTML = 'Check if bytes are inscribed already' ;
}
2023-03-23 00:36:51 +01:00
async function init ( num ) {
if ( ! num ) {
2023-03-28 03:52:32 +02:00
let isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
2023-03-23 00:36:51 +01:00
if ( isSafari ) $( '. safari_warning ' ). style . display = "block" ;
2023-03-28 03:52:32 +02:00
let minfee = await getMinFeeRate ();
2023-03-23 00:36:51 +01:00
$( '# sats_per_byte ' ). innerText = minfee;
$( '# sats_range ' ). value = minfee;
}
num = num + 1 ;
2023-03-28 03:52:32 +02:00
let allrates = await getAllFeeRates ();
2023-03-23 00:36:51 +01:00
$( '. minfee . num ' ). innerText = allrates[ " minimumFee " ];
$( '. midfee . num ' ). innerText = allrates[ " hourFee " ];
$( '. maxfee . num ' ). innerText = allrates[ " fastestFee " ];
2023-03-28 03:52:32 +02:00
let isgreen ;
2023-03-23 00:36:51 +01:00
$$( '. fee . num ' ). forEach ( function ( item ) {
if ( item . style . backgroundColor = = " green " || getComputedStyle ( item ). backgroundColor = = " rgb ( 0 , 128 , 0 )" ) isgreen = item;
});
if ( isgreen ) {
$( '# sats_per_byte ' ). innerText = Number( isgreen . innerText );
$( '# sats_range ' ). value = Number( isgreen . innerText );
sessionStorage [ " feerate " ] = isgreen . innerText ;
}
sessionStorage [ " bitcoin_price " ] = await getBitcoinPrice ();
await waitSomeSeconds ( 10 );
init ( num );
}
init ( 0 );
</ script >
< div class = "black-bg" onclick = "modalVanish();" ></ div >
2023-03-28 03:52:32 +02:00
< div class = "modal" >< div class = "modal-content" ></ div >< button id = "modal-reset" style = "margin-top: 15px; display: none;" onclick = "location.reload();" > Reset</ button ></ div >
2023-03-23 00:36:51 +01:00
</ body >
</ html >